@loucompanion/forge-bridge 0.1.3-dev.9d8f2e7e5241 → 0.1.3-dev.a58d7cd1092f
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 +83 -0
- package/dist/bridge/bridge.base.d.ts +4 -1
- package/dist/bridge/bridge.service.d.ts +55 -3
- package/dist/bridge/bridge.service.js +834 -87
- package/dist/bridge/bridge.types.d.ts +89 -1
- 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 +37 -3
- package/dist/cli/cli.service.d.ts +33 -2
- package/dist/cli/cli.service.js +209 -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-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/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-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 +47 -0
- package/dist/runtime/host-runtime-contract.js +700 -0
- package/dist/runtime/host-runtime-lease.d.ts +197 -0
- package/dist/runtime/host-runtime-lease.js +2203 -0
- package/dist/runtime/host-runtime-promotion.d.ts +23 -0
- package/dist/runtime/host-runtime-promotion.js +159 -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/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +231 -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/preflight.d.ts +45 -1
- package/dist/sandbox/preflight.js +299 -34
- package/dist/sandbox/process.js +21 -2
- package/dist/sandbox/repository-lock.js +41 -3
- package/dist/sandbox/runtime.d.ts +66 -3
- package/dist/sandbox/runtime.js +670 -105
- package/dist/sandbox/sandbox.types.d.ts +10 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.js +1 -1
- package/dist/sandbox/worker.js +2 -5
- package/dist/sandbox/workspace.service.d.ts +1 -0
- package/dist/sandbox/workspace.service.js +28 -5
- 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 +233 -24
- 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 +46 -0
- package/dist/session/pre-publication-validation.js +208 -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.deterministic.js +47 -1
- package/dist/session/provider-cli/provider-cli.process.d.ts +25 -0
- package/dist/session/provider-cli/provider-cli.process.js +463 -34
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
- package/dist/session/provider-cli/provider-cli.prompts.js +108 -25
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -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 +22 -2
- package/dist/session/provider-cli/provider-cli.service.js +529 -100
- 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 +522 -41
- package/dist/session/session.types.d.ts +72 -0
- package/dist/session/session.types.js +74 -3
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- 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 +1 -0
- package/dist/shared/process/process.js +1 -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 +13 -2
- package/dist/worktree/worktree.service.js +238 -47
- package/dist/worktree/worktree.types.d.ts +5 -1
- package/package.json +5 -3
- package/runtime/windows-job-supervisor.ps1 +444 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -7,12 +7,14 @@ export interface RunOnceDispatchPacket {
|
|
|
7
7
|
readonly session: DispatchSession;
|
|
8
8
|
readonly lease: DispatchLease;
|
|
9
9
|
readonly execution: DispatchExecution;
|
|
10
|
+
readonly runtimeIntent: DispatchRuntimeIntent;
|
|
10
11
|
readonly serverLocal: DispatchServerLocalConfig;
|
|
11
12
|
readonly sandbox?: DispatchSandboxContext | null;
|
|
12
13
|
readonly pullRequest?: DispatchPullRequestContext | null;
|
|
13
14
|
readonly apiKeySecretGrant?: ApiKeySecretGrant | null;
|
|
14
15
|
readonly apiKeySecretGrantHandle?: ApiKeySecretGrantHandle | null;
|
|
15
16
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
17
|
+
readonly priorPhaseArtifacts?: readonly DispatchPriorPhaseArtifact[];
|
|
16
18
|
readonly warmPathOverrides?: readonly string[];
|
|
17
19
|
readonly qaBrowser?: DispatchQaBrowserContext | null;
|
|
18
20
|
readonly qaScope?: DispatchQaScope | null;
|
|
@@ -24,6 +26,10 @@ export interface RunOnceDispatchPacket {
|
|
|
24
26
|
readonly designerRefs?: DispatchDesignerRefHandoffContext | null;
|
|
25
27
|
readonly draft?: DispatchDraftPrompt | null;
|
|
26
28
|
}
|
|
29
|
+
export interface DispatchRuntimeIntent {
|
|
30
|
+
readonly executionClass: "analysis_only" | "managed_runtime";
|
|
31
|
+
readonly applicationRuntimeRequired: boolean;
|
|
32
|
+
}
|
|
27
33
|
export interface DispatchSandboxContext {
|
|
28
34
|
readonly dispatchAttemptId: string;
|
|
29
35
|
readonly executionLeaseId: string;
|
|
@@ -48,6 +54,9 @@ export interface DispatchPublishGrantHandle {
|
|
|
48
54
|
export interface DispatchSandboxResourceRequest {
|
|
49
55
|
readonly sandboxSlots: number;
|
|
50
56
|
readonly cpuMillis: number;
|
|
57
|
+
readonly workerMemoryLimitBytes: number;
|
|
58
|
+
readonly serviceMemoryLimitBytes: number;
|
|
59
|
+
readonly runtimeOverheadBytes: number;
|
|
51
60
|
readonly memoryBytes: number;
|
|
52
61
|
readonly workspaceBytes: number;
|
|
53
62
|
readonly pids: number;
|
|
@@ -85,6 +94,7 @@ export interface DispatchQaScope {
|
|
|
85
94
|
readonly schema_version: number;
|
|
86
95
|
readonly surface: readonly string[];
|
|
87
96
|
readonly required_evidence: readonly string[];
|
|
97
|
+
readonly required_capabilities?: readonly string[];
|
|
88
98
|
readonly not_applicable: readonly string[];
|
|
89
99
|
readonly browser_required: boolean;
|
|
90
100
|
readonly api_required: boolean;
|
|
@@ -101,6 +111,7 @@ export interface DispatchQaAppLaunchTarget {
|
|
|
101
111
|
readonly command?: string | null;
|
|
102
112
|
readonly working_directory?: string | null;
|
|
103
113
|
readonly ready_url?: string | null;
|
|
114
|
+
readonly managed_runtime_port?: string | null;
|
|
104
115
|
readonly routes: readonly string[];
|
|
105
116
|
readonly notes?: string | null;
|
|
106
117
|
}
|
|
@@ -189,6 +200,19 @@ export interface DispatchStoryQaDependencyContext {
|
|
|
189
200
|
readonly fromWorkItemId: string;
|
|
190
201
|
readonly toWorkItemId: string;
|
|
191
202
|
}
|
|
203
|
+
export interface DispatchPriorPhaseArtifact {
|
|
204
|
+
readonly workItemId: string;
|
|
205
|
+
readonly artifactId: string;
|
|
206
|
+
readonly cycleRunId: string;
|
|
207
|
+
readonly phase: "implementation" | "self_review" | "validation";
|
|
208
|
+
readonly status: string;
|
|
209
|
+
readonly producedAt: string;
|
|
210
|
+
readonly sourcePath: string;
|
|
211
|
+
readonly verdictJson: string;
|
|
212
|
+
readonly report: string;
|
|
213
|
+
readonly verdictContentHash: string;
|
|
214
|
+
readonly reportContentHash: string;
|
|
215
|
+
}
|
|
192
216
|
export interface DispatchDesignerRefHandoffContext {
|
|
193
217
|
readonly schemaVersion: number;
|
|
194
218
|
readonly kind: string;
|
|
@@ -278,6 +302,7 @@ export interface DispatchCycleRun {
|
|
|
278
302
|
readonly requestedBase: string;
|
|
279
303
|
readonly baseBranch: string;
|
|
280
304
|
readonly expectedCurrentBranch?: string | null;
|
|
305
|
+
readonly repairHeadSha?: string | null;
|
|
281
306
|
readonly attempt: number;
|
|
282
307
|
readonly model: string;
|
|
283
308
|
}
|
|
@@ -285,6 +310,8 @@ export interface DispatchSession {
|
|
|
285
310
|
readonly id: string;
|
|
286
311
|
readonly model: string;
|
|
287
312
|
readonly lane: string;
|
|
313
|
+
readonly resumedFromSessionId?: string | null;
|
|
314
|
+
readonly resumeProviderThreadId?: string | null;
|
|
288
315
|
}
|
|
289
316
|
export interface DispatchLease {
|
|
290
317
|
readonly id: string;
|
|
@@ -353,6 +380,19 @@ export interface RunnerDispatchArtifact {
|
|
|
353
380
|
readonly producedBy: string;
|
|
354
381
|
readonly status: string;
|
|
355
382
|
readonly phase?: string | null;
|
|
383
|
+
/**
|
|
384
|
+
* QA sidecars are transported with the terminal result because managed
|
|
385
|
+
* worktrees may be released before the API mirrors the result. The API
|
|
386
|
+
* validates these bytes against the canonical evidence metadata and stores
|
|
387
|
+
* only that metadata.
|
|
388
|
+
*/
|
|
389
|
+
readonly sidecars?: readonly RunnerDispatchSidecar[] | null;
|
|
390
|
+
}
|
|
391
|
+
export interface RunnerDispatchSidecar {
|
|
392
|
+
readonly path: string;
|
|
393
|
+
readonly contentBase64: string;
|
|
394
|
+
readonly contentHash: string;
|
|
395
|
+
readonly bytes: number;
|
|
356
396
|
}
|
|
357
397
|
export interface RunOnceResultPacket {
|
|
358
398
|
readonly schemaVersion: 1;
|
|
@@ -368,4 +408,36 @@ export interface RunnerEventLine {
|
|
|
368
408
|
readonly event: RunnerDispatchEvent;
|
|
369
409
|
}
|
|
370
410
|
export declare function parseRunOnceDispatchPacket(value: unknown): RunOnceDispatchPacket;
|
|
411
|
+
export interface HostRuntimeReadinessProbeRequest {
|
|
412
|
+
readonly requestId: string;
|
|
413
|
+
readonly fencingToken: string;
|
|
414
|
+
readonly userId: string;
|
|
415
|
+
readonly projectId: string;
|
|
416
|
+
readonly repoId: string;
|
|
417
|
+
readonly runnerId: string;
|
|
418
|
+
readonly runnerInstanceId: string;
|
|
419
|
+
readonly attempt: number;
|
|
420
|
+
readonly repoName: string;
|
|
421
|
+
readonly remoteUrl: string;
|
|
422
|
+
readonly defaultBranch: string;
|
|
423
|
+
readonly cloneOnDemand: boolean;
|
|
424
|
+
readonly runnerRepoLocalPath: string;
|
|
425
|
+
readonly revision: string;
|
|
426
|
+
readonly expiresAt: string;
|
|
427
|
+
readonly repoRoot: string;
|
|
428
|
+
readonly cleanupWorktrees: boolean;
|
|
429
|
+
}
|
|
430
|
+
export interface HostRuntimeReadinessProbeResult {
|
|
431
|
+
readonly status: "parallel_ready" | "serialized" | "failed";
|
|
432
|
+
readonly runtimeContractFingerprint?: string | null;
|
|
433
|
+
readonly runtimeContractSource?: string | null;
|
|
434
|
+
readonly reasonCode?: string | null;
|
|
435
|
+
readonly reasonMessage?: string | null;
|
|
436
|
+
readonly qaAppLaunchTarget?: HostRuntimeReadinessQaLaunchTarget | null;
|
|
437
|
+
readonly checkedAt: string;
|
|
438
|
+
}
|
|
439
|
+
export interface HostRuntimeReadinessQaLaunchTarget {
|
|
440
|
+
readonly managedRuntimePort: string;
|
|
441
|
+
readonly routes: readonly string[];
|
|
442
|
+
}
|
|
371
443
|
export declare function failureResult(summary: string, events?: readonly RunnerDispatchEvent[]): RunOnceResultPacket;
|
|
@@ -14,12 +14,14 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
14
14
|
session: parseSession(packet.session),
|
|
15
15
|
lease: parseLease(packet.lease),
|
|
16
16
|
execution: parseExecution(packet.execution),
|
|
17
|
+
runtimeIntent: parseRuntimeIntent(packet.runtimeIntent),
|
|
17
18
|
serverLocal: parseServerLocal(packet.serverLocal),
|
|
18
19
|
sandbox: parseSandbox(packet.sandbox, schemaVersion),
|
|
19
20
|
pullRequest: parsePullRequest(packet.pullRequest),
|
|
20
21
|
apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
|
|
21
22
|
apiKeySecretGrantHandle: parseApiKeySecretGrantHandle(packet.apiKeySecretGrantHandle),
|
|
22
23
|
artifacts: parseArtifacts(packet.artifacts),
|
|
24
|
+
priorPhaseArtifacts: parsePriorPhaseArtifacts(packet.priorPhaseArtifacts),
|
|
23
25
|
warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
|
|
24
26
|
qaBrowser: parseQaBrowser(packet.qaBrowser),
|
|
25
27
|
qaScope: parseQaScope(packet.qaScope ?? packet.qa_scope),
|
|
@@ -32,6 +34,21 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
32
34
|
draft: parseDraftPrompt(packet.draft)
|
|
33
35
|
};
|
|
34
36
|
}
|
|
37
|
+
function parseRuntimeIntent(value) {
|
|
38
|
+
if (value === undefined || value === null) {
|
|
39
|
+
return { executionClass: "managed_runtime", applicationRuntimeRequired: true };
|
|
40
|
+
}
|
|
41
|
+
const intent = asObject(value, "runtimeIntent");
|
|
42
|
+
const executionClass = requiredString(intent, "executionClass").trim().toLowerCase();
|
|
43
|
+
if (executionClass !== "analysis_only" && executionClass !== "managed_runtime") {
|
|
44
|
+
throw new Error("runtimeIntent.executionClass must be analysis_only or managed_runtime.");
|
|
45
|
+
}
|
|
46
|
+
const applicationRuntimeRequired = requiredBoolean(intent, "applicationRuntimeRequired");
|
|
47
|
+
if ((executionClass === "analysis_only") === applicationRuntimeRequired) {
|
|
48
|
+
throw new Error("runtimeIntent execution class and application runtime requirement are inconsistent.");
|
|
49
|
+
}
|
|
50
|
+
return { executionClass, applicationRuntimeRequired };
|
|
51
|
+
}
|
|
35
52
|
function parseSandbox(value, schemaVersion) {
|
|
36
53
|
if (value === undefined || value === null) {
|
|
37
54
|
if (schemaVersion === 2) {
|
|
@@ -93,17 +110,38 @@ function parseSandboxResourceRequest(value) {
|
|
|
93
110
|
return {
|
|
94
111
|
sandboxSlots: 1,
|
|
95
112
|
cpuMillis: 2000,
|
|
96
|
-
|
|
113
|
+
workerMemoryLimitBytes: 2 * 1024 ** 3,
|
|
114
|
+
serviceMemoryLimitBytes: 0,
|
|
115
|
+
runtimeOverheadBytes: 128 * 1024 ** 2,
|
|
116
|
+
memoryBytes: 2 * 1024 ** 3 + 128 * 1024 ** 2,
|
|
97
117
|
workspaceBytes: 10 * 1024 ** 3,
|
|
98
118
|
pids: 512,
|
|
99
119
|
buildSlots: 1
|
|
100
120
|
};
|
|
101
121
|
}
|
|
102
122
|
const resources = asObject(value, "sandbox.requestedResources");
|
|
123
|
+
const memoryBytes = boundedInteger(resources, "memoryBytes", 128 * 1024 ** 2, 512 * 1024 ** 3);
|
|
124
|
+
const hasMemoryBreakdown = ["workerMemoryLimitBytes", "serviceMemoryLimitBytes", "runtimeOverheadBytes"]
|
|
125
|
+
.some(key => resources[key] !== undefined && resources[key] !== null);
|
|
126
|
+
const workerMemoryLimitBytes = hasMemoryBreakdown
|
|
127
|
+
? boundedInteger(resources, "workerMemoryLimitBytes", 128 * 1024 ** 2, 512 * 1024 ** 3)
|
|
128
|
+
: memoryBytes;
|
|
129
|
+
const serviceMemoryLimitBytes = hasMemoryBreakdown
|
|
130
|
+
? boundedInteger(resources, "serviceMemoryLimitBytes", 0, 512 * 1024 ** 3)
|
|
131
|
+
: 0;
|
|
132
|
+
const runtimeOverheadBytes = hasMemoryBreakdown
|
|
133
|
+
? boundedInteger(resources, "runtimeOverheadBytes", 0, 64 * 1024 ** 3)
|
|
134
|
+
: 0;
|
|
135
|
+
if (workerMemoryLimitBytes + serviceMemoryLimitBytes + runtimeOverheadBytes !== memoryBytes) {
|
|
136
|
+
throw new Error("sandbox.requestedResources memory breakdown must equal memoryBytes.");
|
|
137
|
+
}
|
|
103
138
|
return {
|
|
104
139
|
sandboxSlots: boundedInteger(resources, "sandboxSlots", 1, 4),
|
|
105
140
|
cpuMillis: boundedInteger(resources, "cpuMillis", 100, 64_000),
|
|
106
|
-
|
|
141
|
+
workerMemoryLimitBytes,
|
|
142
|
+
serviceMemoryLimitBytes,
|
|
143
|
+
runtimeOverheadBytes,
|
|
144
|
+
memoryBytes,
|
|
107
145
|
workspaceBytes: boundedInteger(resources, "workspaceBytes", 1024 ** 3, 10 * 1024 ** 4),
|
|
108
146
|
pids: boundedInteger(resources, "pids", 32, 65_536),
|
|
109
147
|
buildSlots: boundedInteger(resources, "buildSlots", 0, 4)
|
|
@@ -176,6 +214,7 @@ function parseCycleRun(value) {
|
|
|
176
214
|
requestedBase: optionalString(cycle, "requestedBase") ?? "develop",
|
|
177
215
|
baseBranch: optionalString(cycle, "baseBranch") ?? "develop",
|
|
178
216
|
expectedCurrentBranch: optionalString(cycle, "expectedCurrentBranch"),
|
|
217
|
+
repairHeadSha: optionalString(cycle, "repairHeadSha"),
|
|
179
218
|
attempt: requiredNumber(cycle, "attempt"),
|
|
180
219
|
model: requiredString(cycle, "model")
|
|
181
220
|
};
|
|
@@ -185,7 +224,9 @@ function parseSession(value) {
|
|
|
185
224
|
return {
|
|
186
225
|
id: requiredString(session, "id"),
|
|
187
226
|
model: requiredString(session, "model"),
|
|
188
|
-
lane: requiredString(session, "lane")
|
|
227
|
+
lane: requiredString(session, "lane"),
|
|
228
|
+
resumedFromSessionId: optionalString(session, "resumedFromSessionId"),
|
|
229
|
+
resumeProviderThreadId: optionalString(session, "resumeProviderThreadId")
|
|
189
230
|
};
|
|
190
231
|
}
|
|
191
232
|
function parseLease(value) {
|
|
@@ -358,6 +399,7 @@ function parseQaScope(value) {
|
|
|
358
399
|
schema_version: requiredNumber(qaScope, "schema_version"),
|
|
359
400
|
surface: parseOptionalStringArray(qaScope.surface, "qaScope.surface"),
|
|
360
401
|
required_evidence: parseOptionalStringArray(qaScope.required_evidence, "qaScope.required_evidence"),
|
|
402
|
+
required_capabilities: parseOptionalStringArray(qaScope.required_capabilities ?? qaScope.requiredCapabilities, "qaScope.required_capabilities"),
|
|
361
403
|
not_applicable: parseOptionalStringArray(qaScope.not_applicable, "qaScope.not_applicable"),
|
|
362
404
|
browser_required: optionalBoolean(qaScope, "browser_required") ?? optionalBoolean(qaScope, "browserRequired") ?? false,
|
|
363
405
|
api_required: optionalBoolean(qaScope, "api_required") ?? optionalBoolean(qaScope, "apiRequired") ?? false,
|
|
@@ -384,6 +426,7 @@ function parseQaAppLaunchTarget(value) {
|
|
|
384
426
|
command: optionalString(launchTarget, "command"),
|
|
385
427
|
working_directory: optionalString(launchTarget, "working_directory"),
|
|
386
428
|
ready_url: optionalString(launchTarget, "ready_url"),
|
|
429
|
+
managed_runtime_port: optionalString(launchTarget, "managed_runtime_port"),
|
|
387
430
|
routes: Array.isArray(routes)
|
|
388
431
|
? routes.map((entry, index) => {
|
|
389
432
|
if (typeof entry !== "string") {
|
|
@@ -662,6 +705,34 @@ function parseArtifacts(value) {
|
|
|
662
705
|
};
|
|
663
706
|
});
|
|
664
707
|
}
|
|
708
|
+
function parsePriorPhaseArtifacts(value) {
|
|
709
|
+
if (value === undefined || value === null) {
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
if (!Array.isArray(value)) {
|
|
713
|
+
throw new Error("priorPhaseArtifacts must be an array when provided.");
|
|
714
|
+
}
|
|
715
|
+
return value.map((entry, index) => {
|
|
716
|
+
const artifact = asObject(entry, `priorPhaseArtifacts[${index}]`);
|
|
717
|
+
const phase = requiredString(artifact, "phase").trim().toLowerCase().replace(/-/g, "_");
|
|
718
|
+
if (phase !== "implementation" && phase !== "self_review" && phase !== "validation") {
|
|
719
|
+
throw new Error(`priorPhaseArtifacts[${index}].phase must be implementation, self_review, or validation.`);
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
workItemId: requiredString(artifact, "workItemId"),
|
|
723
|
+
artifactId: requiredString(artifact, "artifactId"),
|
|
724
|
+
cycleRunId: requiredString(artifact, "cycleRunId"),
|
|
725
|
+
phase,
|
|
726
|
+
status: requiredString(artifact, "status"),
|
|
727
|
+
producedAt: requiredString(artifact, "producedAt"),
|
|
728
|
+
sourcePath: requiredString(artifact, "sourcePath"),
|
|
729
|
+
verdictJson: requiredString(artifact, "verdictJson"),
|
|
730
|
+
report: optionalString(artifact, "report") ?? "",
|
|
731
|
+
verdictContentHash: requiredString(artifact, "verdictContentHash").toLowerCase(),
|
|
732
|
+
reportContentHash: requiredString(artifact, "reportContentHash").toLowerCase()
|
|
733
|
+
};
|
|
734
|
+
});
|
|
735
|
+
}
|
|
665
736
|
function asObject(value, label) {
|
|
666
737
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
667
738
|
throw new Error(`${label} must be an object.`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatBytes(bytes: number): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function formatBytes(bytes) {
|
|
2
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
3
|
+
return "0 B";
|
|
4
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
5
|
+
const power = Math.max(0, Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024))));
|
|
6
|
+
const value = bytes / 1024 ** power;
|
|
7
|
+
const precision = power === 0 || value >= 100 ? 0 : 1;
|
|
8
|
+
return `${value.toFixed(precision)} ${units[power]}`;
|
|
9
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function isSubPath(root: string, candidate: string): boolean;
|
|
2
2
|
export declare function resolveExistingDirectoryPath(inputPath: string): Promise<string>;
|
|
3
|
+
export declare function resolvePathIdentity(inputPath: string): Promise<string>;
|
|
3
4
|
export declare function ensureDirectory(inputPath: string): Promise<string>;
|
|
4
5
|
export declare function safeSegment(value: string, fallback: string): string;
|
package/dist/shared/path/path.js
CHANGED
|
@@ -4,7 +4,9 @@ export function isSubPath(root, candidate) {
|
|
|
4
4
|
const normalizedRoot = path.resolve(root);
|
|
5
5
|
const normalizedCandidate = path.resolve(candidate);
|
|
6
6
|
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
7
|
-
return relative === "" || (
|
|
7
|
+
return relative === "" || (relative !== ".."
|
|
8
|
+
&& !relative.startsWith(`..${path.sep}`)
|
|
9
|
+
&& !path.isAbsolute(relative));
|
|
8
10
|
}
|
|
9
11
|
export async function resolveExistingDirectoryPath(inputPath) {
|
|
10
12
|
const stat = await fs.stat(inputPath);
|
|
@@ -13,6 +15,24 @@ export async function resolveExistingDirectoryPath(inputPath) {
|
|
|
13
15
|
}
|
|
14
16
|
return await fs.realpath(inputPath);
|
|
15
17
|
}
|
|
18
|
+
export async function resolvePathIdentity(inputPath) {
|
|
19
|
+
let existingAncestor = path.resolve(inputPath);
|
|
20
|
+
const missingSegments = [];
|
|
21
|
+
while (true) {
|
|
22
|
+
try {
|
|
23
|
+
return path.join(await fs.realpath(existingAncestor), ...missingSegments);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== "ENOENT")
|
|
27
|
+
throw error;
|
|
28
|
+
const parent = path.dirname(existingAncestor);
|
|
29
|
+
if (parent === existingAncestor)
|
|
30
|
+
throw error;
|
|
31
|
+
missingSegments.unshift(path.basename(existingAncestor));
|
|
32
|
+
existingAncestor = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
16
36
|
export async function ensureDirectory(inputPath) {
|
|
17
37
|
await fs.mkdir(inputPath, { recursive: true });
|
|
18
38
|
return await resolveExistingDirectoryPath(inputPath);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DotNetToolchainSelection {
|
|
2
|
+
readonly executablePath: string;
|
|
3
|
+
readonly rootPath: string;
|
|
4
|
+
readonly sdkVersion: string;
|
|
5
|
+
readonly requiredMajor: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DotNetToolchainOptions {
|
|
8
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
9
|
+
readonly platform?: NodeJS.Platform;
|
|
10
|
+
readonly homeDirectory?: string;
|
|
11
|
+
readonly fileExists?: (candidate: string) => Promise<boolean>;
|
|
12
|
+
readonly listSdks?: (candidate: string) => Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
export declare function configureDotNetToolchain(repoRoots: readonly string[], options?: DotNetToolchainOptions): Promise<DotNetToolchainSelection | null>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const skippedDirectories = new Set([".git", "bin", "node_modules", "obj"]);
|
|
8
|
+
export async function configureDotNetToolchain(repoRoots, options = {}) {
|
|
9
|
+
const environment = options.environment ?? process.env;
|
|
10
|
+
const platform = options.platform ?? process.platform;
|
|
11
|
+
const requiredMajor = await requiredDotNetMajor(repoRoots);
|
|
12
|
+
if (requiredMajor === null)
|
|
13
|
+
return null;
|
|
14
|
+
const explicit = environmentValue(environment, "FORGE_DOTNET_PATH");
|
|
15
|
+
if (explicit && !path.isAbsolute(explicit)) {
|
|
16
|
+
throw new Error("dotnet_toolchain_invalid: FORGE_DOTNET_PATH must be an absolute executable path.");
|
|
17
|
+
}
|
|
18
|
+
const candidates = dotNetCandidates(environment, platform, options.homeDirectory ?? os.homedir(), requiredMajor);
|
|
19
|
+
const fileExists = options.fileExists ?? isFile;
|
|
20
|
+
const listSdks = options.listSdks ?? probeSdks;
|
|
21
|
+
const probed = [];
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
if (!await fileExists(candidate))
|
|
24
|
+
continue;
|
|
25
|
+
try {
|
|
26
|
+
const output = await listSdks(candidate);
|
|
27
|
+
const sdkVersion = sdkVersions(output).find((version) => majorVersion(version) === requiredMajor);
|
|
28
|
+
probed.push(`${candidate} (${sdkVersions(output).join(", ") || "no SDKs"})`);
|
|
29
|
+
if (!sdkVersion)
|
|
30
|
+
continue;
|
|
31
|
+
const rootPath = path.dirname(candidate);
|
|
32
|
+
prependPath(environment, rootPath, platform);
|
|
33
|
+
setEnvironmentValue(environment, "DOTNET_ROOT", rootPath);
|
|
34
|
+
if (platform === "win32")
|
|
35
|
+
setEnvironmentValue(environment, "DOTNET_ROOT_X64", rootPath);
|
|
36
|
+
setEnvironmentValue(environment, "FORGE_DOTNET_PATH", candidate);
|
|
37
|
+
return { executablePath: candidate, rootPath, sdkVersion, requiredMajor };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
probed.push(`${candidate} (${error instanceof Error ? error.message : String(error)})`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const detail = probed.length > 0 ? ` Probed: ${probed.join("; ")}.` : "";
|
|
44
|
+
throw new Error(`dotnet_toolchain_unavailable: repositories require .NET SDK ${requiredMajor}.x, but no compatible dotnet executable was found.`
|
|
45
|
+
+ ` Set FORGE_DOTNET_PATH to the absolute executable path.${detail}`);
|
|
46
|
+
}
|
|
47
|
+
async function requiredDotNetMajor(repoRoots) {
|
|
48
|
+
const majors = [];
|
|
49
|
+
for (const root of [...new Set(repoRoots.map((candidate) => path.resolve(candidate)))]) {
|
|
50
|
+
const globalMajor = await readGlobalJsonMajor(root);
|
|
51
|
+
if (globalMajor !== null) {
|
|
52
|
+
majors.push(globalMajor);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
majors.push(...await readProjectTargetMajors(root));
|
|
56
|
+
}
|
|
57
|
+
return majors.length > 0 ? Math.max(...majors) : null;
|
|
58
|
+
}
|
|
59
|
+
async function readGlobalJsonMajor(root) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(await fs.readFile(path.join(root, "global.json"), "utf8"));
|
|
62
|
+
return typeof parsed.sdk?.version === "string" ? majorVersion(parsed.sdk.version) : null;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function readProjectTargetMajors(root) {
|
|
69
|
+
const projects = await findProjectFiles(root, 4, 256);
|
|
70
|
+
const majors = [];
|
|
71
|
+
for (const project of projects) {
|
|
72
|
+
let source;
|
|
73
|
+
try {
|
|
74
|
+
source = await fs.readFile(project, "utf8");
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
for (const match of source.matchAll(/<TargetFrameworks?>\s*([^<]+)\s*<\/TargetFrameworks?>/giu)) {
|
|
80
|
+
for (const framework of match[1]?.split(";") ?? []) {
|
|
81
|
+
const target = /^net(\d+)\./iu.exec(framework.trim());
|
|
82
|
+
if (target?.[1])
|
|
83
|
+
majors.push(Number.parseInt(target[1], 10));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return majors;
|
|
88
|
+
}
|
|
89
|
+
async function findProjectFiles(root, maximumDepth, maximumFiles) {
|
|
90
|
+
const projects = [];
|
|
91
|
+
async function visit(directory, depth) {
|
|
92
|
+
if (depth > maximumDepth || projects.length >= maximumFiles)
|
|
93
|
+
return;
|
|
94
|
+
let entries;
|
|
95
|
+
try {
|
|
96
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
if (projects.length >= maximumFiles)
|
|
103
|
+
return;
|
|
104
|
+
if (entry.isDirectory() && !skippedDirectories.has(entry.name)) {
|
|
105
|
+
await visit(path.join(directory, entry.name), depth + 1);
|
|
106
|
+
}
|
|
107
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".csproj")) {
|
|
108
|
+
projects.push(path.join(directory, entry.name));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
await visit(root, 0);
|
|
113
|
+
return projects;
|
|
114
|
+
}
|
|
115
|
+
function dotNetCandidates(environment, platform, homeDirectory, requiredMajor) {
|
|
116
|
+
const executable = platform === "win32" ? "dotnet.exe" : "dotnet";
|
|
117
|
+
const explicit = environmentValue(environment, "FORGE_DOTNET_PATH");
|
|
118
|
+
if (explicit)
|
|
119
|
+
return [explicit];
|
|
120
|
+
const dotNetRoot = environmentValue(environment, "DOTNET_ROOT");
|
|
121
|
+
const pathValue = environmentValue(environment, "PATH") ?? "";
|
|
122
|
+
const programFiles = environmentValue(environment, "ProgramFiles");
|
|
123
|
+
const candidates = [
|
|
124
|
+
dotNetRoot ? path.join(dotNetRoot, executable) : null,
|
|
125
|
+
path.join(homeDirectory, ".dotnet", executable),
|
|
126
|
+
path.join(homeDirectory, `.dotnet${requiredMajor}`, executable),
|
|
127
|
+
...(platform === "win32" && programFiles ? [path.join(programFiles, "dotnet", executable)] : []),
|
|
128
|
+
...pathValue.split(path.delimiter).filter(Boolean).map((directory) => path.join(directory, executable)),
|
|
129
|
+
].filter((candidate) => Boolean(candidate));
|
|
130
|
+
return [...new Map(candidates.map((candidate) => [canonicalPath(candidate, platform), path.resolve(candidate)])).values()];
|
|
131
|
+
}
|
|
132
|
+
async function probeSdks(candidate) {
|
|
133
|
+
const { stdout } = await execFileAsync(candidate, ["--list-sdks"], {
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
timeout: 10_000,
|
|
136
|
+
windowsHide: true,
|
|
137
|
+
});
|
|
138
|
+
return stdout;
|
|
139
|
+
}
|
|
140
|
+
function sdkVersions(output) {
|
|
141
|
+
return output.split(/\r?\n/gu)
|
|
142
|
+
.map((line) => /^(\d+\.\d+\.\d+(?:[-+][^\s]+)?)/u.exec(line.trim())?.[1] ?? null)
|
|
143
|
+
.filter((version) => version !== null);
|
|
144
|
+
}
|
|
145
|
+
function majorVersion(version) {
|
|
146
|
+
const match = /^(\d+)\./u.exec(version.trim());
|
|
147
|
+
return match?.[1] ? Number.parseInt(match[1], 10) : null;
|
|
148
|
+
}
|
|
149
|
+
async function isFile(candidate) {
|
|
150
|
+
try {
|
|
151
|
+
return (await fs.stat(candidate)).isFile();
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function prependPath(environment, directory, platform) {
|
|
158
|
+
const current = environmentValue(environment, "PATH") ?? "";
|
|
159
|
+
const normalizedDirectory = canonicalPath(directory, platform);
|
|
160
|
+
const remaining = current.split(path.delimiter)
|
|
161
|
+
.filter(Boolean)
|
|
162
|
+
.filter((entry) => canonicalPath(entry, platform) !== normalizedDirectory);
|
|
163
|
+
setEnvironmentValue(environment, "PATH", [directory, ...remaining].join(path.delimiter));
|
|
164
|
+
}
|
|
165
|
+
function environmentValue(environment, name) {
|
|
166
|
+
const key = Object.keys(environment).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
167
|
+
return key ? environment[key] : undefined;
|
|
168
|
+
}
|
|
169
|
+
function setEnvironmentValue(environment, name, value) {
|
|
170
|
+
const keys = Object.keys(environment).filter((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
171
|
+
const target = keys[0] ?? name;
|
|
172
|
+
for (const duplicate of keys.slice(1))
|
|
173
|
+
delete environment[duplicate];
|
|
174
|
+
environment[target] = value;
|
|
175
|
+
}
|
|
176
|
+
function canonicalPath(candidate, platform) {
|
|
177
|
+
const normalized = path.normalize(candidate);
|
|
178
|
+
return platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
179
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HostProcessSupervisorMetadata } from "./host-process-supervisor.js";
|
|
2
|
+
export type HostProcessRecoveryOutcome = "absent" | "terminated" | "pid_reused" | "unverifiable";
|
|
3
|
+
export interface HostProcessRecovery {
|
|
4
|
+
recover(metadata: HostProcessSupervisorMetadata): Promise<HostProcessRecoveryOutcome>;
|
|
5
|
+
}
|
|
6
|
+
/** Exact, birth-marker-fenced recovery for a process tree left by an old bridge. */
|
|
7
|
+
export declare class ExactHostProcessRecovery implements HostProcessRecovery {
|
|
8
|
+
private readonly platform;
|
|
9
|
+
private readonly graceMs;
|
|
10
|
+
constructor(platform?: NodeJS.Platform, graceMs?: number);
|
|
11
|
+
recover(metadata: HostProcessSupervisorMetadata): Promise<HostProcessRecoveryOutcome>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { captureHostProcessIdentity } from "./host-process-supervisor.js";
|
|
2
|
+
/** Exact, birth-marker-fenced recovery for a process tree left by an old bridge. */
|
|
3
|
+
export class ExactHostProcessRecovery {
|
|
4
|
+
platform;
|
|
5
|
+
graceMs;
|
|
6
|
+
constructor(platform = process.platform, graceMs = 2_000) {
|
|
7
|
+
this.platform = platform;
|
|
8
|
+
this.graceMs = graceMs;
|
|
9
|
+
}
|
|
10
|
+
async recover(metadata) {
|
|
11
|
+
if (!processExists(metadata.rootPid))
|
|
12
|
+
return "absent";
|
|
13
|
+
if (!metadata.rootIdentity)
|
|
14
|
+
return "unverifiable";
|
|
15
|
+
const currentIdentity = captureHostProcessIdentity(metadata.rootPid, this.platform);
|
|
16
|
+
if (!currentIdentity)
|
|
17
|
+
return processExists(metadata.rootPid) ? "unverifiable" : "absent";
|
|
18
|
+
if (currentIdentity !== metadata.rootIdentity)
|
|
19
|
+
return "pid_reused";
|
|
20
|
+
if (this.platform === "win32") {
|
|
21
|
+
signalExactProcess(metadata.rootPid, "SIGTERM");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
signalExactProcessGroup(metadata.rootPid, "SIGTERM");
|
|
25
|
+
}
|
|
26
|
+
await waitForExit(metadata.rootPid, this.graceMs);
|
|
27
|
+
if (processExists(metadata.rootPid)) {
|
|
28
|
+
if (captureHostProcessIdentity(metadata.rootPid, this.platform) !== metadata.rootIdentity) {
|
|
29
|
+
return "pid_reused";
|
|
30
|
+
}
|
|
31
|
+
if (this.platform === "win32") {
|
|
32
|
+
signalExactProcess(metadata.rootPid, "SIGKILL");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
signalExactProcessGroup(metadata.rootPid, "SIGKILL");
|
|
36
|
+
}
|
|
37
|
+
await waitForExit(metadata.rootPid, this.graceMs);
|
|
38
|
+
}
|
|
39
|
+
if (processExists(metadata.rootPid)
|
|
40
|
+
&& captureHostProcessIdentity(metadata.rootPid, this.platform) === metadata.rootIdentity) {
|
|
41
|
+
throw new Error("host_process_recovery_degraded: exact supervised process tree did not stop.");
|
|
42
|
+
}
|
|
43
|
+
return "terminated";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function processExists(pid) {
|
|
47
|
+
try {
|
|
48
|
+
process.kill(pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return error.code === "EPERM";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function signalExactProcess(pid, signal) {
|
|
56
|
+
try {
|
|
57
|
+
process.kill(pid, signal);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.code !== "ESRCH")
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function signalExactProcessGroup(pid, signal) {
|
|
65
|
+
try {
|
|
66
|
+
process.kill(-pid, signal);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.code !== "ESRCH")
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function waitForExit(pid, timeoutMs) {
|
|
74
|
+
const deadline = Date.now() + timeoutMs;
|
|
75
|
+
while (Date.now() < deadline && processExists(pid)) {
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
77
|
+
}
|
|
78
|
+
}
|