@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,330 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const windowsJobSupervisorPath = fileURLToPath(new URL("../../../runtime/windows-job-supervisor.ps1", import.meta.url));
|
|
7
|
+
export function resolveHostProcessSupervisedLaunch(request, resolvedCommand, resolvedArgs, platform = process.platform) {
|
|
8
|
+
const env = request.env;
|
|
9
|
+
if (env?.FORGE_RUNTIME_MODE !== "host")
|
|
10
|
+
return null;
|
|
11
|
+
const supervisorId = requiredEnvironment(env, "FORGE_PROCESS_SUPERVISOR_ID");
|
|
12
|
+
const metadataPath = requiredEnvironment(env, "FORGE_PROCESS_SUPERVISOR_METADATA_PATH");
|
|
13
|
+
const logDirectory = requiredEnvironment(env, "FORGE_LOG_DIR");
|
|
14
|
+
assertMetadataPath(logDirectory, metadataPath);
|
|
15
|
+
const commandDigest = createHash("sha256")
|
|
16
|
+
.update(JSON.stringify([resolvedCommand, ...resolvedArgs]))
|
|
17
|
+
.digest("hex");
|
|
18
|
+
if (platform === "win32") {
|
|
19
|
+
if (!fs.existsSync(windowsJobSupervisorPath)) {
|
|
20
|
+
throw new Error(`host_process_supervisor_missing: ${windowsJobSupervisorPath}`);
|
|
21
|
+
}
|
|
22
|
+
const payloadPath = `${metadataPath}.launch.json`;
|
|
23
|
+
const readyPath = `${metadataPath}.provider-ready.json`;
|
|
24
|
+
const startGrantPath = `${metadataPath}.provider-start-granted`;
|
|
25
|
+
const memoryTelemetryPath = `${metadataPath}.memory.json`;
|
|
26
|
+
const serializedPayload = `${JSON.stringify({
|
|
27
|
+
command: resolvedCommand,
|
|
28
|
+
args: resolvedArgs,
|
|
29
|
+
cwd: request.cwd,
|
|
30
|
+
parentPid: process.pid,
|
|
31
|
+
supervisorId,
|
|
32
|
+
readyPath,
|
|
33
|
+
startGrantPath,
|
|
34
|
+
memoryTelemetryPath,
|
|
35
|
+
})}\n`;
|
|
36
|
+
const payloadSha256 = createHash("sha256").update(serializedPayload).digest("hex");
|
|
37
|
+
fs.writeFileSync(payloadPath, serializedPayload, {
|
|
38
|
+
encoding: "utf8",
|
|
39
|
+
flag: "wx",
|
|
40
|
+
mode: 0o600,
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
command: windowsPowerShellPath(),
|
|
44
|
+
args: [
|
|
45
|
+
"-NoLogo",
|
|
46
|
+
"-NoProfile",
|
|
47
|
+
"-NonInteractive",
|
|
48
|
+
"-ExecutionPolicy",
|
|
49
|
+
"Bypass",
|
|
50
|
+
"-File",
|
|
51
|
+
windowsJobSupervisorPath,
|
|
52
|
+
"-Mode",
|
|
53
|
+
"Outer",
|
|
54
|
+
"-PayloadPath",
|
|
55
|
+
payloadPath,
|
|
56
|
+
"-PayloadSha256",
|
|
57
|
+
payloadSha256,
|
|
58
|
+
],
|
|
59
|
+
detached: false,
|
|
60
|
+
supervisorId,
|
|
61
|
+
strategy: "windows_job_object",
|
|
62
|
+
metadataPath,
|
|
63
|
+
memoryTelemetryPath,
|
|
64
|
+
payloadPath,
|
|
65
|
+
payloadSha256,
|
|
66
|
+
readyPath,
|
|
67
|
+
startGrantPath,
|
|
68
|
+
commandDigest,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
command: resolvedCommand,
|
|
73
|
+
args: resolvedArgs,
|
|
74
|
+
detached: true,
|
|
75
|
+
supervisorId,
|
|
76
|
+
strategy: "unix_process_group",
|
|
77
|
+
metadataPath,
|
|
78
|
+
commandDigest,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function persistHostProcessSupervisorMetadata(launch, request, rootPid, rootCreatedAt, providerReady) {
|
|
82
|
+
if (!Number.isSafeInteger(rootPid) || rootPid <= 0) {
|
|
83
|
+
throw new Error("host_process_supervisor_invalid: supervised root PID was not assigned.");
|
|
84
|
+
}
|
|
85
|
+
const env = request.env ?? {};
|
|
86
|
+
const metadata = {
|
|
87
|
+
schemaVersion: 1,
|
|
88
|
+
supervisorId: launch.supervisorId,
|
|
89
|
+
strategy: launch.strategy,
|
|
90
|
+
rootPid,
|
|
91
|
+
rootCreatedAt,
|
|
92
|
+
rootIdentity: providerReady?.rootPid === rootPid
|
|
93
|
+
? providerReady.rootIdentity ?? null
|
|
94
|
+
: captureHostProcessIdentity(rootPid),
|
|
95
|
+
commandDigest: launch.commandDigest,
|
|
96
|
+
worktreePath: request.cwd,
|
|
97
|
+
runtimeLeaseId: requiredEnvironment(env, "FORGE_RESOURCE_LEASE_ID"),
|
|
98
|
+
dispatchAttemptId: requiredEnvironment(env, "FORGE_DISPATCH_ATTEMPT_ID"),
|
|
99
|
+
fencingToken: requiredEnvironment(env, "FORGE_FENCING_TOKEN"),
|
|
100
|
+
bridgePid: process.pid,
|
|
101
|
+
providerPid: providerReady?.providerPid,
|
|
102
|
+
providerCreatedAt: providerReady?.providerCreatedAt,
|
|
103
|
+
providerIdentity: providerReady?.providerIdentity
|
|
104
|
+
?? (providerReady ? captureHostProcessIdentity(providerReady.providerPid) : undefined),
|
|
105
|
+
providerStartAttempt: providerReady?.startAttempt,
|
|
106
|
+
};
|
|
107
|
+
fs.writeFileSync(launch.metadataPath, `${JSON.stringify(metadata)}\n`, {
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
flag: "wx",
|
|
110
|
+
mode: 0o600,
|
|
111
|
+
});
|
|
112
|
+
return metadata;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Captures an OS-issued process birth marker. Recovery compares this marker
|
|
116
|
+
* before signalling a persisted PID so PID reuse can never redirect cleanup
|
|
117
|
+
* at an unrelated process.
|
|
118
|
+
*/
|
|
119
|
+
export function captureHostProcessIdentity(pid, platform = process.platform) {
|
|
120
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
121
|
+
return null;
|
|
122
|
+
try {
|
|
123
|
+
if (platform === "linux") {
|
|
124
|
+
const bootId = fs.readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
125
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
126
|
+
const close = stat.lastIndexOf(")");
|
|
127
|
+
const fields = close >= 0 ? stat.slice(close + 2).trim().split(/\s+/u) : [];
|
|
128
|
+
const startTicks = fields[19];
|
|
129
|
+
return bootId && startTicks && /^\d+$/u.test(startTicks)
|
|
130
|
+
? `linux:${bootId}:${startTicks}`
|
|
131
|
+
: null;
|
|
132
|
+
}
|
|
133
|
+
if (platform === "win32") {
|
|
134
|
+
const output = execFileSync(windowsPowerShellPath(), [
|
|
135
|
+
"-NoLogo",
|
|
136
|
+
"-NoProfile",
|
|
137
|
+
"-NonInteractive",
|
|
138
|
+
"-Command",
|
|
139
|
+
`(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`,
|
|
140
|
+
], {
|
|
141
|
+
encoding: "utf8",
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
timeout: 10_000,
|
|
144
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
145
|
+
}).trim();
|
|
146
|
+
return /^\d+$/u.test(output) ? `win32:${output}` : null;
|
|
147
|
+
}
|
|
148
|
+
// Other Unix variants do not expose a sufficiently strong birth marker
|
|
149
|
+
// through Node. Fail closed rather than trust a second-resolution ps time.
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export async function terminateHostProcessTree(child, launch, graceMs = 2_000) {
|
|
157
|
+
if (!launch) {
|
|
158
|
+
await terminateUnsupervisedProcessTree(child, graceMs);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (launch.strategy === "windows_job_object") {
|
|
162
|
+
const rootPid = child.pid ?? 0;
|
|
163
|
+
const metadata = readSupervisorMetadata(launch.metadataPath);
|
|
164
|
+
const rootIdentity = metadata?.rootPid === rootPid
|
|
165
|
+
? metadata.rootIdentity ?? null
|
|
166
|
+
: captureHostProcessIdentity(rootPid, "win32");
|
|
167
|
+
try {
|
|
168
|
+
child.kill("SIGTERM");
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Closing the launcher is best effort when the Job Object already settled.
|
|
172
|
+
}
|
|
173
|
+
await waitForExactProcessExit(rootPid, rootIdentity, graceMs, "win32");
|
|
174
|
+
if (exactProcessExists(rootPid, rootIdentity, "win32")) {
|
|
175
|
+
forceWindowsProcessTree(rootPid);
|
|
176
|
+
await waitForExactProcessExit(rootPid, rootIdentity, graceMs, "win32");
|
|
177
|
+
}
|
|
178
|
+
if (exactProcessExists(rootPid, rootIdentity, "win32")) {
|
|
179
|
+
throw new Error("host_process_supervisor_termination_failed: supervised Windows root did not stop.");
|
|
180
|
+
}
|
|
181
|
+
const providerPid = metadata?.providerPid ?? 0;
|
|
182
|
+
const providerIdentity = metadata?.providerIdentity ?? null;
|
|
183
|
+
if (exactProcessExists(providerPid, providerIdentity, "win32")) {
|
|
184
|
+
forceWindowsProcessTree(providerPid);
|
|
185
|
+
await waitForExactProcessExit(providerPid, providerIdentity, graceMs, "win32");
|
|
186
|
+
}
|
|
187
|
+
if (exactProcessExists(providerPid, providerIdentity, "win32")) {
|
|
188
|
+
throw new Error("host_process_supervisor_termination_failed: supervised Windows provider tree did not stop.");
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const processGroupId = child.pid;
|
|
193
|
+
if (!processGroupId || !signalUnixProcessGroup(processGroupId, "SIGTERM"))
|
|
194
|
+
return;
|
|
195
|
+
await waitForUnixProcessGroupExit(processGroupId, graceMs);
|
|
196
|
+
if (unixProcessGroupExists(processGroupId)) {
|
|
197
|
+
signalUnixProcessGroup(processGroupId, "SIGKILL");
|
|
198
|
+
await waitForUnixProcessGroupExit(processGroupId, graceMs);
|
|
199
|
+
}
|
|
200
|
+
if (unixProcessGroupExists(processGroupId)) {
|
|
201
|
+
throw new Error("host_process_supervisor_termination_failed: supervised Unix process group did not stop.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function terminateUnsupervisedProcessTree(child, graceMs) {
|
|
205
|
+
const rootPid = child.pid ?? 0;
|
|
206
|
+
if (!Number.isSafeInteger(rootPid) || rootPid <= 0) {
|
|
207
|
+
try {
|
|
208
|
+
child.kill("SIGTERM");
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// The process already settled before a PID was assigned.
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (process.platform === "win32") {
|
|
216
|
+
// Without a Job Object there is no trustworthy ancestry record after the
|
|
217
|
+
// root has already exited. Never target a potentially reused Windows PID.
|
|
218
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
219
|
+
return;
|
|
220
|
+
const rootIdentity = captureHostProcessIdentity(rootPid, "win32");
|
|
221
|
+
forceWindowsProcessTree(rootPid);
|
|
222
|
+
await waitForExactProcessExit(rootPid, rootIdentity, graceMs, "win32");
|
|
223
|
+
if (exactProcessExists(rootPid, rootIdentity, "win32")) {
|
|
224
|
+
throw new Error("host_process_supervisor_termination_failed: unsupervised Windows provider tree did not stop.");
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (!signalUnixProcessGroup(rootPid, "SIGTERM"))
|
|
229
|
+
return;
|
|
230
|
+
await waitForUnixProcessGroupExit(rootPid, graceMs);
|
|
231
|
+
if (unixProcessGroupExists(rootPid)) {
|
|
232
|
+
signalUnixProcessGroup(rootPid, "SIGKILL");
|
|
233
|
+
await waitForUnixProcessGroupExit(rootPid, graceMs);
|
|
234
|
+
}
|
|
235
|
+
if (unixProcessGroupExists(rootPid)) {
|
|
236
|
+
throw new Error("host_process_supervisor_termination_failed: unsupervised Unix provider tree did not stop.");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function requiredEnvironment(env, name) {
|
|
240
|
+
const value = env[name]?.trim();
|
|
241
|
+
if (!value)
|
|
242
|
+
throw new Error(`host_process_supervisor_invalid: ${name} is required.`);
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function assertMetadataPath(logDirectory, metadataPath) {
|
|
246
|
+
const root = path.resolve(logDirectory);
|
|
247
|
+
const target = path.resolve(metadataPath);
|
|
248
|
+
if (target !== root && !target.startsWith(`${root}${path.sep}`)) {
|
|
249
|
+
throw new Error("host_process_supervisor_invalid: metadata path must be inside FORGE_LOG_DIR.");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function windowsPowerShellPath() {
|
|
253
|
+
const systemRoot = process.env.SystemRoot?.trim() || "C:\\Windows";
|
|
254
|
+
const candidate = path.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
255
|
+
return fs.existsSync(candidate) ? candidate : "powershell.exe";
|
|
256
|
+
}
|
|
257
|
+
function signalUnixProcessGroup(processGroupId, signal) {
|
|
258
|
+
try {
|
|
259
|
+
process.kill(-processGroupId, signal);
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
if (error.code === "ESRCH")
|
|
264
|
+
return false;
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function unixProcessGroupExists(processGroupId) {
|
|
269
|
+
try {
|
|
270
|
+
process.kill(-processGroupId, 0);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (error.code === "ESRCH")
|
|
275
|
+
return false;
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function waitForUnixProcessGroupExit(processGroupId, timeoutMs) {
|
|
280
|
+
const deadline = Date.now() + timeoutMs;
|
|
281
|
+
while (Date.now() < deadline && unixProcessGroupExists(processGroupId)) {
|
|
282
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function readSupervisorMetadata(metadataPath) {
|
|
286
|
+
try {
|
|
287
|
+
return JSON.parse(fs.readFileSync(metadataPath, "utf8"));
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function exactProcessExists(pid, identity, platform) {
|
|
294
|
+
if (!identity || !processExists(pid))
|
|
295
|
+
return false;
|
|
296
|
+
return captureHostProcessIdentity(pid, platform) === identity;
|
|
297
|
+
}
|
|
298
|
+
function processExists(pid) {
|
|
299
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
300
|
+
return false;
|
|
301
|
+
try {
|
|
302
|
+
process.kill(pid, 0);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
return error.code === "EPERM";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function waitForExactProcessExit(pid, identity, timeoutMs, platform) {
|
|
310
|
+
const deadline = Date.now() + timeoutMs;
|
|
311
|
+
while (Date.now() < deadline && exactProcessExists(pid, identity, platform)) {
|
|
312
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function forceWindowsProcessTree(pid) {
|
|
316
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
317
|
+
return;
|
|
318
|
+
const systemRoot = process.env.SystemRoot?.trim() || "C:\\Windows";
|
|
319
|
+
const taskkill = path.win32.join(systemRoot, "System32", "taskkill.exe");
|
|
320
|
+
try {
|
|
321
|
+
execFileSync(taskkill, ["/PID", String(pid), "/T", "/F"], {
|
|
322
|
+
windowsHide: true,
|
|
323
|
+
timeout: 5_000,
|
|
324
|
+
stdio: "ignore",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// A racing process exit is verified by the caller before it returns.
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -2,6 +2,9 @@ export interface ProcessRunRequest {
|
|
|
2
2
|
readonly command: string;
|
|
3
3
|
readonly args: readonly string[];
|
|
4
4
|
readonly cwd: string;
|
|
5
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
6
|
+
readonly rawOutput?: boolean;
|
|
7
|
+
readonly windowsVerbatimArguments?: boolean;
|
|
5
8
|
}
|
|
6
9
|
export interface ProcessRunResult {
|
|
7
10
|
readonly exitCode: number;
|
|
@@ -5,22 +5,28 @@ export class NodeProcessRunner {
|
|
|
5
5
|
return await new Promise((resolve, reject) => {
|
|
6
6
|
const child = spawn(request.command, request.args, {
|
|
7
7
|
cwd: request.cwd,
|
|
8
|
-
|
|
8
|
+
env: request.env,
|
|
9
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
10
|
+
windowsVerbatimArguments: request.windowsVerbatimArguments,
|
|
9
11
|
});
|
|
10
12
|
let stdout = "";
|
|
11
13
|
let stderr = "";
|
|
12
14
|
child.on("error", reject);
|
|
13
|
-
const stdoutReader = readline.createInterface({ input: child.stdout });
|
|
14
|
-
const stderrReader = readline.createInterface({ input: child.stderr });
|
|
15
|
-
|
|
15
|
+
const stdoutReader = request.rawOutput ? undefined : readline.createInterface({ input: child.stdout });
|
|
16
|
+
const stderrReader = request.rawOutput ? undefined : readline.createInterface({ input: child.stderr });
|
|
17
|
+
if (request.rawOutput) {
|
|
18
|
+
child.stdout.setEncoding("utf8").on("data", chunk => { stdout += chunk; });
|
|
19
|
+
child.stderr.setEncoding("utf8").on("data", chunk => { stderr += chunk; });
|
|
20
|
+
}
|
|
21
|
+
stdoutReader?.on("line", (line) => {
|
|
16
22
|
stdout += `${line}\n`;
|
|
17
23
|
});
|
|
18
|
-
stderrReader
|
|
24
|
+
stderrReader?.on("line", (line) => {
|
|
19
25
|
stderr += `${line}\n`;
|
|
20
26
|
});
|
|
21
27
|
child.on("close", (code) => {
|
|
22
|
-
stdoutReader
|
|
23
|
-
stderrReader
|
|
28
|
+
stdoutReader?.close();
|
|
29
|
+
stderrReader?.close();
|
|
24
30
|
resolve({
|
|
25
31
|
exitCode: code ?? 1,
|
|
26
32
|
stdout,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface LaunchCommand {
|
|
2
|
+
readonly command: string;
|
|
3
|
+
readonly args: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function buildWindowsCmdLaunch(command: string, args: readonly string[], comSpec?: string | undefined): LaunchCommand;
|
|
6
|
+
export declare function buildWindowsLaunchCommand(commandPath: string, args: readonly string[]): LaunchCommand;
|
|
7
|
+
export declare function windowsCommandLine(command: string, args: readonly string[]): string;
|
|
8
|
+
export declare function quoteWindowsCommandLineArg(value: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function buildWindowsCmdLaunch(command, args, comSpec = process.env.ComSpec) {
|
|
3
|
+
const resolvedComSpec = comSpec?.trim();
|
|
4
|
+
const commandLine = windowsCommandLine(command, args);
|
|
5
|
+
const cmdPayload = commandLine.startsWith('"') ? `"${commandLine}"` : commandLine;
|
|
6
|
+
return {
|
|
7
|
+
command: resolvedComSpec || "cmd.exe",
|
|
8
|
+
args: ["/d", "/s", "/v:off", "/c", cmdPayload]
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function buildWindowsLaunchCommand(commandPath, args) {
|
|
12
|
+
const extension = path.extname(commandPath).toLowerCase();
|
|
13
|
+
if (extension === ".ps1") {
|
|
14
|
+
return {
|
|
15
|
+
command: powershellExecutable(),
|
|
16
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", commandPath, ...args]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (extension === ".cmd" || extension === ".bat") {
|
|
20
|
+
return buildWindowsCmdLaunch(commandPath, args);
|
|
21
|
+
}
|
|
22
|
+
return { command: commandPath, args };
|
|
23
|
+
}
|
|
24
|
+
export function windowsCommandLine(command, args) {
|
|
25
|
+
return [command, ...args].map(quoteWindowsCommandLineArg).join(" ");
|
|
26
|
+
}
|
|
27
|
+
export function quoteWindowsCommandLineArg(value) {
|
|
28
|
+
const escaped = value
|
|
29
|
+
.replace(/\^/g, "^^")
|
|
30
|
+
.replace(/%/g, "^%");
|
|
31
|
+
if (escaped.length > 0 && !/[\s"&|<>()^%!]/.test(escaped)) {
|
|
32
|
+
return escaped;
|
|
33
|
+
}
|
|
34
|
+
return `"${escaped.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/g, "$1$1")}"`;
|
|
35
|
+
}
|
|
36
|
+
function powershellExecutable() {
|
|
37
|
+
const systemRoot = process.env.SystemRoot;
|
|
38
|
+
return systemRoot
|
|
39
|
+
? path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
40
|
+
: "powershell.exe";
|
|
41
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type BridgeChatCapability } from "../bridge/bridge-chat.types.js";
|
|
2
|
+
import type { BridgeProviderStatus } from "../bridge/bridge.types.js";
|
|
3
|
+
export declare function supportsDraftImageAttachmentsFor(cliProviders: readonly string[]): boolean;
|
|
4
|
+
export declare function bridgeChatCapabilityFor(cliProviders: readonly string[], providerStatuses: Readonly<Record<string, BridgeProviderStatus>>, baseCapability?: BridgeChatCapability | null): BridgeChatCapability;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { bridgeChatProtocolVersion, normalizeBridgeChatCapability, unavailableBridgeChatCapability, } from "../bridge/bridge-chat.types.js";
|
|
2
|
+
const defaultBridgeChatMaxMessageBytes = 128 * 1024;
|
|
3
|
+
const defaultBridgeChatMaxContextRefs = 8;
|
|
4
|
+
export function supportsDraftImageAttachmentsFor(cliProviders) {
|
|
5
|
+
return cliProviders.some((provider) => provider.trim().toLowerCase() === "codex");
|
|
6
|
+
}
|
|
7
|
+
export function bridgeChatCapabilityFor(cliProviders, providerStatuses, baseCapability) {
|
|
8
|
+
const base = normalizeBridgeChatCapability(baseCapability ?? unavailableBridgeChatCapability());
|
|
9
|
+
if (base.protocolVersion !== bridgeChatProtocolVersion) {
|
|
10
|
+
return base;
|
|
11
|
+
}
|
|
12
|
+
const providers = chatProviderEntries(cliProviders, providerStatuses);
|
|
13
|
+
const usableProviders = providers.filter((provider) => provider.status?.signedIn !== false);
|
|
14
|
+
if (usableProviders.length === 0) {
|
|
15
|
+
return unavailableBridgeChatCapability(bridgeChatProtocolVersion, providers.length === 0 ? "no_cli_providers" : "no_signed_in_cli_providers", providers.length === 0
|
|
16
|
+
? "No CLI providers are configured for Bridge Chat."
|
|
17
|
+
: "No signed-in CLI providers are available for Bridge Chat.");
|
|
18
|
+
}
|
|
19
|
+
return normalizeBridgeChatCapability({
|
|
20
|
+
...base,
|
|
21
|
+
availability: usableProviders.length === providers.length ? "available" : "degraded",
|
|
22
|
+
protocolVersion: bridgeChatProtocolVersion,
|
|
23
|
+
supportedProviders: usableProviders.map((provider) => provider.providerKind),
|
|
24
|
+
supportedModels: sortedUniqueStrings(usableProviders.flatMap((provider) => provider.status?.models ?? []), false),
|
|
25
|
+
supportsTextStreaming: true,
|
|
26
|
+
supportsCancel: true,
|
|
27
|
+
supportsResume: true,
|
|
28
|
+
supportsProviderThreadContinuity: true,
|
|
29
|
+
supportsAttachments: true,
|
|
30
|
+
supportsActionBrokerGrants: true,
|
|
31
|
+
maxMessageBytes: base.maxMessageBytes || defaultBridgeChatMaxMessageBytes,
|
|
32
|
+
maxContextRefs: base.maxContextRefs || defaultBridgeChatMaxContextRefs,
|
|
33
|
+
unavailableCode: null,
|
|
34
|
+
unavailableReason: null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function chatProviderEntries(cliProviders, providerStatuses) {
|
|
38
|
+
const statuses = new Map();
|
|
39
|
+
for (const [provider, status] of Object.entries(providerStatuses)) {
|
|
40
|
+
const providerKind = provider.trim().toLowerCase();
|
|
41
|
+
if (providerKind.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
statuses.set(providerKind, { providerKind, status });
|
|
44
|
+
}
|
|
45
|
+
const providers = new Map();
|
|
46
|
+
for (const provider of cliProviders) {
|
|
47
|
+
const providerKind = provider.trim().toLowerCase();
|
|
48
|
+
if (providerKind.length === 0)
|
|
49
|
+
continue;
|
|
50
|
+
providers.set(providerKind, {
|
|
51
|
+
providerKind,
|
|
52
|
+
status: statuses.get(providerKind)?.status,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
for (const [providerKind, entry] of statuses) {
|
|
56
|
+
if (!providers.has(providerKind)) {
|
|
57
|
+
providers.set(providerKind, entry);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...providers.values()].sort((left, right) => compareStable(left.providerKind, right.providerKind));
|
|
61
|
+
}
|
|
62
|
+
function sortedUniqueStrings(values, lower) {
|
|
63
|
+
const byKey = new Map();
|
|
64
|
+
for (const value of values) {
|
|
65
|
+
const clean = value.trim();
|
|
66
|
+
if (clean.length === 0)
|
|
67
|
+
continue;
|
|
68
|
+
const normalized = lower ? clean.toLowerCase() : clean;
|
|
69
|
+
const key = normalized.toLowerCase();
|
|
70
|
+
if (!byKey.has(key)) {
|
|
71
|
+
byKey.set(key, normalized);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...byKey.values()].sort(compareStable);
|
|
75
|
+
}
|
|
76
|
+
function compareStable(left, right) {
|
|
77
|
+
const leftKey = left.toLowerCase();
|
|
78
|
+
const rightKey = right.toLowerCase();
|
|
79
|
+
if (leftKey < rightKey)
|
|
80
|
+
return -1;
|
|
81
|
+
if (leftKey > rightKey)
|
|
82
|
+
return 1;
|
|
83
|
+
if (left < right)
|
|
84
|
+
return -1;
|
|
85
|
+
if (left > right)
|
|
86
|
+
return 1;
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
const secretKeyPattern = /(api[_-]?key|authorization|bearer|client[_-]?secret|password|secret|token)/i;
|
|
2
|
+
// Serialized artifact keys follow the existing assignment policy, not the broad
|
|
3
|
+
// object redactor: tokenCount/maxTokens are evidence, not credential values.
|
|
4
|
+
const jsonCredentialKeyPattern = /(api[_-]?key|client[_-]?secret|password|secret|token)$/i;
|
|
5
|
+
const attachmentContentKeyPattern = /^(textContent|contentBase64)$/i;
|
|
6
|
+
const bridgeChatHandoffGrantKeyPattern = /^(tempFileToken|grantNonce|nonce|handle|grantId|handoffId|attachmentId)$/i;
|
|
7
|
+
const sensitiveFilenameKeyPattern = /^(fileName|filename)$/i;
|
|
8
|
+
const serializedAttachmentContentPattern = /("(?:textContent|contentBase64)"\s*:\s*")(?:(?:\\.)|[^"\\])*(")/gi;
|
|
9
|
+
const serializedBridgeChatHandoffGrantPattern = /("(?:tempFileToken|grantNonce|nonce|handle|grantId|handoffId|attachmentId)"\s*:\s*")(?:(?:\\.)|[^"\\])*(")/gi;
|
|
10
|
+
const serializedSensitiveFilenamePattern = /("(?:fileName|filename)"\s*:\s*")(?:(?:\\.)|[^"\\])*(")/gi;
|
|
11
|
+
const authorizationCredentialPattern = /(\bauthorization\s*[:=]\s*(?:bearer|basic)?\s*)([^"',\s]+)/gi;
|
|
12
|
+
const bearerCredentialPattern = /(\bbearer\s+)([^"',\s]+)/gi;
|
|
13
|
+
const secretAssignmentPattern = /(api[_-]?key|client[_-]?secret|password|secret|token)(\s*["']?\s*[:=]\s*["']?\s*)([^"',\s]+)/gi;
|
|
14
|
+
// Keep this boundary in parity with Forge.Api's SecretRedactor. Without it, the
|
|
15
|
+
// `sk-` substring in a structural identifier such as `task-skill-contract`
|
|
16
|
+
// is mistaken for a provider credential and corrupts the artifact after it has
|
|
17
|
+
// already passed schema validation.
|
|
18
|
+
const providerKeyPattern = /(?<![a-zA-Z0-9])sk-[a-zA-Z0-9_-]{12,}/g;
|
|
2
19
|
export function redactSecrets(value, exactSecrets = []) {
|
|
3
20
|
return redactValue(value, exactSecrets);
|
|
4
21
|
}
|
|
22
|
+
export function shouldRedactJsonProperty(key) {
|
|
23
|
+
// Assignment redaction already permits whitespace before the delimiter. Match
|
|
24
|
+
// that policy without normalizing the actual JSON property name on the wire.
|
|
25
|
+
const policyKey = key.trim();
|
|
26
|
+
return jsonCredentialKeyPattern.test(policyKey)
|
|
27
|
+
|| /^(authorization|bearer)$/i.test(policyKey)
|
|
28
|
+
|| attachmentContentKeyPattern.test(policyKey)
|
|
29
|
+
|| bridgeChatHandoffGrantKeyPattern.test(policyKey)
|
|
30
|
+
|| (redactSensitiveFilenames() && sensitiveFilenameKeyPattern.test(policyKey));
|
|
31
|
+
}
|
|
5
32
|
export function redactText(value, exactSecrets = []) {
|
|
6
|
-
let redacted = value
|
|
33
|
+
let redacted = value
|
|
34
|
+
.replace(serializedAttachmentContentPattern, "$1[REDACTED]$2")
|
|
35
|
+
.replace(serializedBridgeChatHandoffGrantPattern, "$1[REDACTED]$2");
|
|
36
|
+
if (redactSensitiveFilenames()) {
|
|
37
|
+
redacted = redacted.replace(serializedSensitiveFilenamePattern, "$1[REDACTED]$2");
|
|
38
|
+
}
|
|
7
39
|
for (const secret of exactSecrets) {
|
|
8
40
|
if (secret.length > 0) {
|
|
9
41
|
redacted = redacted.split(secret).join("[REDACTED]");
|
|
10
42
|
}
|
|
11
43
|
}
|
|
12
44
|
return redacted
|
|
13
|
-
.replace(
|
|
14
|
-
.replace(
|
|
45
|
+
.replace(authorizationCredentialPattern, "$1[REDACTED]")
|
|
46
|
+
.replace(bearerCredentialPattern, "$1[REDACTED]")
|
|
47
|
+
.replace(secretAssignmentPattern, "$1$2[REDACTED]")
|
|
48
|
+
.replace(providerKeyPattern, "[REDACTED]");
|
|
15
49
|
}
|
|
16
50
|
function redactValue(value, exactSecrets) {
|
|
17
51
|
if (typeof value === "string") {
|
|
@@ -25,6 +59,15 @@ function redactValue(value, exactSecrets) {
|
|
|
25
59
|
}
|
|
26
60
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
27
61
|
key,
|
|
28
|
-
secretKeyPattern.test(key)
|
|
62
|
+
secretKeyPattern.test(key)
|
|
63
|
+
|| attachmentContentKeyPattern.test(key)
|
|
64
|
+
|| bridgeChatHandoffGrantKeyPattern.test(key)
|
|
65
|
+
|| (redactSensitiveFilenames() && sensitiveFilenameKeyPattern.test(key))
|
|
66
|
+
? "[REDACTED]"
|
|
67
|
+
: redactValue(item, exactSecrets)
|
|
29
68
|
]));
|
|
30
69
|
}
|
|
70
|
+
function redactSensitiveFilenames() {
|
|
71
|
+
const value = process.env.FORGE_REDACT_FILENAMES?.trim().toLowerCase();
|
|
72
|
+
return value === "1" || value === "true" || value === "yes";
|
|
73
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const forgeBridgeVersion = "0.1.
|
|
1
|
+
export declare const forgeBridgeVersion = "0.1.4-dev.1ff37038366e";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const forgeBridgeVersion = "0.1.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.4-dev.1ff37038366e";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { DispatchResolvedArtifact } from "../session/session.types.js";
|
|
2
|
-
export declare function injectWorktreeArtifacts(worktreePath: string, artifacts: readonly DispatchResolvedArtifact[]): Promise<void>;
|
|
2
|
+
export declare function injectWorktreeArtifacts(worktreePath: string, artifacts: readonly DispatchResolvedArtifact[], protectedRelativePaths?: ReadonlySet<string>): Promise<void>;
|
|
3
3
|
export declare function filterArtifactsForLane(artifacts: readonly DispatchResolvedArtifact[], lane: string, phase: string): readonly DispatchResolvedArtifact[];
|
|
4
|
-
export declare function writeArtifact(worktreePath: string, artifact: DispatchResolvedArtifact): Promise<void>;
|
|
4
|
+
export declare function writeArtifact(worktreePath: string, artifact: DispatchResolvedArtifact, protectedRelativePaths?: ReadonlySet<string>): Promise<void>;
|
|
5
5
|
export declare function selectLaneCommand(artifacts: readonly DispatchResolvedArtifact[], lane: string, phase: string): DispatchResolvedArtifact | undefined;
|
|
6
6
|
export declare function resolveCommandSlug(lane: string, phase: string): string;
|
|
7
|
+
export declare function artifactExecutionBody(artifact: DispatchResolvedArtifact): string;
|
|
7
8
|
export declare function buildSpecBody(laneCommand: DispatchResolvedArtifact, workItem: {
|
|
8
9
|
readonly id: string;
|
|
9
10
|
readonly title: string;
|