@loucompanion/forge-bridge 0.1.3-dev.b3bce6ba65a6 → 0.1.3-dev.ba293ccf876b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/dist/bridge/bridge.base.d.ts +8 -1
- package/dist/bridge/bridge.service.d.ts +98 -3
- package/dist/bridge/bridge.service.js +1425 -45
- package/dist/bridge/bridge.types.d.ts +205 -1
- 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 +100 -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.js +47 -1
- package/dist/cli/cli.service.d.ts +45 -2
- package/dist/cli/cli.service.js +377 -27
- package/dist/cli/local-config.d.ts +5 -0
- package/dist/cli/local-config.js +45 -1
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +105 -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 +386 -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 +83 -0
- package/dist/runtime/docker-engine-probe.js +300 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +103 -0
- package/dist/runtime/host-diagnostics.d.ts +4 -0
- package/dist/runtime/host-diagnostics.js +95 -0
- package/dist/runtime/host-listener-inspector.d.ts +46 -0
- package/dist/runtime/host-listener-inspector.js +349 -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 +56 -0
- package/dist/runtime/host-runtime-contract.js +709 -0
- package/dist/runtime/host-runtime-lease.d.ts +201 -0
- package/dist/runtime/host-runtime-lease.js +2314 -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 +68 -0
- package/dist/runtime/memory-capacity.js +121 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +284 -0
- package/dist/runtime/pinned-directory-process.d.ts +4 -0
- package/dist/runtime/pinned-directory-process.js +20 -0
- package/dist/runtime/pinned-source-directory.d.ts +9 -0
- package/dist/runtime/pinned-source-directory.js +56 -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 +3 -0
- package/dist/runtime/prepared-runtime-identity.js +191 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +22 -0
- package/dist/runtime/prepared-runtime-readiness.js +191 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +158 -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 +5 -0
- package/dist/sandbox/repository-lock.js +121 -0
- package/dist/sandbox/runtime.d.ts +297 -0
- package/dist/sandbox/runtime.js +2338 -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 +72 -0
- package/dist/session/managed-attempt-tools.d.ts +21 -0
- package/dist/session/managed-attempt-tools.js +912 -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 +335 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +256 -26
- package/dist/session/phase-completion.d.ts +12 -2
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +51 -10
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.d.ts +3 -3
- package/dist/session/phase-completion.prompts.js +24 -8
- package/dist/session/phase-completion.runtime.d.ts +13 -1
- package/dist/session/phase-completion.runtime.js +189 -8
- package/dist/session/phase-completion.schemas.js +117 -4
- package/dist/session/phase-completion.validation.d.ts +4 -1
- package/dist/session/phase-completion.validation.js +153 -22
- package/dist/session/pre-publication-validation.d.ts +47 -0
- package/dist/session/pre-publication-validation.js +249 -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 +3 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +68 -9
- package/dist/session/provider-cli/provider-cli.process.d.ts +26 -0
- package/dist/session/provider-cli/provider-cli.process.js +474 -23
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +14 -2
- package/dist/session/provider-cli/provider-cli.prompts.js +147 -25
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -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 +1 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +25 -2
- package/dist/session/provider-cli/provider-cli.service.js +636 -84
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.js +91 -16
- 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 +38 -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 +531 -42
- package/dist/session/session.types.d.ts +115 -1
- package/dist/session/session.types.js +194 -3
- 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 +14 -0
- package/dist/shared/process/dotnet-toolchain.js +179 -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 +2 -0
- package/dist/shared/process/process.js +11 -6
- package/dist/shared/provider-capabilities.d.ts +1 -0
- package/dist/shared/provider-capabilities.js +3 -0
- package/dist/shared/redaction/redaction.js +12 -2
- 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 +43 -4
- package/dist/worktree/dependency-warmer.d.ts +28 -1
- package/dist/worktree/dependency-warmer.js +308 -9
- 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/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 +14 -2
- package/dist/worktree/worktree.service.js +296 -47
- package/dist/worktree/worktree.types.d.ts +5 -1
- package/package.json +8 -4
- package/runtime/windows-job-supervisor.ps1 +444 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DispatchQaReworkContext, DispatchRepo, DispatchWorkItem, DispatchResolvedArtifact } from "../session/session.types.js";
|
|
1
|
+
import type { DispatchQaReworkContext, DispatchPriorPhaseArtifact, DispatchRepo, DispatchWorkItem, DispatchResolvedArtifact } from "../session/session.types.js";
|
|
2
2
|
import type { WarmDependenciesResult } from "./dependency-warmer.js";
|
|
3
3
|
export interface WorktreePrepareRequest {
|
|
4
4
|
readonly repo: DispatchRepo;
|
|
@@ -12,8 +12,11 @@ export interface WorktreePrepareRequest {
|
|
|
12
12
|
readonly allowedRepoRoot: string;
|
|
13
13
|
readonly cleanupWorktrees: boolean;
|
|
14
14
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
15
|
+
readonly priorPhaseArtifacts?: readonly DispatchPriorPhaseArtifact[];
|
|
15
16
|
readonly qaRework?: DispatchQaReworkContext | null;
|
|
16
17
|
readonly warmPathOverrides?: readonly string[];
|
|
18
|
+
readonly applicationRuntimeRequired?: boolean;
|
|
19
|
+
readonly preserveExistingState?: boolean;
|
|
17
20
|
}
|
|
18
21
|
export interface WorktreeHandle {
|
|
19
22
|
readonly repoPath: string;
|
|
@@ -23,4 +26,5 @@ export interface WorktreeHandle {
|
|
|
23
26
|
readonly cleanupWorktrees: boolean;
|
|
24
27
|
readonly dependencyWarm?: WarmDependenciesResult;
|
|
25
28
|
readonly qaReworkPacketPath?: string;
|
|
29
|
+
readonly hydratedPriorPhaseArtifactCount?: number;
|
|
26
30
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.3-dev.
|
|
3
|
+
"version": "0.1.3-dev.ba293ccf876b",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
+
"forge": "dist/cli/bin.js",
|
|
8
9
|
"forge-bridge": "dist/cli/bin.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "./dist/index.js",
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist",
|
|
22
|
+
"runtime",
|
|
21
23
|
"README.md"
|
|
22
24
|
],
|
|
23
25
|
"repository": {
|
|
@@ -37,13 +39,15 @@
|
|
|
37
39
|
},
|
|
38
40
|
"scripts": {
|
|
39
41
|
"build": "tsc -p tsconfig.json",
|
|
40
|
-
"test": "
|
|
42
|
+
"test": "node scripts/run-owned-tests.mjs",
|
|
43
|
+
"test:residue": "node scripts/run-owned-residue-gate.mjs",
|
|
41
44
|
"test:watch": "vitest",
|
|
42
45
|
"smoke:package": "npm run build && node scripts/smoke-package.mjs",
|
|
43
|
-
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\""
|
|
46
|
+
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\" \"vitest.setup.ts\""
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|
|
46
|
-
"@microsoft/signalr": "^8.0.17"
|
|
49
|
+
"@microsoft/signalr": "^8.0.17",
|
|
50
|
+
"yaml": "^2.9.0"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@eslint/js": "^10.0.1",
|
|
@@ -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)
|