@loucompanion/forge-bridge 0.1.3-dev.d46a8e73d03d → 0.1.3-dev.e7b62d8fdd33

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 (145) hide show
  1. package/README.md +83 -0
  2. package/dist/bridge/bridge.base.d.ts +4 -1
  3. package/dist/bridge/bridge.service.d.ts +55 -3
  4. package/dist/bridge/bridge.service.js +834 -87
  5. package/dist/bridge/bridge.types.d.ts +89 -1
  6. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  7. package/dist/bridge/runner-host-capabilities.js +93 -0
  8. package/dist/cleanup/cleanup.service.d.ts +14 -2
  9. package/dist/cleanup/cleanup.service.js +496 -3
  10. package/dist/cleanup/cleanup.types.d.ts +10 -0
  11. package/dist/cleanup/index.d.ts +1 -1
  12. package/dist/cleanup/index.js +1 -1
  13. package/dist/cleanup/internals.d.ts +2 -2
  14. package/dist/cleanup/internals.js +1 -1
  15. package/dist/cli/cli.help.js +37 -3
  16. package/dist/cli/cli.service.d.ts +33 -2
  17. package/dist/cli/cli.service.js +209 -27
  18. package/dist/cli/local-config.d.ts +5 -0
  19. package/dist/cli/local-config.js +45 -1
  20. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  21. package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
  22. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  23. package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
  24. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  25. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  26. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  27. package/dist/runtime/attempt-process-supervisor.js +386 -0
  28. package/dist/runtime/docker-engine-probe.d.ts +83 -0
  29. package/dist/runtime/docker-engine-probe.js +300 -0
  30. package/dist/runtime/generated-output-reset.d.ts +10 -0
  31. package/dist/runtime/generated-output-reset.js +103 -0
  32. package/dist/runtime/host-listener-inspector.d.ts +46 -0
  33. package/dist/runtime/host-listener-inspector.js +349 -0
  34. package/dist/runtime/host-port-broker.d.ts +76 -0
  35. package/dist/runtime/host-port-broker.js +333 -0
  36. package/dist/runtime/host-runtime-contract.d.ts +47 -0
  37. package/dist/runtime/host-runtime-contract.js +700 -0
  38. package/dist/runtime/host-runtime-lease.d.ts +197 -0
  39. package/dist/runtime/host-runtime-lease.js +2203 -0
  40. package/dist/runtime/host-runtime-promotion.d.ts +23 -0
  41. package/dist/runtime/host-runtime-promotion.js +159 -0
  42. package/dist/runtime/memory-capacity.d.ts +68 -0
  43. package/dist/runtime/memory-capacity.js +121 -0
  44. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  45. package/dist/runtime/node-host-runtime-adapter.js +284 -0
  46. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  47. package/dist/runtime/playwright-host-runtime-adapter.js +231 -0
  48. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  49. package/dist/runtime/runtime-isolation-lease.js +158 -0
  50. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  51. package/dist/runtime/workspace-resource-estimator.js +174 -0
  52. package/dist/sandbox/preflight.d.ts +45 -1
  53. package/dist/sandbox/preflight.js +299 -34
  54. package/dist/sandbox/process.js +125 -40
  55. package/dist/sandbox/repository-lock.js +41 -3
  56. package/dist/sandbox/runtime.d.ts +66 -3
  57. package/dist/sandbox/runtime.js +670 -105
  58. package/dist/sandbox/sandbox.types.d.ts +10 -0
  59. package/dist/sandbox/smoke.d.ts +76 -0
  60. package/dist/sandbox/smoke.js +380 -0
  61. package/dist/sandbox/worker-supervisor.js +1 -1
  62. package/dist/sandbox/worker.js +2 -5
  63. package/dist/sandbox/workspace.service.d.ts +1 -0
  64. package/dist/sandbox/workspace.service.js +28 -5
  65. package/dist/session/managed-attempt-tools.d.ts +21 -0
  66. package/dist/session/managed-attempt-tools.js +912 -0
  67. package/dist/session/managed-build-coordinator.d.ts +5 -0
  68. package/dist/session/managed-build-coordinator.js +105 -0
  69. package/dist/session/managed-build-receipt.d.ts +21 -0
  70. package/dist/session/managed-build-receipt.js +335 -0
  71. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  72. package/dist/session/phase-completion.artifacts.js +233 -24
  73. package/dist/session/phase-completion.d.ts +12 -2
  74. package/dist/session/phase-completion.helpers.d.ts +5 -0
  75. package/dist/session/phase-completion.helpers.js +51 -10
  76. package/dist/session/phase-completion.js +14 -3
  77. package/dist/session/phase-completion.prompts.d.ts +3 -3
  78. package/dist/session/phase-completion.prompts.js +24 -8
  79. package/dist/session/phase-completion.runtime.d.ts +13 -1
  80. package/dist/session/phase-completion.runtime.js +189 -8
  81. package/dist/session/phase-completion.schemas.js +117 -4
  82. package/dist/session/phase-completion.validation.d.ts +4 -1
  83. package/dist/session/phase-completion.validation.js +153 -22
  84. package/dist/session/pre-publication-validation.d.ts +46 -0
  85. package/dist/session/pre-publication-validation.js +208 -0
  86. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  87. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  88. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
  89. package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
  90. package/dist/session/provider-cli/provider-cli.deterministic.js +49 -1
  91. package/dist/session/provider-cli/provider-cli.process.d.ts +25 -0
  92. package/dist/session/provider-cli/provider-cli.process.js +463 -34
  93. package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
  94. package/dist/session/provider-cli/provider-cli.prompts.js +108 -25
  95. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
  96. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  97. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  98. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  99. package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
  100. package/dist/session/provider-cli/provider-cli.service.d.ts +22 -2
  101. package/dist/session/provider-cli/provider-cli.service.js +524 -100
  102. package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
  103. package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
  104. package/dist/session/provider-cli/provider-output-state.js +91 -16
  105. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  106. package/dist/session/provider-cli/provider-usage.js +28 -5
  107. package/dist/session/session-redaction.d.ts +3 -0
  108. package/dist/session/session-redaction.js +38 -0
  109. package/dist/session/session.base.d.ts +6 -1
  110. package/dist/session/session.service.d.ts +8 -2
  111. package/dist/session/session.service.js +522 -41
  112. package/dist/session/session.types.d.ts +72 -0
  113. package/dist/session/session.types.js +74 -3
  114. package/dist/shared/format/format-bytes.d.ts +1 -0
  115. package/dist/shared/format/format-bytes.js +9 -0
  116. package/dist/shared/path/path.d.ts +1 -0
  117. package/dist/shared/path/path.js +21 -1
  118. package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
  119. package/dist/shared/process/dotnet-toolchain.js +179 -0
  120. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  121. package/dist/shared/process/host-process-recovery.js +78 -0
  122. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  123. package/dist/shared/process/host-process-supervisor.js +330 -0
  124. package/dist/shared/process/process.d.ts +1 -0
  125. package/dist/shared/process/process.js +1 -0
  126. package/dist/shared/redaction/redaction.js +12 -2
  127. package/dist/version.d.ts +1 -1
  128. package/dist/version.js +1 -1
  129. package/dist/worktree/artifact-injector.d.ts +3 -2
  130. package/dist/worktree/artifact-injector.js +43 -4
  131. package/dist/worktree/dependency-warmer.d.ts +28 -1
  132. package/dist/worktree/dependency-warmer.js +308 -9
  133. package/dist/worktree/index.d.ts +2 -1
  134. package/dist/worktree/index.js +1 -0
  135. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  136. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  137. package/dist/worktree/worktree-paths.d.ts +4 -0
  138. package/dist/worktree/worktree-paths.js +30 -0
  139. package/dist/worktree/worktree.base.d.ts +3 -0
  140. package/dist/worktree/worktree.service.d.ts +13 -2
  141. package/dist/worktree/worktree.service.js +238 -47
  142. package/dist/worktree/worktree.types.d.ts +5 -1
  143. package/package.json +5 -3
  144. package/runtime/windows-job-supervisor.ps1 +444 -0
  145. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,444 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [ValidateSet("Outer", "Inner")]
4
+ [string]$Mode,
5
+
6
+ [Parameter(Mandatory = $true)]
7
+ [string]$PayloadPath,
8
+
9
+ [Parameter(Mandatory = $true)]
10
+ [ValidatePattern("^[0-9a-f]{64}$")]
11
+ [string]$PayloadSha256,
12
+
13
+ [string]$GateName
14
+ )
15
+
16
+ $ErrorActionPreference = "Stop"
17
+
18
+ Add-Type -TypeDefinition @"
19
+ using System;
20
+ using System.Runtime.InteropServices;
21
+ using System.Text;
22
+
23
+ public static class ForgeWindowsJob
24
+ {
25
+ [StructLayout(LayoutKind.Sequential)]
26
+ private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
27
+ {
28
+ public long PerProcessUserTimeLimit;
29
+ public long PerJobUserTimeLimit;
30
+ public uint LimitFlags;
31
+ public UIntPtr MinimumWorkingSetSize;
32
+ public UIntPtr MaximumWorkingSetSize;
33
+ public uint ActiveProcessLimit;
34
+ public UIntPtr Affinity;
35
+ public uint PriorityClass;
36
+ public uint SchedulingClass;
37
+ }
38
+
39
+ [StructLayout(LayoutKind.Sequential)]
40
+ private struct IO_COUNTERS
41
+ {
42
+ public ulong ReadOperationCount;
43
+ public ulong WriteOperationCount;
44
+ public ulong OtherOperationCount;
45
+ public ulong ReadTransferCount;
46
+ public ulong WriteTransferCount;
47
+ public ulong OtherTransferCount;
48
+ }
49
+
50
+ [StructLayout(LayoutKind.Sequential)]
51
+ private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
52
+ {
53
+ public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
54
+ public IO_COUNTERS IoInfo;
55
+ public UIntPtr ProcessMemoryLimit;
56
+ public UIntPtr JobMemoryLimit;
57
+ public UIntPtr PeakProcessMemoryUsed;
58
+ public UIntPtr PeakJobMemoryUsed;
59
+ }
60
+
61
+ private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
62
+ private const int JobObjectExtendedLimitInformation = 9;
63
+
64
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
65
+ private static extern IntPtr CreateJobObject(IntPtr securityAttributes, string name);
66
+
67
+ [DllImport("kernel32.dll", SetLastError = true)]
68
+ private static extern bool SetInformationJobObject(
69
+ IntPtr job,
70
+ int informationClass,
71
+ IntPtr information,
72
+ uint informationLength);
73
+
74
+ [DllImport("kernel32.dll", SetLastError = true)]
75
+ public static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
76
+
77
+ [DllImport("kernel32.dll", SetLastError = true)]
78
+ public static extern bool TerminateJobObject(IntPtr job, uint exitCode);
79
+
80
+ [DllImport("kernel32.dll", SetLastError = true)]
81
+ private static extern bool QueryInformationJobObject(
82
+ IntPtr job,
83
+ int informationClass,
84
+ IntPtr information,
85
+ uint informationLength,
86
+ out uint returnLength);
87
+
88
+ [DllImport("kernel32.dll", SetLastError = true)]
89
+ public static extern bool CloseHandle(IntPtr handle);
90
+
91
+ public static IntPtr CreateKillOnCloseJob(string name)
92
+ {
93
+ IntPtr job = CreateJobObject(IntPtr.Zero, name);
94
+ if (job == IntPtr.Zero)
95
+ {
96
+ throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "CreateJobObject failed.");
97
+ }
98
+
99
+ var limits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
100
+ limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
101
+ int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
102
+ IntPtr pointer = Marshal.AllocHGlobal(size);
103
+ try
104
+ {
105
+ Marshal.StructureToPtr(limits, pointer, false);
106
+ if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, pointer, (uint)size))
107
+ {
108
+ throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "SetInformationJobObject failed.");
109
+ }
110
+ }
111
+ catch
112
+ {
113
+ CloseHandle(job);
114
+ throw;
115
+ }
116
+ finally
117
+ {
118
+ Marshal.FreeHGlobal(pointer);
119
+ }
120
+
121
+ return job;
122
+ }
123
+
124
+ public static ulong PeakJobMemoryUsed(IntPtr job)
125
+ {
126
+ int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
127
+ IntPtr pointer = Marshal.AllocHGlobal(size);
128
+ try
129
+ {
130
+ uint returned;
131
+ if (!QueryInformationJobObject(job, JobObjectExtendedLimitInformation, pointer, (uint)size, out returned))
132
+ {
133
+ throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "QueryInformationJobObject failed.");
134
+ }
135
+ var information = (JOBOBJECT_EXTENDED_LIMIT_INFORMATION)Marshal.PtrToStructure(
136
+ pointer,
137
+ typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
138
+ return information.PeakJobMemoryUsed.ToUInt64();
139
+ }
140
+ finally
141
+ {
142
+ Marshal.FreeHGlobal(pointer);
143
+ }
144
+ }
145
+
146
+ public static string BuildArguments(object[] arguments)
147
+ {
148
+ var result = new StringBuilder();
149
+ for (int index = 0; index < arguments.Length; index++)
150
+ {
151
+ if (index > 0) result.Append(' ');
152
+ result.Append(QuoteArgument(Convert.ToString(arguments[index]) ?? String.Empty));
153
+ }
154
+ return result.ToString();
155
+ }
156
+
157
+ private static string QuoteArgument(string argument)
158
+ {
159
+ if (argument.Length > 0 && argument.IndexOfAny(new[] { ' ', '\t', '\n', '\v', '"' }) < 0)
160
+ {
161
+ return argument;
162
+ }
163
+
164
+ var result = new StringBuilder();
165
+ result.Append('"');
166
+ int backslashes = 0;
167
+ foreach (char character in argument)
168
+ {
169
+ if (character == '\\')
170
+ {
171
+ backslashes++;
172
+ continue;
173
+ }
174
+
175
+ if (character == '"')
176
+ {
177
+ result.Append('\\', backslashes * 2 + 1);
178
+ result.Append('"');
179
+ backslashes = 0;
180
+ continue;
181
+ }
182
+
183
+ result.Append('\\', backslashes);
184
+ backslashes = 0;
185
+ result.Append(character);
186
+ }
187
+
188
+ result.Append('\\', backslashes * 2);
189
+ result.Append('"');
190
+ return result.ToString();
191
+ }
192
+ }
193
+ "@
194
+
195
+ function Read-ForgeSupervisorPayload {
196
+ if (-not [IO.Path]::IsPathRooted($PayloadPath)) {
197
+ throw "The supervisor payload path must be absolute."
198
+ }
199
+ $bytes = [IO.File]::ReadAllBytes($PayloadPath)
200
+ $hasher = [Security.Cryptography.SHA256]::Create()
201
+ try {
202
+ $actualSha256 = -join ($hasher.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") })
203
+ }
204
+ finally {
205
+ $hasher.Dispose()
206
+ }
207
+ if (-not [string]::Equals($actualSha256, $PayloadSha256, [StringComparison]::Ordinal)) {
208
+ throw "The supervisor payload does not match its expected SHA-256 identity."
209
+ }
210
+ $utf8 = New-Object Text.UTF8Encoding($false, $true)
211
+ $json = $utf8.GetString($bytes)
212
+ return $json | ConvertFrom-Json
213
+ }
214
+
215
+ function New-ForgeChildProcess(
216
+ [string]$command,
217
+ [object[]]$arguments,
218
+ [string]$workingDirectory,
219
+ [bool]$redirectStreams
220
+ ) {
221
+ $maximumAttempts = 3
222
+ for ($attempt = 1; $attempt -le $maximumAttempts; $attempt += 1) {
223
+ $startInfo = New-Object System.Diagnostics.ProcessStartInfo
224
+ $startInfo.FileName = $command
225
+ $startInfo.Arguments = [ForgeWindowsJob]::BuildArguments($arguments)
226
+ $startInfo.WorkingDirectory = $workingDirectory
227
+ $startInfo.UseShellExecute = $false
228
+ $startInfo.CreateNoWindow = $true
229
+ $startInfo.RedirectStandardInput = $redirectStreams
230
+ $startInfo.RedirectStandardOutput = $redirectStreams
231
+ $startInfo.RedirectStandardError = $redirectStreams
232
+
233
+ $child = New-Object System.Diagnostics.Process
234
+ $child.StartInfo = $startInfo
235
+ try {
236
+ if (-not $child.Start()) {
237
+ throw "Failed to start supervised process '$command'."
238
+ }
239
+ $child | Add-Member -NotePropertyName ForgeStartAttempt -NotePropertyValue $attempt
240
+ return $child
241
+ }
242
+ catch {
243
+ $nativeError = $_.Exception
244
+ while ($nativeError.InnerException) {
245
+ $nativeError = $nativeError.InnerException
246
+ }
247
+ $retryable = $nativeError -is [ComponentModel.Win32Exception] -and $nativeError.NativeErrorCode -in @(5, 32, 33)
248
+ $child.Dispose()
249
+ if (-not $retryable -or $attempt -eq $maximumAttempts) {
250
+ throw
251
+ }
252
+ Start-Sleep -Milliseconds (100 * $attempt)
253
+ }
254
+ }
255
+ }
256
+
257
+ function Write-ForgeProviderReady([object]$request, [Diagnostics.Process]$provider) {
258
+ $readyPath = [string]$request.readyPath
259
+ if ([string]::IsNullOrWhiteSpace($readyPath) -or -not [IO.Path]::IsPathRooted($readyPath)) {
260
+ throw "The provider-ready path must be absolute."
261
+ }
262
+ $payloadDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PayloadPath))
263
+ $readyDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($readyPath))
264
+ if (-not [string]::Equals($payloadDirectory, $readyDirectory, [StringComparison]::OrdinalIgnoreCase)) {
265
+ throw "The provider-ready path must be beside the supervisor payload."
266
+ }
267
+
268
+ $ready = @{
269
+ schemaVersion = 1
270
+ supervisorId = [string]$request.supervisorId
271
+ rootPid = [int]$env:FORGE_SUPERVISOR_ROOT_PID
272
+ rootIdentity = [string]$env:FORGE_SUPERVISOR_ROOT_IDENTITY
273
+ providerPid = $provider.Id
274
+ providerCreatedAt = $provider.StartTime.ToUniversalTime().ToString("O")
275
+ providerIdentity = "win32:$($provider.StartTime.ToUniversalTime().Ticks)"
276
+ startAttempt = [int]$provider.ForgeStartAttempt
277
+ } | ConvertTo-Json -Compress
278
+ $temporaryPath = "$readyPath.$([Guid]::NewGuid().ToString('N')).tmp"
279
+ [IO.File]::WriteAllText($temporaryPath, "$ready`n", (New-Object Text.UTF8Encoding($false)))
280
+ [IO.File]::Move($temporaryPath, $readyPath)
281
+ }
282
+
283
+ function Wait-ForgeProviderStartGrant([object]$request) {
284
+ $grantPath = [string]$request.startGrantPath
285
+ if ([string]::IsNullOrWhiteSpace($grantPath) -or -not [IO.Path]::IsPathRooted($grantPath)) {
286
+ throw "The provider start-grant path must be absolute."
287
+ }
288
+ $payloadDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PayloadPath))
289
+ $grantDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($grantPath))
290
+ if (-not [string]::Equals($payloadDirectory, $grantDirectory, [StringComparison]::OrdinalIgnoreCase)) {
291
+ throw "The provider start-grant path must be beside the supervisor payload."
292
+ }
293
+ for ($attempt = 0; $attempt -lt 1200; $attempt += 1) {
294
+ if ([IO.File]::Exists($grantPath)) {
295
+ return
296
+ }
297
+ Start-Sleep -Milliseconds 25
298
+ }
299
+ throw "Timed out waiting for the provider start grant."
300
+ }
301
+
302
+ function Start-ForgeOutputCopies([Diagnostics.Process]$child) {
303
+ return @(
304
+ $child.StandardOutput.BaseStream.CopyToAsync([Console]::OpenStandardOutput()),
305
+ $child.StandardError.BaseStream.CopyToAsync([Console]::OpenStandardError())
306
+ )
307
+ }
308
+
309
+ function Write-ForgeStandardInput([Diagnostics.Process]$child, [string]$inputText) {
310
+ if ($inputText.Length -gt 0) {
311
+ $child.StandardInput.Write($inputText)
312
+ }
313
+ $child.StandardInput.Close()
314
+ }
315
+
316
+ function Write-ForgeMemoryTelemetry([object]$request, [IntPtr]$job) {
317
+ $telemetryPath = [string]$request.memoryTelemetryPath
318
+ if ([string]::IsNullOrWhiteSpace($telemetryPath) -or -not [IO.Path]::IsPathRooted($telemetryPath)) {
319
+ throw "The memory telemetry path must be absolute."
320
+ }
321
+ $payloadDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PayloadPath))
322
+ $telemetryDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($telemetryPath))
323
+ if (-not [string]::Equals($payloadDirectory, $telemetryDirectory, [StringComparison]::OrdinalIgnoreCase)) {
324
+ throw "The memory telemetry path must be beside the supervisor payload."
325
+ }
326
+ $telemetry = @{
327
+ schemaVersion = 1
328
+ peakMemoryBytes = [ForgeWindowsJob]::PeakJobMemoryUsed($job)
329
+ sampleCount = 1
330
+ source = "windows_job_object_peak_job_memory"
331
+ observedAt = [DateTimeOffset]::UtcNow.ToString("O")
332
+ } | ConvertTo-Json -Compress
333
+ $temporaryPath = "$telemetryPath.$([Guid]::NewGuid().ToString('N')).tmp"
334
+ try {
335
+ [IO.File]::WriteAllText($temporaryPath, "$telemetry`n", (New-Object Text.UTF8Encoding($false)))
336
+ [IO.File]::Move($temporaryPath, $telemetryPath)
337
+ }
338
+ finally {
339
+ if ([IO.File]::Exists($temporaryPath)) {
340
+ try { [IO.File]::Delete($temporaryPath) } catch { }
341
+ }
342
+ }
343
+ }
344
+
345
+ function Write-ForgeMemoryTelemetryBestEffort([object]$request, [IntPtr]$job) {
346
+ try {
347
+ Write-ForgeMemoryTelemetry $request $job
348
+ }
349
+ catch {
350
+ # Telemetry is optional. Provider output, descendant cleanup, and the real
351
+ # provider exit code remain authoritative if querying or persisting it fails.
352
+ }
353
+ }
354
+
355
+ if ($Mode -eq "Inner") {
356
+ if ([string]::IsNullOrWhiteSpace($GateName)) {
357
+ throw "The inner supervisor requires a gate name."
358
+ }
359
+
360
+ $inputText = [Console]::In.ReadToEnd()
361
+ $request = Read-ForgeSupervisorPayload
362
+ $gate = [Threading.EventWaitHandle]::OpenExisting($GateName)
363
+ try {
364
+ if (-not $gate.WaitOne(30000)) {
365
+ throw "Timed out waiting for Windows Job Object assignment."
366
+ }
367
+ }
368
+ finally {
369
+ $gate.Dispose()
370
+ }
371
+
372
+ $provider = New-ForgeChildProcess ([string]$request.command) @($request.args) ([string]$request.cwd) $true
373
+ Write-ForgeProviderReady $request $provider
374
+ $copies = Start-ForgeOutputCopies $provider
375
+ Wait-ForgeProviderStartGrant $request
376
+ Write-ForgeStandardInput $provider $inputText
377
+ $provider.WaitForExit()
378
+ if (-not [Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]$copies, 1000)) {
379
+ $provider.StandardOutput.Dispose()
380
+ $provider.StandardError.Dispose()
381
+ }
382
+ exit $provider.ExitCode
383
+ }
384
+
385
+ $inputText = [Console]::In.ReadToEnd()
386
+ $request = Read-ForgeSupervisorPayload
387
+ $env:FORGE_SUPERVISOR_ROOT_PID = [string]$PID
388
+ $env:FORGE_SUPERVISOR_ROOT_IDENTITY = "win32:$([Diagnostics.Process]::GetCurrentProcess().StartTime.ToUniversalTime().Ticks)"
389
+ $parent = [Diagnostics.Process]::GetProcessById([int]$request.parentPid)
390
+ $jobName = "Local\Forge-$([string]$request.supervisorId)"
391
+ $job = [ForgeWindowsJob]::CreateKillOnCloseJob($jobName)
392
+ $gateNameValue = "Local\ForgeGate-$([Guid]::NewGuid().ToString('N'))"
393
+ $createdNew = $false
394
+ $gate = New-Object Threading.EventWaitHandle($false, [Threading.EventResetMode]::ManualReset, $gateNameValue, [ref]$createdNew)
395
+ $inner = $null
396
+
397
+ try {
398
+ $powerShell = Join-Path $PSHOME "powershell.exe"
399
+ $innerArguments = @(
400
+ "-NoLogo",
401
+ "-NoProfile",
402
+ "-NonInteractive",
403
+ "-ExecutionPolicy",
404
+ "Bypass",
405
+ "-File",
406
+ $PSCommandPath,
407
+ "-Mode",
408
+ "Inner",
409
+ "-PayloadPath",
410
+ $PayloadPath,
411
+ "-PayloadSha256",
412
+ $PayloadSha256,
413
+ "-GateName",
414
+ $gateNameValue
415
+ )
416
+ $inner = New-ForgeChildProcess $powerShell $innerArguments ([string]$request.cwd) $true
417
+ if (-not [ForgeWindowsJob]::AssignProcessToJobObject($job, $inner.Handle)) {
418
+ throw (New-Object System.ComponentModel.Win32Exception([Runtime.InteropServices.Marshal]::GetLastWin32Error(), "AssignProcessToJobObject failed."))
419
+ }
420
+
421
+ $copies = Start-ForgeOutputCopies $inner
422
+ $gate.Set() | Out-Null
423
+ Write-ForgeStandardInput $inner $inputText
424
+ while (-not $inner.WaitForExit(250)) {
425
+ $parent.Refresh()
426
+ if ($parent.HasExited) {
427
+ [ForgeWindowsJob]::TerminateJobObject($job, 125) | Out-Null
428
+ exit 125
429
+ }
430
+ }
431
+
432
+ $exitCode = $inner.ExitCode
433
+ Write-ForgeMemoryTelemetryBestEffort $request $job
434
+ [ForgeWindowsJob]::TerminateJobObject($job, 1) | Out-Null
435
+ [Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]$copies, 2000) | Out-Null
436
+ exit $exitCode
437
+ }
438
+ finally {
439
+ if ($inner -and -not $inner.HasExited) {
440
+ [ForgeWindowsJob]::TerminateJobObject($job, 1) | Out-Null
441
+ }
442
+ $gate.Dispose()
443
+ [ForgeWindowsJob]::CloseHandle($job) | Out-Null
444
+ }
@@ -0,0 +1,33 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [int]$RootProcessId,
4
+ [Parameter(Mandatory = $true)]
5
+ [string]$ListenerProcessIds
6
+ )
7
+
8
+ $ErrorActionPreference = "Stop"
9
+ $parents = @{}
10
+ Get-CimInstance Win32_Process | ForEach-Object {
11
+ $parents[[int]$_.ProcessId] = [int]$_.ParentProcessId
12
+ }
13
+
14
+ $results = @()
15
+ foreach ($listenerText in $ListenerProcessIds.Split(',')) {
16
+ $current = [int]$listenerText
17
+ $visited = @{}
18
+ $owned = $false
19
+ while ($current -gt 0 -and -not $visited.ContainsKey($current)) {
20
+ if ($current -eq $RootProcessId) {
21
+ $owned = $true
22
+ break
23
+ }
24
+ $visited[$current] = $true
25
+ if (-not $parents.ContainsKey($current)) {
26
+ break
27
+ }
28
+ $current = [int]$parents[$current]
29
+ }
30
+ $results += $owned
31
+ }
32
+
33
+ ConvertTo-Json -Compress -InputObject @($results)