@seanyao/roll 4.721.1 → 4.721.2
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/CHANGELOG.md +5 -0
- package/dist/roll.mjs +147 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/roll.mjs
CHANGED
|
@@ -14639,6 +14639,19 @@ function timeoutTeardownCommands(ctx) {
|
|
|
14639
14639
|
{ kind: "release_lock" }
|
|
14640
14640
|
];
|
|
14641
14641
|
}
|
|
14642
|
+
function abortedTeardownCommands(ctx) {
|
|
14643
|
+
return [
|
|
14644
|
+
{ kind: "kill_agent", graceSec: WATCHDOG_KILL_GRACE_SEC },
|
|
14645
|
+
{ kind: "measure_worktree" },
|
|
14646
|
+
{ kind: "emit_event", event: cycleEndEvent(ctx, "aborted") },
|
|
14647
|
+
{ kind: "append_run", status: "aborted", outcome: mapV2Status("aborted"), cycleId: ctx.cycleId },
|
|
14648
|
+
{
|
|
14649
|
+
kind: "append_alert",
|
|
14650
|
+
message: `cycle ${ctx.cycleId}: builder child process lost (killed out-of-band) \u2014 cycle aborted; worktree preserved`
|
|
14651
|
+
},
|
|
14652
|
+
{ kind: "release_lock" }
|
|
14653
|
+
];
|
|
14654
|
+
}
|
|
14642
14655
|
function cycleTimeoutVerdict(input) {
|
|
14643
14656
|
const wall = input.wallLimitSec ?? CYCLE_WALL_TIMEOUT_SEC;
|
|
14644
14657
|
const idle = input.noProgressLimitSec ?? CYCLE_NO_PROGRESS_SEC;
|
|
@@ -14833,6 +14846,12 @@ function cycleStep(state, event) {
|
|
|
14833
14846
|
{ kind: "cleanup_worktree", branch: state.ctx.branch }
|
|
14834
14847
|
]);
|
|
14835
14848
|
case "agent_exited": {
|
|
14849
|
+
if (event.lost === true) {
|
|
14850
|
+
return {
|
|
14851
|
+
state: { ...state, phase: "execute", terminal: "aborted", done: true, ctx: { ...state.ctx, agentExitCode: event.exit, agentTimedOut: event.timedOut } },
|
|
14852
|
+
commands: abortedTeardownCommands(terminalCtx(state))
|
|
14853
|
+
};
|
|
14854
|
+
}
|
|
14836
14855
|
const plan = retryPlan({ attempt: state.attempt, exit: event.exit, timedOut: event.timedOut });
|
|
14837
14856
|
if (plan.action === "abort_timeout") {
|
|
14838
14857
|
return {
|
|
@@ -246187,6 +246206,10 @@ function spawnAndWait(bin, args, opts, pty = false, profile) {
|
|
|
246187
246206
|
stderr += d.toString("utf8");
|
|
246188
246207
|
});
|
|
246189
246208
|
liveAgents.add(child);
|
|
246209
|
+
try {
|
|
246210
|
+
opts.onSpawn?.(child);
|
|
246211
|
+
} catch {
|
|
246212
|
+
}
|
|
246190
246213
|
let settled = false;
|
|
246191
246214
|
let exitDrainFallback;
|
|
246192
246215
|
const settle = (result2) => {
|
|
@@ -284694,6 +284717,68 @@ function startStallDetector(opts) {
|
|
|
284694
284717
|
}
|
|
284695
284718
|
};
|
|
284696
284719
|
}
|
|
284720
|
+
var LIVENESS_POLL_MS = 5e3;
|
|
284721
|
+
var LIVENESS_CONFIRM_TICKS = 2;
|
|
284722
|
+
function defaultIsAlive(pid) {
|
|
284723
|
+
try {
|
|
284724
|
+
process.kill(pid, 0);
|
|
284725
|
+
return true;
|
|
284726
|
+
} catch (e) {
|
|
284727
|
+
return e.code === "EPERM";
|
|
284728
|
+
}
|
|
284729
|
+
}
|
|
284730
|
+
function startBuilderLivenessProbe(opts) {
|
|
284731
|
+
const { cycleId, agent, appendEvent: appendEvent3 } = opts;
|
|
284732
|
+
const isAlive = opts.isAlive ?? defaultIsAlive;
|
|
284733
|
+
const kill = opts.kill ?? (() => killLiveAgents("SIGKILL"));
|
|
284734
|
+
const pollMs = opts.pollMs ?? (Number((process.env["ROLL_LIVENESS_POLL_MS"] ?? "").trim()) || LIVENESS_POLL_MS);
|
|
284735
|
+
const confirmTicks = opts.confirmTicks ?? LIVENESS_CONFIRM_TICKS;
|
|
284736
|
+
let lost = false;
|
|
284737
|
+
let deadStreak = 0;
|
|
284738
|
+
const tick = () => {
|
|
284739
|
+
if (lost || !opts.spawnPending())
|
|
284740
|
+
return;
|
|
284741
|
+
const pid = opts.pid();
|
|
284742
|
+
if (pid === void 0) {
|
|
284743
|
+
deadStreak = 0;
|
|
284744
|
+
return;
|
|
284745
|
+
}
|
|
284746
|
+
let alive = true;
|
|
284747
|
+
try {
|
|
284748
|
+
alive = isAlive(pid);
|
|
284749
|
+
} catch {
|
|
284750
|
+
}
|
|
284751
|
+
if (alive) {
|
|
284752
|
+
deadStreak = 0;
|
|
284753
|
+
return;
|
|
284754
|
+
}
|
|
284755
|
+
deadStreak += 1;
|
|
284756
|
+
if (deadStreak < confirmTicks)
|
|
284757
|
+
return;
|
|
284758
|
+
lost = true;
|
|
284759
|
+
clearInterval(timer);
|
|
284760
|
+
try {
|
|
284761
|
+
appendEvent3({ type: "cycle:agent_lost", cycleId, agent, pid, ts: Date.now() });
|
|
284762
|
+
} catch {
|
|
284763
|
+
}
|
|
284764
|
+
try {
|
|
284765
|
+
kill();
|
|
284766
|
+
} catch {
|
|
284767
|
+
}
|
|
284768
|
+
try {
|
|
284769
|
+
opts.onLost?.({ pid });
|
|
284770
|
+
} catch {
|
|
284771
|
+
}
|
|
284772
|
+
};
|
|
284773
|
+
const timer = setInterval(tick, pollMs);
|
|
284774
|
+
timer.unref?.();
|
|
284775
|
+
return {
|
|
284776
|
+
stop: () => {
|
|
284777
|
+
clearInterval(timer);
|
|
284778
|
+
return { lost };
|
|
284779
|
+
}
|
|
284780
|
+
};
|
|
284781
|
+
}
|
|
284697
284782
|
function createCaptureMarkerSink(runDir, capture) {
|
|
284698
284783
|
let buf = "";
|
|
284699
284784
|
const pending = [];
|
|
@@ -284817,6 +284902,24 @@ ${skillBodyForSpawn}` : skillBodyForSpawn;
|
|
|
284817
284902
|
let timeoutFired = null;
|
|
284818
284903
|
let activeMainLeak = { detected: false, files: [] };
|
|
284819
284904
|
let mainLeakWatchdog;
|
|
284905
|
+
let spawnedPid;
|
|
284906
|
+
let spawnSettled = false;
|
|
284907
|
+
let agentLost = false;
|
|
284908
|
+
let resolveLostRace;
|
|
284909
|
+
const lostRace = new Promise((resolve17) => {
|
|
284910
|
+
resolveLostRace = () => resolve17({ stdout: "", stderr: "", exitCode: 137, timedOut: false });
|
|
284911
|
+
});
|
|
284912
|
+
const livenessProbe = startBuilderLivenessProbe({
|
|
284913
|
+
cycleId: ctx.cycleId ?? "",
|
|
284914
|
+
agent: cmd.agent,
|
|
284915
|
+
pid: () => spawnedPid,
|
|
284916
|
+
spawnPending: () => !spawnSettled,
|
|
284917
|
+
appendEvent: (ev) => ports.events.appendEvent(ports.paths.eventsPath, ev),
|
|
284918
|
+
onLost: () => {
|
|
284919
|
+
agentLost = true;
|
|
284920
|
+
resolveLostRace?.();
|
|
284921
|
+
}
|
|
284922
|
+
});
|
|
284820
284923
|
try {
|
|
284821
284924
|
appendWriteProtectionEvent(ports, applyMainCheckoutWriteProtection({
|
|
284822
284925
|
repoCwd: ports.repoCwd,
|
|
@@ -284825,37 +284928,47 @@ ${skillBodyForSpawn}` : skillBodyForSpawn;
|
|
|
284825
284928
|
nowMs: () => eventTs2(ports)
|
|
284826
284929
|
}));
|
|
284827
284930
|
mainLeakWatchdog = startMainCheckoutLeakWatchdog(ports, ctx);
|
|
284828
|
-
res = await
|
|
284829
|
-
|
|
284830
|
-
|
|
284831
|
-
|
|
284832
|
-
|
|
284833
|
-
|
|
284834
|
-
|
|
284835
|
-
|
|
284836
|
-
|
|
284837
|
-
|
|
284838
|
-
|
|
284839
|
-
|
|
284840
|
-
|
|
284841
|
-
|
|
284842
|
-
|
|
284843
|
-
|
|
284844
|
-
|
|
284845
|
-
|
|
284846
|
-
|
|
284847
|
-
|
|
284848
|
-
|
|
284849
|
-
|
|
284850
|
-
|
|
284851
|
-
|
|
284852
|
-
|
|
284853
|
-
|
|
284931
|
+
res = await Promise.race([
|
|
284932
|
+
ports.agentSpawn(cmd.agent, {
|
|
284933
|
+
purpose: "builder",
|
|
284934
|
+
// E4: the builder runs in the submodule cycle worktree for a submodule
|
|
284935
|
+
// story (execCwd); its git env + writable roots target the submodule's
|
|
284936
|
+
// own repo/object store so its commits actually land there.
|
|
284937
|
+
cwd: execCwd,
|
|
284938
|
+
skillBody: finalSkillBody,
|
|
284939
|
+
...ctx.evidenceRunDir !== void 0 ? { runDir: ctx.evidenceRunDir } : {},
|
|
284940
|
+
writableRoots: submoduleAgentWritableRoots(ports.repoCwd, execRepoCwd, ports.paths.alertsPath),
|
|
284941
|
+
...ctx.model !== void 0 && ctx.model !== "" ? { model: ctx.model } : {},
|
|
284942
|
+
env: {
|
|
284943
|
+
...process.env,
|
|
284944
|
+
ROLL_LOOP_ALERT: ports.paths.alertsPath,
|
|
284945
|
+
...worktreeGitEnv(execCwd, ports.repoCwd),
|
|
284946
|
+
...agentSpawnEnvironment(cmd.agent)
|
|
284947
|
+
},
|
|
284948
|
+
// FIX-204B: pin the executor-picked story into the agent prompt — the
|
|
284949
|
+
// claim (pick_story → 🔨) and the work must be the same story.
|
|
284950
|
+
...ctx.storyId !== void 0 && ctx.storyId !== "" ? { storyId: ctx.storyId } : {},
|
|
284951
|
+
onChunk: (d) => {
|
|
284952
|
+
timeoutWatchdog.markProgress();
|
|
284953
|
+
stallDetector.markProgress();
|
|
284954
|
+
captureSink?.onChunk(d);
|
|
284955
|
+
try {
|
|
284956
|
+
appendFileSync16(livePath, d);
|
|
284957
|
+
} catch {
|
|
284958
|
+
}
|
|
284959
|
+
signalRecorder.accept(d);
|
|
284960
|
+
},
|
|
284961
|
+
// FIX-1474: hand the live child to the liveness probe so an
|
|
284962
|
+
// out-of-band death is detected within a bounded window.
|
|
284963
|
+
onSpawn: (child) => {
|
|
284964
|
+
spawnedPid = child.pid;
|
|
284854
284965
|
}
|
|
284855
|
-
|
|
284856
|
-
|
|
284857
|
-
|
|
284966
|
+
}),
|
|
284967
|
+
lostRace
|
|
284968
|
+
]);
|
|
284858
284969
|
} finally {
|
|
284970
|
+
spawnSettled = true;
|
|
284971
|
+
livenessProbe.stop();
|
|
284859
284972
|
if (mainLeakWatchdog !== void 0) {
|
|
284860
284973
|
activeMainLeak = await mainLeakWatchdog.stop();
|
|
284861
284974
|
}
|
|
@@ -284963,7 +285076,7 @@ ${res.stderr}`.split("\n").find((l) => l.trim() !== "") ?? "").slice(0, 200);
|
|
|
284963
285076
|
// FIX-343 (step ①): the builder session id is part of the auditable
|
|
284964
285077
|
// header — the attest gate's scorer≠builder-session invariant is then
|
|
284965
285078
|
// traceable to a recorded build-session id, not asserted.
|
|
284966
|
-
`# exit=${res.exitCode} timedOut=${res.timedOut} build-session=${builderSessionId}
|
|
285079
|
+
`# exit=${res.exitCode} timedOut=${res.timedOut} lost=${agentLost} build-session=${builderSessionId}
|
|
284967
285080
|
--- stdout ---
|
|
284968
285081
|
${res.stdout}
|
|
284969
285082
|
--- stderr ---
|
|
@@ -285011,7 +285124,10 @@ ${res.stderr}
|
|
|
285011
285124
|
cycleStartSec: ctx.startSec
|
|
285012
285125
|
}) ?? void 0 : void 0;
|
|
285013
285126
|
return {
|
|
285014
|
-
|
|
285127
|
+
// FIX-1474: a lost child converges the cycle to the explicit `aborted`
|
|
285128
|
+
// terminal in the orchestrator (no retry, no blocked) — the death was
|
|
285129
|
+
// environmental, recorded fail-loud via cycle:agent_lost.
|
|
285130
|
+
event: { type: "agent_exited", exit: res.exitCode, timedOut: res.timedOut, ...agentLost ? { lost: true } : {} },
|
|
285015
285131
|
// FIX-343 (step ①): persist the builder session id on the cycle context so
|
|
285016
285132
|
// it survives to the attest gate (the scorer≠builder-session invariant is then
|
|
285017
285133
|
// traceable to a recorded build-session id, not asserted).
|