@gethmy/harness 1.6.0 → 1.7.0
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/dist/cli.js +376 -219
- package/dist/index.js +293 -146
- package/package.json +2 -2
- package/src/cli.ts +34 -2
- package/src/exec-types.ts +33 -4
- package/src/git-pr.ts +53 -5
- package/src/oracle-collector.ts +38 -8
- package/src/oracle.ts +464 -53
- package/src/repair-sandbox.test.ts +166 -1
- package/src/repair-sandbox.ts +203 -8
- package/src/run-containment.ts +191 -37
- package/src/stage-cli.ts +32 -8
- package/src/verification.ts +200 -21
- package/src/worktree.ts +19 -2
package/dist/index.js
CHANGED
|
@@ -100,14 +100,13 @@ var init_log = __esm(() => {
|
|
|
100
100
|
};
|
|
101
101
|
});
|
|
102
102
|
// ../harmony-shared/dist/agentStaleness.js
|
|
103
|
-
var AGENT_HEARTBEAT_LIVENESS_MS, AGENT_MILESTONE_LIVENESS_MS, AGENT_SWEEP_DAEMON_MS, AGENT_SWEEP_INTERACTIVE_MS, AGENT_SWEEP_PAUSED_MS,
|
|
103
|
+
var AGENT_HEARTBEAT_LIVENESS_MS, AGENT_MILESTONE_LIVENESS_MS, AGENT_SWEEP_DAEMON_MS, AGENT_SWEEP_INTERACTIVE_MS, AGENT_SWEEP_PAUSED_MS, ACTIVE_STATUSES;
|
|
104
104
|
var init_agentStaleness = __esm(() => {
|
|
105
105
|
AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
|
|
106
106
|
AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
|
|
107
107
|
AGENT_SWEEP_DAEMON_MS = 30 * 60 * 1000;
|
|
108
108
|
AGENT_SWEEP_INTERACTIVE_MS = 2 * 60 * 60 * 1000;
|
|
109
109
|
AGENT_SWEEP_PAUSED_MS = 4 * 60 * 60 * 1000;
|
|
110
|
-
SWEPT_SESSION_WRITE_GRACE_MS = 60 * 60 * 1000;
|
|
111
110
|
ACTIVE_STATUSES = new Set(["working", "blocked", "waiting"]);
|
|
112
111
|
});
|
|
113
112
|
// ../harmony-shared/dist/branchRef.js
|
|
@@ -862,6 +861,11 @@ function containedEnv(parentEnv = process.env) {
|
|
|
862
861
|
}
|
|
863
862
|
return out;
|
|
864
863
|
}
|
|
864
|
+
function heldTestEnvKeysToStrip(parentEnv = process.env) {
|
|
865
|
+
return [
|
|
866
|
+
...new Set([...secretEnvKeysToStrip(parentEnv), ...MODEL_CREDENTIAL_KEYS])
|
|
867
|
+
];
|
|
868
|
+
}
|
|
865
869
|
function gitMetadataDenyPaths(worktree) {
|
|
866
870
|
const paths = new Set;
|
|
867
871
|
const dotGit = join(worktree, ".git");
|
|
@@ -964,14 +968,20 @@ function implementRunToolPolicy(args) {
|
|
|
964
968
|
return allow;
|
|
965
969
|
};
|
|
966
970
|
}
|
|
967
|
-
function harmonyMcpServer() {
|
|
971
|
+
function harmonyMcpServer(run) {
|
|
968
972
|
const require2 = createRequire2(import.meta.url);
|
|
969
973
|
const cli = join(dirname2(require2.resolve("@gethmy/mcp")), "cli.js");
|
|
970
974
|
return {
|
|
971
975
|
harmony: {
|
|
972
976
|
type: "stdio",
|
|
973
977
|
command: process.execPath,
|
|
974
|
-
args: [cli, "serve"]
|
|
978
|
+
args: [cli, "serve"],
|
|
979
|
+
...run ? {
|
|
980
|
+
env: {
|
|
981
|
+
HARMONY_AGENT_CARD_ID: run.cardId,
|
|
982
|
+
HARMONY_AGENT_SESSION_ID: run.agentSessionId
|
|
983
|
+
}
|
|
984
|
+
} : {}
|
|
975
985
|
}
|
|
976
986
|
};
|
|
977
987
|
}
|
|
@@ -1004,7 +1014,7 @@ function implementRunContainment(args) {
|
|
|
1004
1014
|
canUseTool: implementRunToolPolicy(args),
|
|
1005
1015
|
gateEveryToolCall: true,
|
|
1006
1016
|
settingSources: args.readOnly === true ? [] : ["project"],
|
|
1007
|
-
mcpServers: harmonyMcpServer(),
|
|
1017
|
+
mcpServers: harmonyMcpServer(args.run),
|
|
1008
1018
|
strictMcpConfig: true,
|
|
1009
1019
|
stripEnvKeys: secretEnvKeysToStrip(),
|
|
1010
1020
|
disallowedTools: [
|
|
@@ -1028,7 +1038,7 @@ function implementRunContainmentCliArgs(args) {
|
|
|
1028
1038
|
containment.disallowedTools.join(",")
|
|
1029
1039
|
];
|
|
1030
1040
|
}
|
|
1031
|
-
var SECRET_ENV_PATTERN, KEEP_ENV_KEYS, ProjectSandboxOverrideError, INERT_PROJECT_SETTING_KEYS, WRITE_TOOL_PATHS, READ_TOOL_PATHS, GIT_NO_HOOKS, IMPLEMENT_ALLOWED_DOMAINS;
|
|
1041
|
+
var SECRET_ENV_PATTERN, KEEP_ENV_KEYS, ProjectSandboxOverrideError, INERT_PROJECT_SETTING_KEYS, MODEL_CREDENTIAL_KEYS, WRITE_TOOL_PATHS, READ_TOOL_PATHS, GIT_NO_HOOKS, IMPLEMENT_ALLOWED_DOMAINS;
|
|
1032
1042
|
var init_run_containment = __esm(() => {
|
|
1033
1043
|
init_confine_to_repo();
|
|
1034
1044
|
init_runner();
|
|
@@ -1065,6 +1075,11 @@ var init_run_containment = __esm(() => {
|
|
|
1065
1075
|
"theme",
|
|
1066
1076
|
"verbose"
|
|
1067
1077
|
]);
|
|
1078
|
+
MODEL_CREDENTIAL_KEYS = [
|
|
1079
|
+
"ANTHROPIC_API_KEY",
|
|
1080
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
1081
|
+
"CLAUDE_CODE_OAUTH_TOKEN"
|
|
1082
|
+
];
|
|
1068
1083
|
WRITE_TOOL_PATHS = {
|
|
1069
1084
|
Write: ["file_path"],
|
|
1070
1085
|
Edit: ["file_path"],
|
|
@@ -2989,7 +3004,8 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2989
3004
|
init_log();
|
|
2990
3005
|
|
|
2991
3006
|
// src/oracle.ts
|
|
2992
|
-
import {
|
|
3007
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3008
|
+
import { lstatSync, readFileSync as readFileSync2, realpathSync as realpathSync2, statSync } from "node:fs";
|
|
2993
3009
|
import {
|
|
2994
3010
|
chmod,
|
|
2995
3011
|
lstat,
|
|
@@ -3003,7 +3019,140 @@ import { tmpdir as tmpdir2 } from "node:os";
|
|
|
3003
3019
|
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
3004
3020
|
import { StringDecoder } from "node:string_decoder";
|
|
3005
3021
|
init_log();
|
|
3006
|
-
|
|
3022
|
+
|
|
3023
|
+
// src/repair-sandbox.ts
|
|
3024
|
+
init_log();
|
|
3025
|
+
import { randomUUID } from "node:crypto";
|
|
3026
|
+
import { promisify as promisify2 } from "node:util";
|
|
3027
|
+
var TAG5 = "repair-sandbox";
|
|
3028
|
+
async function dockerExec(argv, opts) {
|
|
3029
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
3030
|
+
return promisify2(execFile2)("docker", argv, {
|
|
3031
|
+
encoding: "utf-8",
|
|
3032
|
+
...opts
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
var MAX_OUTPUT_BUFFER2 = 20971520;
|
|
3036
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
3037
|
+
var SANDBOX_MOUNT = "/repo";
|
|
3038
|
+
var SANDBOX_MEMORY = "4g";
|
|
3039
|
+
var SANDBOX_PIDS = "512";
|
|
3040
|
+
var dockerProbe = null;
|
|
3041
|
+
async function sandboxAvailable() {
|
|
3042
|
+
if (dockerProbe === true)
|
|
3043
|
+
return true;
|
|
3044
|
+
try {
|
|
3045
|
+
await dockerExec(["version", "--format", "{{.Server.Version}}"], {
|
|
3046
|
+
timeout: PROBE_TIMEOUT_MS
|
|
3047
|
+
});
|
|
3048
|
+
dockerProbe = true;
|
|
3049
|
+
} catch {
|
|
3050
|
+
dockerProbe = false;
|
|
3051
|
+
}
|
|
3052
|
+
return dockerProbe;
|
|
3053
|
+
}
|
|
3054
|
+
function __resetSandboxProbe() {
|
|
3055
|
+
dockerProbe = null;
|
|
3056
|
+
}
|
|
3057
|
+
async function removeSandboxContainer(name) {
|
|
3058
|
+
try {
|
|
3059
|
+
await dockerExec(["rm", "--force", name], { timeout: PROBE_TIMEOUT_MS });
|
|
3060
|
+
log.info(TAG5, `removed sandbox container ${name}`);
|
|
3061
|
+
} catch {}
|
|
3062
|
+
}
|
|
3063
|
+
function sandboxRunArgs(image, worktree, command, name, mounts = []) {
|
|
3064
|
+
return [
|
|
3065
|
+
"run",
|
|
3066
|
+
"--rm",
|
|
3067
|
+
...name ? ["--name", name] : [],
|
|
3068
|
+
"--network=none",
|
|
3069
|
+
...sandboxHardeningArgs(worktree, mounts),
|
|
3070
|
+
"--entrypoint",
|
|
3071
|
+
command.cmd,
|
|
3072
|
+
image,
|
|
3073
|
+
...command.args
|
|
3074
|
+
];
|
|
3075
|
+
}
|
|
3076
|
+
function sandboxHardeningArgs(worktree, mounts = []) {
|
|
3077
|
+
return [
|
|
3078
|
+
"--cap-drop=ALL",
|
|
3079
|
+
"--security-opt=no-new-privileges",
|
|
3080
|
+
`--memory=${SANDBOX_MEMORY}`,
|
|
3081
|
+
`--pids-limit=${SANDBOX_PIDS}`,
|
|
3082
|
+
...typeof process.getuid === "function" && typeof process.getgid === "function" ? ["--user", `${process.getuid()}:${process.getgid()}`] : [],
|
|
3083
|
+
"--env",
|
|
3084
|
+
"HOME=/tmp",
|
|
3085
|
+
"--volume",
|
|
3086
|
+
`${worktree}:${SANDBOX_MOUNT}`,
|
|
3087
|
+
...mounts.flatMap((m) => ["--volume", `${m.host}:${m.container}`]),
|
|
3088
|
+
"--workdir",
|
|
3089
|
+
SANDBOX_MOUNT
|
|
3090
|
+
];
|
|
3091
|
+
}
|
|
3092
|
+
var SANDBOX_DEV_SERVER_BIND = "0.0.0.0";
|
|
3093
|
+
function devServerContainerName(port) {
|
|
3094
|
+
return `harmony-devserver-${port}`;
|
|
3095
|
+
}
|
|
3096
|
+
function devServerSandboxArgs(args) {
|
|
3097
|
+
return [
|
|
3098
|
+
"run",
|
|
3099
|
+
"--rm",
|
|
3100
|
+
"--quiet",
|
|
3101
|
+
"--name",
|
|
3102
|
+
args.name,
|
|
3103
|
+
"--publish",
|
|
3104
|
+
`127.0.0.1:${args.port}:${args.port}`,
|
|
3105
|
+
...sandboxHardeningArgs(args.worktree),
|
|
3106
|
+
"--entrypoint",
|
|
3107
|
+
args.command.cmd,
|
|
3108
|
+
args.image,
|
|
3109
|
+
...args.command.args
|
|
3110
|
+
];
|
|
3111
|
+
}
|
|
3112
|
+
async function runInSandbox(args) {
|
|
3113
|
+
const name = `harmony-repair-${randomUUID()}`;
|
|
3114
|
+
const argv = sandboxRunArgs(args.image, args.worktree, args.command, name);
|
|
3115
|
+
log.info(TAG5, `sandbox: ${args.command.cmd} ${args.command.args.join(" ")} (image ${args.image})`);
|
|
3116
|
+
try {
|
|
3117
|
+
const { stdout } = await dockerExec(argv, {
|
|
3118
|
+
timeout: args.timeoutMs,
|
|
3119
|
+
maxBuffer: MAX_OUTPUT_BUFFER2
|
|
3120
|
+
});
|
|
3121
|
+
return { passed: true, output: stdout ?? "" };
|
|
3122
|
+
} catch (err) {
|
|
3123
|
+
const e = err;
|
|
3124
|
+
const output = `${e.stdout ?? ""}${e.stderr ?? ""}`;
|
|
3125
|
+
if (typeof e.code !== "number") {
|
|
3126
|
+
const timedOut = e.killed === true || e.signal != null;
|
|
3127
|
+
if (timedOut)
|
|
3128
|
+
await removeSandboxContainer(name);
|
|
3129
|
+
return {
|
|
3130
|
+
passed: false,
|
|
3131
|
+
output,
|
|
3132
|
+
sandboxError: timedOut ? `the sandbox timed out after ${args.timeoutMs}ms` : `the sandbox did not run: ${e.message ?? "unknown error"}`
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
if (e.code === 125) {
|
|
3136
|
+
return {
|
|
3137
|
+
passed: false,
|
|
3138
|
+
output,
|
|
3139
|
+
sandboxError: `the sandbox could not start (image "${args.image}" missing or unusable)`
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
return { passed: false, output };
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// src/oracle.ts
|
|
3147
|
+
init_run_containment();
|
|
3148
|
+
var TAG6 = "oracle";
|
|
3149
|
+
|
|
3150
|
+
class OracleSandboxConfigError extends Error {
|
|
3151
|
+
constructor(message) {
|
|
3152
|
+
super(message);
|
|
3153
|
+
this.name = "OracleSandboxConfigError";
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3007
3156
|
async function resolveContained(repoPath, relativePath) {
|
|
3008
3157
|
if (isAbsolute3(relativePath)) {
|
|
3009
3158
|
throw new Error(`refusing to place an oracle at an absolute path: ${relativePath}`);
|
|
@@ -3134,12 +3283,24 @@ function gradeOracleRed(summary, exitCode) {
|
|
|
3134
3283
|
function argvPath(path) {
|
|
3135
3284
|
return path.startsWith("./") ? path : `./${path}`;
|
|
3136
3285
|
}
|
|
3137
|
-
async function runHeldOracle(repoPath, oracle,
|
|
3286
|
+
async function runHeldOracle(repoPath, oracle, options = {}) {
|
|
3287
|
+
const heldTestTimeoutMs = options.timeoutMs ?? DEFAULT_METRIC_TIMEOUT_MS;
|
|
3288
|
+
const sandbox = options.sandbox?.image.trim() ? options.sandbox : undefined;
|
|
3289
|
+
if (sandbox && !await sandboxAvailable()) {
|
|
3290
|
+
throw new OracleSandboxConfigError(`the held test is configured to run in "${sandbox.image}" but no container runtime answered — ` + "start Docker or unset verification.sandboxImage to run it on the host");
|
|
3291
|
+
}
|
|
3138
3292
|
const spec = resolveOracleRunnerSpec(oracle);
|
|
3139
3293
|
const reportDir = await mkdtemp(join2(tmpdir2(), reportDirPrefix()));
|
|
3140
3294
|
const reportPath = join2(reportDir, spec.report.file);
|
|
3141
3295
|
try {
|
|
3142
|
-
return await spawnHeldOracle(
|
|
3296
|
+
return await spawnHeldOracle({
|
|
3297
|
+
spec,
|
|
3298
|
+
repoPath,
|
|
3299
|
+
oracle,
|
|
3300
|
+
reportPath,
|
|
3301
|
+
timeoutMs: sandbox ? heldTestTimeoutMs + SANDBOX_STARTUP_GRACE_MS : heldTestTimeoutMs,
|
|
3302
|
+
sandbox
|
|
3303
|
+
});
|
|
3143
3304
|
} finally {
|
|
3144
3305
|
await removeReportDir(reportDir);
|
|
3145
3306
|
}
|
|
@@ -3160,23 +3321,57 @@ function assertUntampered(reportPath) {
|
|
|
3160
3321
|
throw new Error(`the oracle report is not owner-writable (mode ${(file.mode & 511).toString(8)}), so the runner could not have written it last — refusing it`);
|
|
3161
3322
|
}
|
|
3162
3323
|
}
|
|
3324
|
+
function assertConsistentWithExit(summary, exitCode) {
|
|
3325
|
+
if (!summary)
|
|
3326
|
+
return;
|
|
3327
|
+
if (summary.failed > 0 && exitCode === 0) {
|
|
3328
|
+
throw new Error(`the oracle report claims ${summary.failed} of ${summary.total} test(s) failed, but the runner exited 0 — ` + "neither allow-listed runner produces that pair, so the report is not the runner's; refusing it");
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3163
3331
|
async function removeReportDir(reportDir) {
|
|
3164
3332
|
try {
|
|
3165
3333
|
await chmod(reportDir, 448).catch(() => {});
|
|
3166
3334
|
await rm(reportDir, { recursive: true, force: true });
|
|
3167
3335
|
} catch (err) {
|
|
3168
|
-
log.warn(
|
|
3336
|
+
log.warn(TAG6, `Could not remove the oracle report directory ${reportDir} (${err instanceof Error ? err.message : String(err)}) — it may still hold the runner's report, which carries assertion text for vitest. Remove it by hand.`);
|
|
3169
3337
|
}
|
|
3170
3338
|
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3339
|
+
var ORACLE_REPORT_MOUNT = "/harmony-oracle-report";
|
|
3340
|
+
var DOCKER_SELF_FAILURE_CODES = new Set([125, 126, 127]);
|
|
3341
|
+
function heldOracleSandboxArgv(args) {
|
|
3342
|
+
const reportPathInContainer = join2(ORACLE_REPORT_MOUNT, args.reportFile);
|
|
3343
|
+
return {
|
|
3344
|
+
command: "docker",
|
|
3345
|
+
args: sandboxRunArgs(args.image, args.worktree, {
|
|
3346
|
+
cmd: args.runner.command,
|
|
3347
|
+
args: [...args.runner.args, ...args.reportFlags(reportPathInContainer)]
|
|
3348
|
+
}, args.containerName, [{ host: args.reportDir, container: ORACLE_REPORT_MOUNT }]),
|
|
3349
|
+
reportPathInContainer
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
async function spawnHeldOracle(args_) {
|
|
3353
|
+
const { spec, repoPath, oracle, reportPath, timeoutMs, sandbox } = args_;
|
|
3354
|
+
const runner = spec.argv(argvPath(oracle.path));
|
|
3355
|
+
const containerName = sandbox ? `harmony-oracle-${randomUUID2()}` : null;
|
|
3356
|
+
const { command, args } = sandbox ? heldOracleSandboxArgv({
|
|
3357
|
+
image: sandbox.image,
|
|
3358
|
+
worktree: realpathSync2(repoPath),
|
|
3359
|
+
reportDir: realpathSync2(dirname3(reportPath)),
|
|
3360
|
+
reportFile: spec.report.file,
|
|
3361
|
+
runner,
|
|
3362
|
+
reportFlags: spec.report.flags,
|
|
3363
|
+
containerName: containerName ?? undefined
|
|
3364
|
+
}) : {
|
|
3365
|
+
command: runner.command,
|
|
3366
|
+
args: [...runner.args, ...spec.report.flags(reportPath)]
|
|
3367
|
+
};
|
|
3174
3368
|
return await new Promise((settleOk, settleErr) => {
|
|
3175
3369
|
let child;
|
|
3176
3370
|
try {
|
|
3177
3371
|
child = spawnInGroup(command, args, {
|
|
3178
3372
|
cwd: repoPath,
|
|
3179
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3373
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3374
|
+
stripEnvKeys: heldTestEnvKeysToStrip()
|
|
3180
3375
|
});
|
|
3181
3376
|
} catch (err) {
|
|
3182
3377
|
settleErr(err);
|
|
@@ -3208,18 +3403,20 @@ ${output}` : output;
|
|
|
3208
3403
|
return verdict;
|
|
3209
3404
|
};
|
|
3210
3405
|
let report = null;
|
|
3211
|
-
const captureReport = () => {
|
|
3406
|
+
const captureReport = (exitCode) => {
|
|
3212
3407
|
try {
|
|
3213
3408
|
assertUntampered(reportPath);
|
|
3214
|
-
|
|
3409
|
+
const parsed = spec.report.parse(readFileSync2(reportPath, "utf8"));
|
|
3410
|
+
assertConsistentWithExit(parsed, exitCode);
|
|
3411
|
+
report = parsed;
|
|
3215
3412
|
} catch (err) {
|
|
3216
3413
|
report = null;
|
|
3217
3414
|
const message = err instanceof Error ? err.message : String(err);
|
|
3218
3415
|
const absent = err instanceof Error && err.code === "ENOENT";
|
|
3219
3416
|
if (absent) {
|
|
3220
|
-
log.info(
|
|
3417
|
+
log.info(TAG6, `No oracle report at ${reportPath} — no verdict.`);
|
|
3221
3418
|
} else {
|
|
3222
|
-
log.warn(
|
|
3419
|
+
log.warn(TAG6, `Refusing the oracle report at ${reportPath}: ${message} — the gate will report no verdict.`);
|
|
3223
3420
|
}
|
|
3224
3421
|
}
|
|
3225
3422
|
};
|
|
@@ -3265,6 +3462,10 @@ ${output}` : output;
|
|
|
3265
3462
|
settle(new Error(`the held test was terminated by signal ${signal}`));
|
|
3266
3463
|
return;
|
|
3267
3464
|
}
|
|
3465
|
+
if (sandbox && DOCKER_SELF_FAILURE_CODES.has(code)) {
|
|
3466
|
+
settle(new OracleSandboxConfigError(`the held test's sandbox ("${sandbox.image}") exited ${code} without running the runner — ` + "the image is missing, unusable, or does not carry the runner; the motor's local log has docker's own message"));
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3268
3469
|
settle(null, {
|
|
3269
3470
|
exitCode: code,
|
|
3270
3471
|
output: finalOutput(),
|
|
@@ -3275,7 +3476,7 @@ ${output}` : output;
|
|
|
3275
3476
|
child.once("exit", (code, signal) => {
|
|
3276
3477
|
if (killing)
|
|
3277
3478
|
return;
|
|
3278
|
-
captureReport();
|
|
3479
|
+
captureReport(code);
|
|
3279
3480
|
if (timer)
|
|
3280
3481
|
clearTimeout(timer);
|
|
3281
3482
|
reapGroup(pgid);
|
|
@@ -3289,7 +3490,9 @@ ${output}` : output;
|
|
|
3289
3490
|
terminateGroup(child, {
|
|
3290
3491
|
sigintTimeoutMs: ORACLE_SIGINT_GRACE_MS,
|
|
3291
3492
|
sigtermTimeoutMs: ORACLE_SIGTERM_GRACE_MS
|
|
3292
|
-
}).catch(() => {}).then(() => {
|
|
3493
|
+
}).catch(() => {}).then(async () => {
|
|
3494
|
+
if (containerName)
|
|
3495
|
+
await removeSandboxContainer(containerName);
|
|
3293
3496
|
settle(new Error(`the held test did not finish within ${timeoutMs}ms`));
|
|
3294
3497
|
});
|
|
3295
3498
|
}, timeoutMs);
|
|
@@ -3297,7 +3500,7 @@ ${output}` : output;
|
|
|
3297
3500
|
}
|
|
3298
3501
|
|
|
3299
3502
|
// src/oracle-collector.ts
|
|
3300
|
-
var
|
|
3503
|
+
var TAG7 = "oracle-collector";
|
|
3301
3504
|
|
|
3302
3505
|
class HeldOracleCollector {
|
|
3303
3506
|
deps;
|
|
@@ -3308,7 +3511,7 @@ class HeldOracleCollector {
|
|
|
3308
3511
|
const stageId = this.oracleStageId(context);
|
|
3309
3512
|
if (!stageId) {
|
|
3310
3513
|
const reason = `No stage downstream of ${context.stageId} declares an \`oracle_passed\` gate, ` + "so there is no held test for this gate to grade. A red gate needs a later stage that runs the same test green.";
|
|
3311
|
-
log.warn(
|
|
3514
|
+
log.warn(TAG7, `${reason} — blocked (config)`);
|
|
3312
3515
|
return {
|
|
3313
3516
|
result: "blocked",
|
|
3314
3517
|
structured: { reason, ...GATE_CONFIG_ERROR_MARK }
|
|
@@ -3316,7 +3519,7 @@ class HeldOracleCollector {
|
|
|
3316
3519
|
}
|
|
3317
3520
|
const oracle = await this.deps.fetchOracle(context.cardId, stageId, this.deps.sessionId);
|
|
3318
3521
|
if (!oracle) {
|
|
3319
|
-
log.info(
|
|
3522
|
+
log.info(TAG7, `No oracle held for stage ${stageId} — blocked`);
|
|
3320
3523
|
return {
|
|
3321
3524
|
result: "blocked",
|
|
3322
3525
|
structured: {
|
|
@@ -3333,13 +3536,13 @@ class HeldOracleCollector {
|
|
|
3333
3536
|
};
|
|
3334
3537
|
await this.deps.place(this.deps.repoPath, oracle);
|
|
3335
3538
|
try {
|
|
3336
|
-
const { exitCode, output, report } = await this.deps.run(this.deps.repoPath, oracle);
|
|
3539
|
+
const { exitCode, output, report } = await this.deps.run(this.deps.repoPath, oracle, { sandbox: this.deps.sandbox });
|
|
3337
3540
|
const logLine = `Oracle run for ${oracle.path} exited ${exitCode}:
|
|
3338
3541
|
${output}`;
|
|
3339
3542
|
if (exitCode === 0) {
|
|
3340
|
-
log.info(
|
|
3543
|
+
log.info(TAG7, logLine);
|
|
3341
3544
|
} else {
|
|
3342
|
-
log.warn(
|
|
3545
|
+
log.warn(TAG7, logLine);
|
|
3343
3546
|
}
|
|
3344
3547
|
return this.verdict({
|
|
3345
3548
|
oracle,
|
|
@@ -3350,12 +3553,14 @@ ${output}`;
|
|
|
3350
3553
|
});
|
|
3351
3554
|
} catch (err) {
|
|
3352
3555
|
const message = errText(err);
|
|
3353
|
-
|
|
3556
|
+
const configError2 = err instanceof OracleSandboxConfigError;
|
|
3557
|
+
log.warn(TAG7, `Oracle run threw: ${message} — blocked${configError2 ? " (config)" : ""}`);
|
|
3354
3558
|
return {
|
|
3355
3559
|
result: "blocked",
|
|
3356
3560
|
structured: {
|
|
3357
3561
|
oracle: { path: oracle.path, ...identity },
|
|
3358
|
-
error: message
|
|
3562
|
+
error: message,
|
|
3563
|
+
...configError2 ? { reason: message, ...GATE_CONFIG_ERROR_MARK } : {}
|
|
3359
3564
|
}
|
|
3360
3565
|
};
|
|
3361
3566
|
} finally {
|
|
@@ -3367,13 +3572,13 @@ ${output}`;
|
|
|
3367
3572
|
await this.deps.remove(this.deps.repoPath, oracle);
|
|
3368
3573
|
return;
|
|
3369
3574
|
} catch (err) {
|
|
3370
|
-
log.warn(
|
|
3575
|
+
log.warn(TAG7, `Removing the held test at ${oracle.path} failed (${errText(err)}) — retrying once`);
|
|
3371
3576
|
}
|
|
3372
3577
|
try {
|
|
3373
3578
|
await this.deps.remove(this.deps.repoPath, oracle);
|
|
3374
|
-
log.info(
|
|
3579
|
+
log.info(TAG7, `Held test at ${oracle.path} removed on the second attempt`);
|
|
3375
3580
|
} catch (err) {
|
|
3376
|
-
log.error(
|
|
3581
|
+
log.error(TAG7, `HELD TEST NOT REMOVED: ${oracle.path} is still in ${this.deps.repoPath} after two attempts (${errText(err)}). ` + "It will be auto-committed by the completion path if it is left there — delete it by hand and check whether it reached a commit.");
|
|
3377
3582
|
}
|
|
3378
3583
|
}
|
|
3379
3584
|
}
|
|
@@ -3416,7 +3621,7 @@ class OracleRedCollector extends HeldOracleCollector {
|
|
|
3416
3621
|
const graded = gradeOracleRed(report, exitCode);
|
|
3417
3622
|
const base = { exitCode, path: oracle.path, ...identity };
|
|
3418
3623
|
if (graded.outcome === "no_verdict") {
|
|
3419
|
-
log.warn(
|
|
3624
|
+
log.warn(TAG7, `Red gate for ${oracle.path}: ${graded.reason} — blocked`);
|
|
3420
3625
|
return {
|
|
3421
3626
|
result: "blocked",
|
|
3422
3627
|
structured: { oracle: base, reason: graded.reason }
|
|
@@ -3448,7 +3653,7 @@ init_log();
|
|
|
3448
3653
|
init_run_containment();
|
|
3449
3654
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3450
3655
|
import { existsSync } from "node:fs";
|
|
3451
|
-
var
|
|
3656
|
+
var TAG8 = "pm";
|
|
3452
3657
|
var cached = null;
|
|
3453
3658
|
function detectPackageManager() {
|
|
3454
3659
|
if (cached)
|
|
@@ -3470,7 +3675,7 @@ function detectPackageManager() {
|
|
|
3470
3675
|
} else {
|
|
3471
3676
|
cached = "npm";
|
|
3472
3677
|
}
|
|
3473
|
-
log.info(
|
|
3678
|
+
log.info(TAG8, `Detected package manager: ${cached}`);
|
|
3474
3679
|
return cached;
|
|
3475
3680
|
}
|
|
3476
3681
|
function installCommand(ignoreScripts = false) {
|
|
@@ -3499,7 +3704,7 @@ function spawnRunArgs(script, ...extra) {
|
|
|
3499
3704
|
init_log();
|
|
3500
3705
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3501
3706
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
3502
|
-
var
|
|
3707
|
+
var TAG9 = "project-type";
|
|
3503
3708
|
var _cache = new Map;
|
|
3504
3709
|
function _resetCache() {
|
|
3505
3710
|
_cache.clear();
|
|
@@ -3510,7 +3715,7 @@ function detect(dir) {
|
|
|
3510
3715
|
return cached2;
|
|
3511
3716
|
const result = detectUncached(dir);
|
|
3512
3717
|
_cache.set(dir, result);
|
|
3513
|
-
log.info(
|
|
3718
|
+
log.info(TAG9, `Detected project type in ${dir}: ${result.kind}`);
|
|
3514
3719
|
return result;
|
|
3515
3720
|
}
|
|
3516
3721
|
function detectUncached(dir) {
|
|
@@ -3604,13 +3809,13 @@ function hasNodeTestScript(dir) {
|
|
|
3604
3809
|
const pkg = JSON.parse(readFileSync3(`${dir}/package.json`, "utf-8"));
|
|
3605
3810
|
script = pkg.scripts?.test;
|
|
3606
3811
|
} catch (err) {
|
|
3607
|
-
log.warn(
|
|
3812
|
+
log.warn(TAG9, `Could not read package.json in ${dir}: ${err instanceof Error ? err.message : err}`);
|
|
3608
3813
|
return false;
|
|
3609
3814
|
}
|
|
3610
3815
|
if (typeof script !== "string" || script.trim().length === 0)
|
|
3611
3816
|
return false;
|
|
3612
3817
|
if (NPM_PLACEHOLDER_TEST.test(script)) {
|
|
3613
|
-
log.info(
|
|
3818
|
+
log.info(TAG9, `package.json 'test' is the npm placeholder — skipping tests`);
|
|
3614
3819
|
return false;
|
|
3615
3820
|
}
|
|
3616
3821
|
return true;
|
|
@@ -3621,7 +3826,7 @@ function firstNodeScript(dir, candidates) {
|
|
|
3621
3826
|
const pkg = JSON.parse(readFileSync3(`${dir}/package.json`, "utf-8"));
|
|
3622
3827
|
scripts = pkg.scripts ?? {};
|
|
3623
3828
|
} catch (err) {
|
|
3624
|
-
log.warn(
|
|
3829
|
+
log.warn(TAG9, `Could not read package.json in ${dir}: ${err instanceof Error ? err.message : err}`);
|
|
3625
3830
|
return null;
|
|
3626
3831
|
}
|
|
3627
3832
|
for (const name of candidates) {
|
|
@@ -3640,7 +3845,7 @@ function xcodeBuildCommand(pt) {
|
|
|
3640
3845
|
return null;
|
|
3641
3846
|
const scheme = resolveXcodeScheme(pt);
|
|
3642
3847
|
if (!scheme) {
|
|
3643
|
-
log.warn(
|
|
3848
|
+
log.warn(TAG9, "Could not resolve an Xcode scheme — skipping build (best-effort)");
|
|
3644
3849
|
return null;
|
|
3645
3850
|
}
|
|
3646
3851
|
const containerFlag = pt.xcodeIsWorkspace ? "-workspace" : "-project";
|
|
@@ -3668,108 +3873,11 @@ function resolveXcodeScheme(pt) {
|
|
|
3668
3873
|
const schemes = pt.xcodeIsWorkspace ? parsed.workspace?.schemes ?? [] : parsed.project?.schemes ?? [];
|
|
3669
3874
|
return schemes[0] ?? null;
|
|
3670
3875
|
} catch (err) {
|
|
3671
|
-
log.warn(
|
|
3876
|
+
log.warn(TAG9, `xcodebuild -list failed: ${err instanceof Error ? err.message : err}`);
|
|
3672
3877
|
return null;
|
|
3673
3878
|
}
|
|
3674
3879
|
}
|
|
3675
3880
|
|
|
3676
|
-
// src/repair-sandbox.ts
|
|
3677
|
-
init_log();
|
|
3678
|
-
import { randomUUID } from "node:crypto";
|
|
3679
|
-
import { promisify as promisify2 } from "node:util";
|
|
3680
|
-
var TAG9 = "repair-sandbox";
|
|
3681
|
-
async function dockerExec(argv, opts) {
|
|
3682
|
-
const { execFile: execFile2 } = await import("node:child_process");
|
|
3683
|
-
return promisify2(execFile2)("docker", argv, {
|
|
3684
|
-
encoding: "utf-8",
|
|
3685
|
-
...opts
|
|
3686
|
-
});
|
|
3687
|
-
}
|
|
3688
|
-
var MAX_OUTPUT_BUFFER2 = 20971520;
|
|
3689
|
-
var PROBE_TIMEOUT_MS = 1e4;
|
|
3690
|
-
var SANDBOX_MOUNT = "/repo";
|
|
3691
|
-
var SANDBOX_MEMORY = "4g";
|
|
3692
|
-
var SANDBOX_PIDS = "512";
|
|
3693
|
-
var dockerProbe = null;
|
|
3694
|
-
async function sandboxAvailable() {
|
|
3695
|
-
if (dockerProbe === true)
|
|
3696
|
-
return true;
|
|
3697
|
-
try {
|
|
3698
|
-
await dockerExec(["version", "--format", "{{.Server.Version}}"], {
|
|
3699
|
-
timeout: PROBE_TIMEOUT_MS
|
|
3700
|
-
});
|
|
3701
|
-
dockerProbe = true;
|
|
3702
|
-
} catch {
|
|
3703
|
-
dockerProbe = false;
|
|
3704
|
-
}
|
|
3705
|
-
return dockerProbe;
|
|
3706
|
-
}
|
|
3707
|
-
function __resetSandboxProbe() {
|
|
3708
|
-
dockerProbe = null;
|
|
3709
|
-
}
|
|
3710
|
-
async function removeContainer(name) {
|
|
3711
|
-
try {
|
|
3712
|
-
await dockerExec(["rm", "--force", name], { timeout: PROBE_TIMEOUT_MS });
|
|
3713
|
-
log.warn(TAG9, `removed the timed-out sandbox container ${name}`);
|
|
3714
|
-
} catch {}
|
|
3715
|
-
}
|
|
3716
|
-
function sandboxRunArgs(image, worktree, command, name) {
|
|
3717
|
-
return [
|
|
3718
|
-
"run",
|
|
3719
|
-
"--rm",
|
|
3720
|
-
...name ? ["--name", name] : [],
|
|
3721
|
-
"--network=none",
|
|
3722
|
-
"--cap-drop=ALL",
|
|
3723
|
-
"--security-opt=no-new-privileges",
|
|
3724
|
-
`--memory=${SANDBOX_MEMORY}`,
|
|
3725
|
-
`--pids-limit=${SANDBOX_PIDS}`,
|
|
3726
|
-
...typeof process.getuid === "function" && typeof process.getgid === "function" ? ["--user", `${process.getuid()}:${process.getgid()}`] : [],
|
|
3727
|
-
"--env",
|
|
3728
|
-
"HOME=/tmp",
|
|
3729
|
-
"--volume",
|
|
3730
|
-
`${worktree}:${SANDBOX_MOUNT}`,
|
|
3731
|
-
"--workdir",
|
|
3732
|
-
SANDBOX_MOUNT,
|
|
3733
|
-
"--entrypoint",
|
|
3734
|
-
command.cmd,
|
|
3735
|
-
image,
|
|
3736
|
-
...command.args
|
|
3737
|
-
];
|
|
3738
|
-
}
|
|
3739
|
-
async function runInSandbox(args) {
|
|
3740
|
-
const name = `harmony-repair-${randomUUID()}`;
|
|
3741
|
-
const argv = sandboxRunArgs(args.image, args.worktree, args.command, name);
|
|
3742
|
-
log.info(TAG9, `sandbox: ${args.command.cmd} ${args.command.args.join(" ")} (image ${args.image})`);
|
|
3743
|
-
try {
|
|
3744
|
-
const { stdout } = await dockerExec(argv, {
|
|
3745
|
-
timeout: args.timeoutMs,
|
|
3746
|
-
maxBuffer: MAX_OUTPUT_BUFFER2
|
|
3747
|
-
});
|
|
3748
|
-
return { passed: true, output: stdout ?? "" };
|
|
3749
|
-
} catch (err) {
|
|
3750
|
-
const e = err;
|
|
3751
|
-
const output = `${e.stdout ?? ""}${e.stderr ?? ""}`;
|
|
3752
|
-
if (typeof e.code !== "number") {
|
|
3753
|
-
const timedOut = e.killed === true || e.signal != null;
|
|
3754
|
-
if (timedOut)
|
|
3755
|
-
await removeContainer(name);
|
|
3756
|
-
return {
|
|
3757
|
-
passed: false,
|
|
3758
|
-
output,
|
|
3759
|
-
sandboxError: timedOut ? `the sandbox timed out after ${args.timeoutMs}ms` : `the sandbox did not run: ${e.message ?? "unknown error"}`
|
|
3760
|
-
};
|
|
3761
|
-
}
|
|
3762
|
-
if (e.code === 125) {
|
|
3763
|
-
return {
|
|
3764
|
-
passed: false,
|
|
3765
|
-
output,
|
|
3766
|
-
sandboxError: `the sandbox could not start (image "${args.image}" missing or unusable)`
|
|
3767
|
-
};
|
|
3768
|
-
}
|
|
3769
|
-
return { passed: false, output };
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
|
|
3773
3881
|
// src/revert-guard.ts
|
|
3774
3882
|
init_log();
|
|
3775
3883
|
init_run_containment();
|
|
@@ -3987,9 +4095,16 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
3987
4095
|
}
|
|
3988
4096
|
const port = config.verification.devServerBasePort + workerId;
|
|
3989
4097
|
let devServer = null;
|
|
4098
|
+
const launch = devServerLaunch({
|
|
4099
|
+
worktreePath,
|
|
4100
|
+
port,
|
|
4101
|
+
sandbox: verificationSandbox(config)
|
|
4102
|
+
});
|
|
3990
4103
|
try {
|
|
3991
|
-
|
|
3992
|
-
|
|
4104
|
+
if (launch.containerName) {
|
|
4105
|
+
await removeSandboxContainer(launch.containerName);
|
|
4106
|
+
}
|
|
4107
|
+
devServer = spawn2(launch.cmd, launch.args, {
|
|
3993
4108
|
cwd: worktreePath,
|
|
3994
4109
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3995
4110
|
env: containedEnv()
|
|
@@ -4052,6 +4167,9 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
4052
4167
|
if (devServer && !devServer.killed) {
|
|
4053
4168
|
devServer.kill("SIGTERM");
|
|
4054
4169
|
}
|
|
4170
|
+
if (launch.containerName) {
|
|
4171
|
+
await removeSandboxContainer(launch.containerName);
|
|
4172
|
+
}
|
|
4055
4173
|
}
|
|
4056
4174
|
}
|
|
4057
4175
|
function attemptAutoFix(worktreePath, config, errors) {
|
|
@@ -4190,6 +4308,24 @@ class DevServerReadinessError extends Error {
|
|
|
4190
4308
|
this.name = "DevServerReadinessError";
|
|
4191
4309
|
}
|
|
4192
4310
|
}
|
|
4311
|
+
function devServerLaunch(args) {
|
|
4312
|
+
const [cmd, runArgs] = spawnRunArgs("dev", "--port", String(args.port), ...args.sandbox ? ["--host", SANDBOX_DEV_SERVER_BIND] : []);
|
|
4313
|
+
if (!args.sandbox)
|
|
4314
|
+
return { cmd, args: runArgs };
|
|
4315
|
+
const containerName = devServerContainerName(args.port);
|
|
4316
|
+
return {
|
|
4317
|
+
cmd: "docker",
|
|
4318
|
+
args: devServerSandboxArgs({
|
|
4319
|
+
image: args.sandbox.image,
|
|
4320
|
+
worktree: args.worktreePath,
|
|
4321
|
+
command: { cmd, args: runArgs },
|
|
4322
|
+
port: args.port,
|
|
4323
|
+
name: containerName
|
|
4324
|
+
}),
|
|
4325
|
+
containerName
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
var DEV_SERVER_READY = /\bready\b/i;
|
|
4193
4329
|
function waitForDevServer(proc, timeout) {
|
|
4194
4330
|
return new Promise((resolve3, reject) => {
|
|
4195
4331
|
let settled = false;
|
|
@@ -4219,7 +4355,7 @@ function waitForDevServer(proc, timeout) {
|
|
|
4219
4355
|
}, timeout);
|
|
4220
4356
|
const onData = (data) => {
|
|
4221
4357
|
const text = data.toString();
|
|
4222
|
-
if (
|
|
4358
|
+
if (DEV_SERVER_READY.test(text) || text.includes("localhost") || text.includes("Local:")) {
|
|
4223
4359
|
settleResolve();
|
|
4224
4360
|
}
|
|
4225
4361
|
};
|
|
@@ -5181,7 +5317,7 @@ function branchAheadOfItsRemote(branchName, repoRoot = resolveRepoRoot()) {
|
|
|
5181
5317
|
return false;
|
|
5182
5318
|
}
|
|
5183
5319
|
}
|
|
5184
|
-
async function rescueUnpushedBranch(client, cardId, branchName, repoRoot = resolveRepoRoot()) {
|
|
5320
|
+
async function rescueUnpushedBranch(client, cardId, branchName, repoRoot = resolveRepoRoot(), agentSessionId) {
|
|
5185
5321
|
const { getBranchWebUrl: getBranchWebUrl2, pushBranch: pushBranch2 } = await Promise.resolve().then(() => (init_git_pr(), exports_git_pr));
|
|
5186
5322
|
try {
|
|
5187
5323
|
pushBranch2(branchName, repoRoot);
|
|
@@ -5194,13 +5330,16 @@ async function rescueUnpushedBranch(client, cardId, branchName, repoRoot = resol
|
|
|
5194
5330
|
const url = getBranchWebUrl2(branchName, repoRoot);
|
|
5195
5331
|
const recover = url ? `View it at ${url} or recover locally: \`git fetch && git checkout ${branchName}\`` : `Recover it locally: \`git fetch && git checkout ${branchName}\``;
|
|
5196
5332
|
const body = `⚠ Run ended before completion. Committed work was push-rescued to ` + `\`origin/${branchName}\` so it isn't lost. ${recover}`;
|
|
5197
|
-
await client.addComment(cardId, body, {
|
|
5333
|
+
await client.addComment(cardId, body, {
|
|
5334
|
+
commentType: "message",
|
|
5335
|
+
...agentSessionId ? { agentSessionId } : {}
|
|
5336
|
+
});
|
|
5198
5337
|
} catch (err) {
|
|
5199
5338
|
log.warn(TAG16, `push-rescue comment failed for ${branchName} (work is still safe on origin): ${err instanceof Error ? err.message : err}`);
|
|
5200
5339
|
}
|
|
5201
5340
|
return true;
|
|
5202
5341
|
}
|
|
5203
|
-
async function teardownWorktree(client, cardId, worktreePath, branchName) {
|
|
5342
|
+
async function teardownWorktree(client, cardId, worktreePath, branchName, agentSessionId) {
|
|
5204
5343
|
let skipBranchDelete = false;
|
|
5205
5344
|
if (branchName && cardId) {
|
|
5206
5345
|
let repoRoot;
|
|
@@ -5211,7 +5350,7 @@ async function teardownWorktree(client, cardId, worktreePath, branchName) {
|
|
|
5211
5350
|
return;
|
|
5212
5351
|
}
|
|
5213
5352
|
if (branchAheadOfItsRemote(branchName, repoRoot)) {
|
|
5214
|
-
const ok = await rescueUnpushedBranch(client, cardId, branchName, repoRoot);
|
|
5353
|
+
const ok = await rescueUnpushedBranch(client, cardId, branchName, repoRoot, agentSessionId);
|
|
5215
5354
|
if (!ok) {
|
|
5216
5355
|
skipBranchDelete = true;
|
|
5217
5356
|
log.error(TAG16, `Keeping local branch ${branchName} (push-rescue failed) to avoid orphaning its commit`);
|
|
@@ -5276,6 +5415,7 @@ export {
|
|
|
5276
5415
|
reportFindings,
|
|
5277
5416
|
renameRemoteBranch,
|
|
5278
5417
|
removeWorktreeHoldingBranch,
|
|
5418
|
+
removeSandboxContainer,
|
|
5279
5419
|
remove,
|
|
5280
5420
|
remoteBranchExists,
|
|
5281
5421
|
relayAgentEvent,
|
|
@@ -5307,6 +5447,8 @@ export {
|
|
|
5307
5447
|
implementRunContainmentCliArgs,
|
|
5308
5448
|
implementRunContainment,
|
|
5309
5449
|
hostPersistencePaths,
|
|
5450
|
+
heldTestEnvKeysToStrip,
|
|
5451
|
+
heldOracleSandboxArgv,
|
|
5310
5452
|
harmonyMcpServer,
|
|
5311
5453
|
gradeOracleRed,
|
|
5312
5454
|
gitMetadataDenyPaths,
|
|
@@ -5329,6 +5471,9 @@ export {
|
|
|
5329
5471
|
extractCiReviewRequestedSha,
|
|
5330
5472
|
extractAzurePrId,
|
|
5331
5473
|
envKeysDroppedByLaunch,
|
|
5474
|
+
devServerSandboxArgs,
|
|
5475
|
+
devServerLaunch,
|
|
5476
|
+
devServerContainerName,
|
|
5332
5477
|
detectPackageManager,
|
|
5333
5478
|
detectGitProvider,
|
|
5334
5479
|
detect,
|
|
@@ -5374,8 +5519,10 @@ export {
|
|
|
5374
5519
|
SDK_ALLOWED_TOOLS,
|
|
5375
5520
|
SANDBOX_STARTUP_GRACE_MS,
|
|
5376
5521
|
SANDBOX_MOUNT,
|
|
5522
|
+
SANDBOX_DEV_SERVER_BIND,
|
|
5377
5523
|
ReviewPassedCollector,
|
|
5378
5524
|
ProjectSandboxOverrideError,
|
|
5525
|
+
OracleSandboxConfigError,
|
|
5379
5526
|
OracleRedCollector,
|
|
5380
5527
|
OracleCollector,
|
|
5381
5528
|
ORACLE_RUNNER_HINTS,
|