@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.1ff37038366e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -1
- package/dist/bridge/bridge-chat.types.d.ts +111 -0
- package/dist/bridge/bridge-chat.types.js +401 -0
- package/dist/bridge/bridge.base.d.ts +9 -1
- package/dist/bridge/bridge.service.d.ts +129 -3
- package/dist/bridge/bridge.service.js +2427 -70
- package/dist/bridge/bridge.types.d.ts +218 -2
- package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
- package/dist/bridge/dispatch-result-outbox.js +62 -0
- package/dist/bridge/dispatch-result-worker.d.ts +2 -0
- package/dist/bridge/dispatch-result-worker.js +122 -0
- package/dist/bridge/internals.d.ts +2 -0
- package/dist/bridge/internals.js +1 -0
- package/dist/bridge/readiness-result-outbox.d.ts +21 -0
- package/dist/bridge/readiness-result-outbox.js +54 -0
- package/dist/bridge/readiness-result-worker.d.ts +2 -0
- package/dist/bridge/readiness-result-worker.js +121 -0
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.d.ts +2 -0
- package/dist/cli/cli.help.js +217 -0
- package/dist/cli/cli.service.d.ts +55 -7
- package/dist/cli/cli.service.js +429 -219
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
- package/dist/runtime/attempt-database-broker.d.ts +89 -0
- package/dist/runtime/attempt-database-broker.js +390 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
- package/dist/runtime/attempt-path-operation.d.ts +21 -0
- package/dist/runtime/attempt-path-operation.js +94 -0
- package/dist/runtime/attempt-private-directory.d.ts +6 -0
- package/dist/runtime/attempt-private-directory.js +54 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
- package/dist/runtime/attempt-process-supervisor.js +397 -0
- package/dist/runtime/canonical-source-file.d.ts +3 -0
- package/dist/runtime/canonical-source-file.js +54 -0
- package/dist/runtime/docker-engine-probe.d.ts +90 -0
- package/dist/runtime/docker-engine-probe.js +333 -0
- package/dist/runtime/generated-output-reset.d.ts +20 -0
- package/dist/runtime/generated-output-reset.js +157 -0
- package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
- package/dist/runtime/host-database-capability-boundary.js +62 -0
- package/dist/runtime/host-diagnostics.d.ts +11 -0
- package/dist/runtime/host-diagnostics.js +191 -0
- package/dist/runtime/host-execution-root.d.ts +4 -0
- package/dist/runtime/host-execution-root.js +39 -0
- package/dist/runtime/host-listener-inspector.d.ts +51 -0
- package/dist/runtime/host-listener-inspector.js +385 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +58 -0
- package/dist/runtime/host-runtime-contract.js +813 -0
- package/dist/runtime/host-runtime-lease.d.ts +217 -0
- package/dist/runtime/host-runtime-lease.js +2487 -0
- package/dist/runtime/host-runtime-promotion.d.ts +32 -0
- package/dist/runtime/host-runtime-promotion.js +206 -0
- package/dist/runtime/memory-capacity.d.ts +70 -0
- package/dist/runtime/memory-capacity.js +123 -0
- package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
- package/dist/runtime/native-test-postgres-fallback.js +91 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +285 -0
- package/dist/runtime/pinned-directory-process.d.ts +9 -0
- package/dist/runtime/pinned-directory-process.js +61 -0
- package/dist/runtime/pinned-source-directory.d.ts +17 -0
- package/dist/runtime/pinned-source-directory.js +94 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
- package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
- package/dist/runtime/prepared-runtime-identity.js +218 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
- package/dist/runtime/prepared-runtime-readiness.js +285 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +161 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +6 -0
- package/dist/sandbox/repository-lock.js +128 -0
- package/dist/sandbox/runtime.d.ts +302 -0
- package/dist/sandbox/runtime.js +2367 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +74 -0
- package/dist/session/managed-attempt-tools.d.ts +28 -0
- package/dist/session/managed-attempt-tools.js +1015 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +105 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +337 -0
- package/dist/session/managed-verification-timeouts.d.ts +8 -0
- package/dist/session/managed-verification-timeouts.js +17 -0
- package/dist/session/phase-completion.artifacts.d.ts +27 -0
- package/dist/session/phase-completion.artifacts.js +829 -0
- package/dist/session/phase-completion.d.ts +53 -42
- package/dist/session/phase-completion.helpers.d.ts +117 -0
- package/dist/session/phase-completion.helpers.js +707 -0
- package/dist/session/phase-completion.js +125 -3007
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +233 -0
- package/dist/session/phase-completion.runtime.d.ts +49 -0
- package/dist/session/phase-completion.runtime.js +840 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +859 -0
- package/dist/session/phase-completion.validation.d.ts +38 -0
- package/dist/session/phase-completion.validation.js +918 -0
- package/dist/session/pre-publication-validation.d.ts +75 -0
- package/dist/session/pre-publication-validation.js +661 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
- package/dist/session/provider-cli/provider-cli.process.js +874 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +16 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +81 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
- package/dist/session/provider-cli/provider-cli.service.js +1257 -2497
- package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
- package/dist/session/provider-cli/provider-output-state.js +817 -0
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +448 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +586 -59
- package/dist/session/session.types.d.ts +324 -3
- package/dist/session/session.types.js +939 -10
- package/dist/session/unreferenced-exports.d.ts +12 -0
- package/dist/session/unreferenced-exports.js +59 -0
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/git/git.js +2 -1
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
- package/dist/shared/process/dotnet-toolchain.js +307 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +78 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +330 -0
- package/dist/shared/process/process.d.ts +3 -0
- package/dist/shared/process/process.js +13 -7
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +41 -0
- package/dist/shared/provider-capabilities.d.ts +4 -0
- package/dist/shared/provider-capabilities.js +88 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +47 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +82 -10
- package/dist/worktree/dependency-warmer.d.ts +53 -1
- package/dist/worktree/dependency-warmer.js +619 -11
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +16 -3
- package/dist/worktree/worktree.service.js +334 -54
- package/dist/worktree/worktree.types.d.ts +9 -1
- package/package.json +12 -7
- package/runtime/windows-job-supervisor.ps1 +722 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -0,0 +1,722 @@
|
|
|
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.IO;
|
|
21
|
+
using System.Runtime.InteropServices;
|
|
22
|
+
using System.Text;
|
|
23
|
+
using Microsoft.Win32.SafeHandles;
|
|
24
|
+
|
|
25
|
+
// A provider created suspended exists (so its exact identity can be recorded)
|
|
26
|
+
// but runs no code until Resume. Providers such as Claude stop waiting for
|
|
27
|
+
// stdin after a few seconds, so the start grant must gate execution, not input.
|
|
28
|
+
public sealed class ForgeSuspendedProcess : IDisposable
|
|
29
|
+
{
|
|
30
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
31
|
+
private struct STARTUPINFO
|
|
32
|
+
{
|
|
33
|
+
public int cb;
|
|
34
|
+
public string lpReserved;
|
|
35
|
+
public string lpDesktop;
|
|
36
|
+
public string lpTitle;
|
|
37
|
+
public int dwX;
|
|
38
|
+
public int dwY;
|
|
39
|
+
public int dwXSize;
|
|
40
|
+
public int dwYSize;
|
|
41
|
+
public int dwXCountChars;
|
|
42
|
+
public int dwYCountChars;
|
|
43
|
+
public int dwFillAttribute;
|
|
44
|
+
public int dwFlags;
|
|
45
|
+
public short wShowWindow;
|
|
46
|
+
public short cbReserved2;
|
|
47
|
+
public IntPtr lpReserved2;
|
|
48
|
+
public IntPtr hStdInput;
|
|
49
|
+
public IntPtr hStdOutput;
|
|
50
|
+
public IntPtr hStdError;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
54
|
+
private struct PROCESS_INFORMATION
|
|
55
|
+
{
|
|
56
|
+
public IntPtr hProcess;
|
|
57
|
+
public IntPtr hThread;
|
|
58
|
+
public int dwProcessId;
|
|
59
|
+
public int dwThreadId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
63
|
+
private struct SECURITY_ATTRIBUTES
|
|
64
|
+
{
|
|
65
|
+
public int nLength;
|
|
66
|
+
public IntPtr lpSecurityDescriptor;
|
|
67
|
+
public bool bInheritHandle;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private const uint CREATE_SUSPENDED = 0x00000004;
|
|
71
|
+
private const uint CREATE_NO_WINDOW = 0x08000000;
|
|
72
|
+
private const int STARTF_USESTDHANDLES = 0x00000100;
|
|
73
|
+
private const int HANDLE_FLAG_INHERIT = 0x00000001;
|
|
74
|
+
private const uint INFINITE = 0xFFFFFFFF;
|
|
75
|
+
|
|
76
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
77
|
+
private static extern bool CreatePipe(
|
|
78
|
+
out SafeFileHandle readPipe,
|
|
79
|
+
out SafeFileHandle writePipe,
|
|
80
|
+
ref SECURITY_ATTRIBUTES attributes,
|
|
81
|
+
int size);
|
|
82
|
+
|
|
83
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
84
|
+
private static extern bool SetHandleInformation(SafeFileHandle handle, int mask, int flags);
|
|
85
|
+
|
|
86
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
87
|
+
private static extern bool CreateProcess(
|
|
88
|
+
string applicationName,
|
|
89
|
+
StringBuilder commandLine,
|
|
90
|
+
IntPtr processAttributes,
|
|
91
|
+
IntPtr threadAttributes,
|
|
92
|
+
bool inheritHandles,
|
|
93
|
+
uint creationFlags,
|
|
94
|
+
IntPtr environment,
|
|
95
|
+
string currentDirectory,
|
|
96
|
+
ref STARTUPINFO startupInfo,
|
|
97
|
+
out PROCESS_INFORMATION processInformation);
|
|
98
|
+
|
|
99
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
100
|
+
private static extern uint ResumeThread(IntPtr thread);
|
|
101
|
+
|
|
102
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
103
|
+
private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
104
|
+
|
|
105
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
106
|
+
private static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);
|
|
107
|
+
|
|
108
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
109
|
+
private static extern bool TerminateProcess(IntPtr process, uint exitCode);
|
|
110
|
+
|
|
111
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
112
|
+
private static extern bool CloseHandle(IntPtr handle);
|
|
113
|
+
|
|
114
|
+
private IntPtr process;
|
|
115
|
+
private IntPtr thread;
|
|
116
|
+
|
|
117
|
+
private ForgeSuspendedProcess(PROCESS_INFORMATION information, Stream input, Stream output, Stream error)
|
|
118
|
+
{
|
|
119
|
+
process = information.hProcess;
|
|
120
|
+
thread = information.hThread;
|
|
121
|
+
Id = information.dwProcessId;
|
|
122
|
+
StandardInput = input;
|
|
123
|
+
StandardOutput = output;
|
|
124
|
+
StandardError = error;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public int Id { get; private set; }
|
|
128
|
+
public Stream StandardInput { get; private set; }
|
|
129
|
+
public Stream StandardOutput { get; private set; }
|
|
130
|
+
public Stream StandardError { get; private set; }
|
|
131
|
+
|
|
132
|
+
// Builds the same command line System.Diagnostics.Process does, so command
|
|
133
|
+
// resolution is unchanged from the unsuspended launch.
|
|
134
|
+
public static ForgeSuspendedProcess Start(string fileName, string arguments, string workingDirectory)
|
|
135
|
+
{
|
|
136
|
+
SafeFileHandle inputRead = null, inputWrite = null;
|
|
137
|
+
SafeFileHandle outputRead = null, outputWrite = null;
|
|
138
|
+
SafeFileHandle errorRead = null, errorWrite = null;
|
|
139
|
+
bool started = false;
|
|
140
|
+
try
|
|
141
|
+
{
|
|
142
|
+
CreateChildPipe(out inputRead, out inputWrite, false);
|
|
143
|
+
CreateChildPipe(out outputRead, out outputWrite, true);
|
|
144
|
+
CreateChildPipe(out errorRead, out errorWrite, true);
|
|
145
|
+
|
|
146
|
+
var startupInfo = new STARTUPINFO();
|
|
147
|
+
startupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
|
|
148
|
+
startupInfo.dwFlags = STARTF_USESTDHANDLES;
|
|
149
|
+
startupInfo.hStdInput = inputRead.DangerousGetHandle();
|
|
150
|
+
startupInfo.hStdOutput = outputWrite.DangerousGetHandle();
|
|
151
|
+
startupInfo.hStdError = errorWrite.DangerousGetHandle();
|
|
152
|
+
|
|
153
|
+
string trimmed = fileName.Trim();
|
|
154
|
+
bool quoted = trimmed.StartsWith("\"", StringComparison.Ordinal) && trimmed.EndsWith("\"", StringComparison.Ordinal);
|
|
155
|
+
var commandLine = new StringBuilder();
|
|
156
|
+
if (!quoted) commandLine.Append('"');
|
|
157
|
+
commandLine.Append(trimmed);
|
|
158
|
+
if (!quoted) commandLine.Append('"');
|
|
159
|
+
if (!String.IsNullOrEmpty(arguments)) commandLine.Append(' ').Append(arguments);
|
|
160
|
+
|
|
161
|
+
PROCESS_INFORMATION information;
|
|
162
|
+
if (!CreateProcess(
|
|
163
|
+
null,
|
|
164
|
+
commandLine,
|
|
165
|
+
IntPtr.Zero,
|
|
166
|
+
IntPtr.Zero,
|
|
167
|
+
true,
|
|
168
|
+
CREATE_SUSPENDED | CREATE_NO_WINDOW,
|
|
169
|
+
IntPtr.Zero,
|
|
170
|
+
String.IsNullOrEmpty(workingDirectory) ? null : workingDirectory,
|
|
171
|
+
ref startupInfo,
|
|
172
|
+
out information))
|
|
173
|
+
{
|
|
174
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "CreateProcess failed.");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
started = true;
|
|
178
|
+
return new ForgeSuspendedProcess(
|
|
179
|
+
information,
|
|
180
|
+
new FileStream(inputWrite, FileAccess.Write, 4096, false),
|
|
181
|
+
new FileStream(outputRead, FileAccess.Read, 4096, false),
|
|
182
|
+
new FileStream(errorRead, FileAccess.Read, 4096, false));
|
|
183
|
+
}
|
|
184
|
+
finally
|
|
185
|
+
{
|
|
186
|
+
// The child owns its ends now; keeping them open would hide EOF.
|
|
187
|
+
if (inputRead != null) inputRead.Dispose();
|
|
188
|
+
if (outputWrite != null) outputWrite.Dispose();
|
|
189
|
+
if (errorWrite != null) errorWrite.Dispose();
|
|
190
|
+
if (!started)
|
|
191
|
+
{
|
|
192
|
+
if (inputWrite != null) inputWrite.Dispose();
|
|
193
|
+
if (outputRead != null) outputRead.Dispose();
|
|
194
|
+
if (errorRead != null) errorRead.Dispose();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Only the child's end is inheritable; the supervisor's end stays private.
|
|
200
|
+
private static void CreateChildPipe(out SafeFileHandle read, out SafeFileHandle write, bool parentReads)
|
|
201
|
+
{
|
|
202
|
+
var attributes = new SECURITY_ATTRIBUTES();
|
|
203
|
+
attributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
204
|
+
attributes.bInheritHandle = true;
|
|
205
|
+
if (!CreatePipe(out read, out write, ref attributes, 0))
|
|
206
|
+
{
|
|
207
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "CreatePipe failed.");
|
|
208
|
+
}
|
|
209
|
+
SafeFileHandle parentEnd = parentReads ? read : write;
|
|
210
|
+
if (!SetHandleInformation(parentEnd, HANDLE_FLAG_INHERIT, 0))
|
|
211
|
+
{
|
|
212
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "SetHandleInformation failed.");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public void Resume()
|
|
217
|
+
{
|
|
218
|
+
if (ResumeThread(thread) == UInt32.MaxValue)
|
|
219
|
+
{
|
|
220
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "ResumeThread failed.");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public int WaitForExit()
|
|
225
|
+
{
|
|
226
|
+
if (WaitForSingleObject(process, INFINITE) != 0)
|
|
227
|
+
{
|
|
228
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "WaitForSingleObject failed.");
|
|
229
|
+
}
|
|
230
|
+
uint exitCode;
|
|
231
|
+
if (!GetExitCodeProcess(process, out exitCode))
|
|
232
|
+
{
|
|
233
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "GetExitCodeProcess failed.");
|
|
234
|
+
}
|
|
235
|
+
return unchecked((int)exitCode);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
public void Kill()
|
|
239
|
+
{
|
|
240
|
+
TerminateProcess(process, 1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public void Dispose()
|
|
244
|
+
{
|
|
245
|
+
StandardInput.Dispose();
|
|
246
|
+
StandardOutput.Dispose();
|
|
247
|
+
StandardError.Dispose();
|
|
248
|
+
if (thread != IntPtr.Zero) { CloseHandle(thread); thread = IntPtr.Zero; }
|
|
249
|
+
if (process != IntPtr.Zero) { CloseHandle(process); process = IntPtr.Zero; }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public static class ForgeWindowsJob
|
|
254
|
+
{
|
|
255
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
256
|
+
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
257
|
+
{
|
|
258
|
+
public long PerProcessUserTimeLimit;
|
|
259
|
+
public long PerJobUserTimeLimit;
|
|
260
|
+
public uint LimitFlags;
|
|
261
|
+
public UIntPtr MinimumWorkingSetSize;
|
|
262
|
+
public UIntPtr MaximumWorkingSetSize;
|
|
263
|
+
public uint ActiveProcessLimit;
|
|
264
|
+
public UIntPtr Affinity;
|
|
265
|
+
public uint PriorityClass;
|
|
266
|
+
public uint SchedulingClass;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
270
|
+
private struct IO_COUNTERS
|
|
271
|
+
{
|
|
272
|
+
public ulong ReadOperationCount;
|
|
273
|
+
public ulong WriteOperationCount;
|
|
274
|
+
public ulong OtherOperationCount;
|
|
275
|
+
public ulong ReadTransferCount;
|
|
276
|
+
public ulong WriteTransferCount;
|
|
277
|
+
public ulong OtherTransferCount;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
281
|
+
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
282
|
+
{
|
|
283
|
+
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
284
|
+
public IO_COUNTERS IoInfo;
|
|
285
|
+
public UIntPtr ProcessMemoryLimit;
|
|
286
|
+
public UIntPtr JobMemoryLimit;
|
|
287
|
+
public UIntPtr PeakProcessMemoryUsed;
|
|
288
|
+
public UIntPtr PeakJobMemoryUsed;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
|
|
292
|
+
private const int JobObjectExtendedLimitInformation = 9;
|
|
293
|
+
|
|
294
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
295
|
+
private static extern IntPtr CreateJobObject(IntPtr securityAttributes, string name);
|
|
296
|
+
|
|
297
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
298
|
+
private static extern bool SetInformationJobObject(
|
|
299
|
+
IntPtr job,
|
|
300
|
+
int informationClass,
|
|
301
|
+
IntPtr information,
|
|
302
|
+
uint informationLength);
|
|
303
|
+
|
|
304
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
305
|
+
public static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
306
|
+
|
|
307
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
308
|
+
public static extern bool TerminateJobObject(IntPtr job, uint exitCode);
|
|
309
|
+
|
|
310
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
311
|
+
private static extern bool QueryInformationJobObject(
|
|
312
|
+
IntPtr job,
|
|
313
|
+
int informationClass,
|
|
314
|
+
IntPtr information,
|
|
315
|
+
uint informationLength,
|
|
316
|
+
out uint returnLength);
|
|
317
|
+
|
|
318
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
319
|
+
public static extern bool CloseHandle(IntPtr handle);
|
|
320
|
+
|
|
321
|
+
public static IntPtr CreateKillOnCloseJob(string name)
|
|
322
|
+
{
|
|
323
|
+
IntPtr job = CreateJobObject(IntPtr.Zero, name);
|
|
324
|
+
if (job == IntPtr.Zero)
|
|
325
|
+
{
|
|
326
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "CreateJobObject failed.");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
var limits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
|
330
|
+
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
331
|
+
int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
332
|
+
IntPtr pointer = Marshal.AllocHGlobal(size);
|
|
333
|
+
try
|
|
334
|
+
{
|
|
335
|
+
Marshal.StructureToPtr(limits, pointer, false);
|
|
336
|
+
if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, pointer, (uint)size))
|
|
337
|
+
{
|
|
338
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "SetInformationJobObject failed.");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch
|
|
342
|
+
{
|
|
343
|
+
CloseHandle(job);
|
|
344
|
+
throw;
|
|
345
|
+
}
|
|
346
|
+
finally
|
|
347
|
+
{
|
|
348
|
+
Marshal.FreeHGlobal(pointer);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return job;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public static ulong PeakJobMemoryUsed(IntPtr job)
|
|
355
|
+
{
|
|
356
|
+
int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
357
|
+
IntPtr pointer = Marshal.AllocHGlobal(size);
|
|
358
|
+
try
|
|
359
|
+
{
|
|
360
|
+
uint returned;
|
|
361
|
+
if (!QueryInformationJobObject(job, JobObjectExtendedLimitInformation, pointer, (uint)size, out returned))
|
|
362
|
+
{
|
|
363
|
+
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "QueryInformationJobObject failed.");
|
|
364
|
+
}
|
|
365
|
+
var information = (JOBOBJECT_EXTENDED_LIMIT_INFORMATION)Marshal.PtrToStructure(
|
|
366
|
+
pointer,
|
|
367
|
+
typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
368
|
+
return information.PeakJobMemoryUsed.ToUInt64();
|
|
369
|
+
}
|
|
370
|
+
finally
|
|
371
|
+
{
|
|
372
|
+
Marshal.FreeHGlobal(pointer);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
public static string BuildArguments(object[] arguments)
|
|
377
|
+
{
|
|
378
|
+
var result = new StringBuilder();
|
|
379
|
+
for (int index = 0; index < arguments.Length; index++)
|
|
380
|
+
{
|
|
381
|
+
if (index > 0) result.Append(' ');
|
|
382
|
+
result.Append(QuoteArgument(Convert.ToString(arguments[index]) ?? String.Empty));
|
|
383
|
+
}
|
|
384
|
+
return result.ToString();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private static string QuoteArgument(string argument)
|
|
388
|
+
{
|
|
389
|
+
if (argument.Length > 0 && argument.IndexOfAny(new[] { ' ', '\t', '\n', '\v', '"' }) < 0)
|
|
390
|
+
{
|
|
391
|
+
return argument;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
var result = new StringBuilder();
|
|
395
|
+
result.Append('"');
|
|
396
|
+
int backslashes = 0;
|
|
397
|
+
foreach (char character in argument)
|
|
398
|
+
{
|
|
399
|
+
if (character == '\\')
|
|
400
|
+
{
|
|
401
|
+
backslashes++;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (character == '"')
|
|
406
|
+
{
|
|
407
|
+
result.Append('\\', backslashes * 2 + 1);
|
|
408
|
+
result.Append('"');
|
|
409
|
+
backslashes = 0;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
result.Append('\\', backslashes);
|
|
414
|
+
backslashes = 0;
|
|
415
|
+
result.Append(character);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
result.Append('\\', backslashes * 2);
|
|
419
|
+
result.Append('"');
|
|
420
|
+
return result.ToString();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
"@
|
|
424
|
+
|
|
425
|
+
function Read-ForgeSupervisorPayload {
|
|
426
|
+
if (-not [IO.Path]::IsPathRooted($PayloadPath)) {
|
|
427
|
+
throw "The supervisor payload path must be absolute."
|
|
428
|
+
}
|
|
429
|
+
$bytes = [IO.File]::ReadAllBytes($PayloadPath)
|
|
430
|
+
$hasher = [Security.Cryptography.SHA256]::Create()
|
|
431
|
+
try {
|
|
432
|
+
$actualSha256 = -join ($hasher.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") })
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
$hasher.Dispose()
|
|
436
|
+
}
|
|
437
|
+
if (-not [string]::Equals($actualSha256, $PayloadSha256, [StringComparison]::Ordinal)) {
|
|
438
|
+
throw "The supervisor payload does not match its expected SHA-256 identity."
|
|
439
|
+
}
|
|
440
|
+
$utf8 = New-Object Text.UTF8Encoding($false, $true)
|
|
441
|
+
$json = $utf8.GetString($bytes)
|
|
442
|
+
return $json | ConvertFrom-Json
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function New-ForgeChildProcess(
|
|
446
|
+
[string]$command,
|
|
447
|
+
[object[]]$arguments,
|
|
448
|
+
[string]$workingDirectory,
|
|
449
|
+
[bool]$redirectStreams
|
|
450
|
+
) {
|
|
451
|
+
$maximumAttempts = 3
|
|
452
|
+
for ($attempt = 1; $attempt -le $maximumAttempts; $attempt += 1) {
|
|
453
|
+
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
|
|
454
|
+
$startInfo.FileName = $command
|
|
455
|
+
$startInfo.Arguments = [ForgeWindowsJob]::BuildArguments($arguments)
|
|
456
|
+
$startInfo.WorkingDirectory = $workingDirectory
|
|
457
|
+
$startInfo.UseShellExecute = $false
|
|
458
|
+
$startInfo.CreateNoWindow = $true
|
|
459
|
+
$startInfo.RedirectStandardInput = $redirectStreams
|
|
460
|
+
$startInfo.RedirectStandardOutput = $redirectStreams
|
|
461
|
+
$startInfo.RedirectStandardError = $redirectStreams
|
|
462
|
+
|
|
463
|
+
$child = New-Object System.Diagnostics.Process
|
|
464
|
+
$child.StartInfo = $startInfo
|
|
465
|
+
try {
|
|
466
|
+
if (-not $child.Start()) {
|
|
467
|
+
throw "Failed to start supervised process '$command'."
|
|
468
|
+
}
|
|
469
|
+
$child | Add-Member -NotePropertyName ForgeStartAttempt -NotePropertyValue $attempt
|
|
470
|
+
return $child
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
$nativeError = $_.Exception
|
|
474
|
+
while ($nativeError.InnerException) {
|
|
475
|
+
$nativeError = $nativeError.InnerException
|
|
476
|
+
}
|
|
477
|
+
$retryable = $nativeError -is [ComponentModel.Win32Exception] -and $nativeError.NativeErrorCode -in @(5, 32, 33)
|
|
478
|
+
$child.Dispose()
|
|
479
|
+
if (-not $retryable -or $attempt -eq $maximumAttempts) {
|
|
480
|
+
throw
|
|
481
|
+
}
|
|
482
|
+
Start-Sleep -Milliseconds (100 * $attempt)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function New-ForgeSuspendedProvider([string]$command, [object[]]$arguments, [string]$workingDirectory) {
|
|
488
|
+
$maximumAttempts = 3
|
|
489
|
+
for ($attempt = 1; $attempt -le $maximumAttempts; $attempt += 1) {
|
|
490
|
+
try {
|
|
491
|
+
$provider = [ForgeSuspendedProcess]::Start(
|
|
492
|
+
$command,
|
|
493
|
+
[ForgeWindowsJob]::BuildArguments($arguments),
|
|
494
|
+
$workingDirectory)
|
|
495
|
+
$provider | Add-Member -NotePropertyName ForgeStartAttempt -NotePropertyValue $attempt
|
|
496
|
+
return $provider
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
$nativeError = $_.Exception
|
|
500
|
+
while ($nativeError.InnerException) {
|
|
501
|
+
$nativeError = $nativeError.InnerException
|
|
502
|
+
}
|
|
503
|
+
$retryable = $nativeError -is [ComponentModel.Win32Exception] -and $nativeError.NativeErrorCode -in @(5, 32, 33)
|
|
504
|
+
if (-not $retryable -or $attempt -eq $maximumAttempts) {
|
|
505
|
+
throw
|
|
506
|
+
}
|
|
507
|
+
Start-Sleep -Milliseconds (100 * $attempt)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function Write-ForgeProviderReady([object]$request, [object]$provider) {
|
|
513
|
+
$readyPath = [string]$request.readyPath
|
|
514
|
+
if ([string]::IsNullOrWhiteSpace($readyPath) -or -not [IO.Path]::IsPathRooted($readyPath)) {
|
|
515
|
+
throw "The provider-ready path must be absolute."
|
|
516
|
+
}
|
|
517
|
+
$payloadDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PayloadPath))
|
|
518
|
+
$readyDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($readyPath))
|
|
519
|
+
if (-not [string]::Equals($payloadDirectory, $readyDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
|
520
|
+
throw "The provider-ready path must be beside the supervisor payload."
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# The supervisor holds the provider's process handle, so this PID cannot be
|
|
524
|
+
# reused before its start time is read.
|
|
525
|
+
$providerStartTime = (Get-Process -Id $provider.Id -ErrorAction Stop).StartTime.ToUniversalTime()
|
|
526
|
+
$ready = @{
|
|
527
|
+
schemaVersion = 1
|
|
528
|
+
supervisorId = [string]$request.supervisorId
|
|
529
|
+
rootPid = [int]$env:FORGE_SUPERVISOR_ROOT_PID
|
|
530
|
+
rootIdentity = [string]$env:FORGE_SUPERVISOR_ROOT_IDENTITY
|
|
531
|
+
providerPid = $provider.Id
|
|
532
|
+
providerCreatedAt = $providerStartTime.ToString("O")
|
|
533
|
+
providerIdentity = "win32:$($providerStartTime.Ticks)"
|
|
534
|
+
startAttempt = [int]$provider.ForgeStartAttempt
|
|
535
|
+
} | ConvertTo-Json -Compress
|
|
536
|
+
$temporaryPath = "$readyPath.$([Guid]::NewGuid().ToString('N')).tmp"
|
|
537
|
+
[IO.File]::WriteAllText($temporaryPath, "$ready`n", (New-Object Text.UTF8Encoding($false)))
|
|
538
|
+
[IO.File]::Move($temporaryPath, $readyPath)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function Wait-ForgeProviderStartGrant([object]$request) {
|
|
542
|
+
$grantPath = [string]$request.startGrantPath
|
|
543
|
+
if ([string]::IsNullOrWhiteSpace($grantPath) -or -not [IO.Path]::IsPathRooted($grantPath)) {
|
|
544
|
+
throw "The provider start-grant path must be absolute."
|
|
545
|
+
}
|
|
546
|
+
$payloadDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PayloadPath))
|
|
547
|
+
$grantDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($grantPath))
|
|
548
|
+
if (-not [string]::Equals($payloadDirectory, $grantDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
|
549
|
+
throw "The provider start-grant path must be beside the supervisor payload."
|
|
550
|
+
}
|
|
551
|
+
for ($attempt = 0; $attempt -lt 1200; $attempt += 1) {
|
|
552
|
+
if ([IO.File]::Exists($grantPath)) {
|
|
553
|
+
return
|
|
554
|
+
}
|
|
555
|
+
Start-Sleep -Milliseconds 25
|
|
556
|
+
}
|
|
557
|
+
throw "Timed out waiting for the provider start grant."
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function Start-ForgeStreamCopies([IO.Stream]$output, [IO.Stream]$errorOutput) {
|
|
561
|
+
return @(
|
|
562
|
+
$output.CopyToAsync([Console]::OpenStandardOutput()),
|
|
563
|
+
$errorOutput.CopyToAsync([Console]::OpenStandardError())
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function Start-ForgeOutputCopies([Diagnostics.Process]$child) {
|
|
568
|
+
return Start-ForgeStreamCopies $child.StandardOutput.BaseStream $child.StandardError.BaseStream
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function Write-ForgeStandardInput([Diagnostics.Process]$child, [string]$inputText) {
|
|
572
|
+
if ($inputText.Length -gt 0) {
|
|
573
|
+
$child.StandardInput.Write($inputText)
|
|
574
|
+
}
|
|
575
|
+
$child.StandardInput.Close()
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function Write-ForgeMemoryTelemetry([object]$request, [IntPtr]$job) {
|
|
579
|
+
$telemetryPath = [string]$request.memoryTelemetryPath
|
|
580
|
+
if ([string]::IsNullOrWhiteSpace($telemetryPath) -or -not [IO.Path]::IsPathRooted($telemetryPath)) {
|
|
581
|
+
throw "The memory telemetry path must be absolute."
|
|
582
|
+
}
|
|
583
|
+
$payloadDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($PayloadPath))
|
|
584
|
+
$telemetryDirectory = [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($telemetryPath))
|
|
585
|
+
if (-not [string]::Equals($payloadDirectory, $telemetryDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
|
586
|
+
throw "The memory telemetry path must be beside the supervisor payload."
|
|
587
|
+
}
|
|
588
|
+
$telemetry = @{
|
|
589
|
+
schemaVersion = 1
|
|
590
|
+
peakMemoryBytes = [ForgeWindowsJob]::PeakJobMemoryUsed($job)
|
|
591
|
+
sampleCount = 1
|
|
592
|
+
source = "windows_job_object_peak_job_memory"
|
|
593
|
+
observedAt = [DateTimeOffset]::UtcNow.ToString("O")
|
|
594
|
+
} | ConvertTo-Json -Compress
|
|
595
|
+
$temporaryPath = "$telemetryPath.$([Guid]::NewGuid().ToString('N')).tmp"
|
|
596
|
+
try {
|
|
597
|
+
[IO.File]::WriteAllText($temporaryPath, "$telemetry`n", (New-Object Text.UTF8Encoding($false)))
|
|
598
|
+
[IO.File]::Move($temporaryPath, $telemetryPath)
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
if ([IO.File]::Exists($temporaryPath)) {
|
|
602
|
+
try { [IO.File]::Delete($temporaryPath) } catch { }
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function Write-ForgeMemoryTelemetryBestEffort([object]$request, [IntPtr]$job) {
|
|
608
|
+
try {
|
|
609
|
+
Write-ForgeMemoryTelemetry $request $job
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
# Telemetry is optional. Provider output, descendant cleanup, and the real
|
|
613
|
+
# provider exit code remain authoritative if querying or persisting it fails.
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if ($Mode -eq "Inner") {
|
|
618
|
+
if ([string]::IsNullOrWhiteSpace($GateName)) {
|
|
619
|
+
throw "The inner supervisor requires a gate name."
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
$inputText = [Console]::In.ReadToEnd()
|
|
623
|
+
$request = Read-ForgeSupervisorPayload
|
|
624
|
+
$gate = [Threading.EventWaitHandle]::OpenExisting($GateName)
|
|
625
|
+
try {
|
|
626
|
+
if (-not $gate.WaitOne(30000)) {
|
|
627
|
+
throw "Timed out waiting for Windows Job Object assignment."
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
finally {
|
|
631
|
+
$gate.Dispose()
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
# The provider exists for the ready record but runs nothing until the start
|
|
635
|
+
# grant. Its input is written only after Resume: a prompt larger than the pipe
|
|
636
|
+
# buffer would otherwise block against a reader that cannot run.
|
|
637
|
+
$provider = New-ForgeSuspendedProvider ([string]$request.command) @($request.args) ([string]$request.cwd)
|
|
638
|
+
try {
|
|
639
|
+
Write-ForgeProviderReady $request $provider
|
|
640
|
+
$copies = Start-ForgeStreamCopies $provider.StandardOutput $provider.StandardError
|
|
641
|
+
Wait-ForgeProviderStartGrant $request
|
|
642
|
+
$provider.Resume()
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
$provider.Kill()
|
|
646
|
+
$provider.Dispose()
|
|
647
|
+
throw
|
|
648
|
+
}
|
|
649
|
+
# Same encoding System.Diagnostics.Process uses for redirected input.
|
|
650
|
+
$inputWriter = New-Object IO.StreamWriter($provider.StandardInput, [Console]::InputEncoding, 4096)
|
|
651
|
+
if ($inputText.Length -gt 0) {
|
|
652
|
+
$inputWriter.Write($inputText)
|
|
653
|
+
}
|
|
654
|
+
$inputWriter.Close()
|
|
655
|
+
$exitCode = $provider.WaitForExit()
|
|
656
|
+
if (-not [Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]$copies, 1000)) {
|
|
657
|
+
$provider.StandardOutput.Dispose()
|
|
658
|
+
$provider.StandardError.Dispose()
|
|
659
|
+
}
|
|
660
|
+
exit $exitCode
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
$inputText = [Console]::In.ReadToEnd()
|
|
664
|
+
$request = Read-ForgeSupervisorPayload
|
|
665
|
+
$env:FORGE_SUPERVISOR_ROOT_PID = [string]$PID
|
|
666
|
+
$env:FORGE_SUPERVISOR_ROOT_IDENTITY = "win32:$([Diagnostics.Process]::GetCurrentProcess().StartTime.ToUniversalTime().Ticks)"
|
|
667
|
+
$parent = [Diagnostics.Process]::GetProcessById([int]$request.parentPid)
|
|
668
|
+
$jobName = "Local\Forge-$([string]$request.supervisorId)"
|
|
669
|
+
$job = [ForgeWindowsJob]::CreateKillOnCloseJob($jobName)
|
|
670
|
+
$gateNameValue = "Local\ForgeGate-$([Guid]::NewGuid().ToString('N'))"
|
|
671
|
+
$createdNew = $false
|
|
672
|
+
$gate = New-Object Threading.EventWaitHandle($false, [Threading.EventResetMode]::ManualReset, $gateNameValue, [ref]$createdNew)
|
|
673
|
+
$inner = $null
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
$powerShell = Join-Path $PSHOME "powershell.exe"
|
|
677
|
+
$innerArguments = @(
|
|
678
|
+
"-NoLogo",
|
|
679
|
+
"-NoProfile",
|
|
680
|
+
"-NonInteractive",
|
|
681
|
+
"-ExecutionPolicy",
|
|
682
|
+
"Bypass",
|
|
683
|
+
"-File",
|
|
684
|
+
$PSCommandPath,
|
|
685
|
+
"-Mode",
|
|
686
|
+
"Inner",
|
|
687
|
+
"-PayloadPath",
|
|
688
|
+
$PayloadPath,
|
|
689
|
+
"-PayloadSha256",
|
|
690
|
+
$PayloadSha256,
|
|
691
|
+
"-GateName",
|
|
692
|
+
$gateNameValue
|
|
693
|
+
)
|
|
694
|
+
$inner = New-ForgeChildProcess $powerShell $innerArguments ([string]$request.cwd) $true
|
|
695
|
+
if (-not [ForgeWindowsJob]::AssignProcessToJobObject($job, $inner.Handle)) {
|
|
696
|
+
throw (New-Object System.ComponentModel.Win32Exception([Runtime.InteropServices.Marshal]::GetLastWin32Error(), "AssignProcessToJobObject failed."))
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
$copies = Start-ForgeOutputCopies $inner
|
|
700
|
+
$gate.Set() | Out-Null
|
|
701
|
+
Write-ForgeStandardInput $inner $inputText
|
|
702
|
+
while (-not $inner.WaitForExit(250)) {
|
|
703
|
+
$parent.Refresh()
|
|
704
|
+
if ($parent.HasExited) {
|
|
705
|
+
[ForgeWindowsJob]::TerminateJobObject($job, 125) | Out-Null
|
|
706
|
+
exit 125
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
$exitCode = $inner.ExitCode
|
|
711
|
+
Write-ForgeMemoryTelemetryBestEffort $request $job
|
|
712
|
+
[ForgeWindowsJob]::TerminateJobObject($job, 1) | Out-Null
|
|
713
|
+
[Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]$copies, 2000) | Out-Null
|
|
714
|
+
exit $exitCode
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
if ($inner -and -not $inner.HasExited) {
|
|
718
|
+
[ForgeWindowsJob]::TerminateJobObject($job, 1) | Out-Null
|
|
719
|
+
}
|
|
720
|
+
$gate.Dispose()
|
|
721
|
+
[ForgeWindowsJob]::CloseHandle($job) | Out-Null
|
|
722
|
+
}
|