@seanyao/roll 4.720.1 → 4.721.1
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 +6 -0
- package/dist/roll.mjs +52 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v4.721.1 — 2026-07-21
|
|
6
|
+
|
|
7
|
+
### 稳定性
|
|
8
|
+
- loop 看门狗的"进展"判定从 stdout 改为 git 状态(新 commit / 工作区文件变动):输出缓冲型 agent 写文件时不再被 15 分钟无进展误杀;新增 `no-state-change` 杀因——默认 25 分钟无 commit 且无文件变动即终止(即使 stdout 仍在输出),原地打转的 cycle 不再烧满 45 分钟墙钟;阈值可用 `ROLL_CYCLE_NO_STATE_CHANGE_SEC` 覆盖(FIX-1477)[loop]
|
|
9
|
+
- attest 闸的"无 AC 块豁免"提前到渲染之前评估:无 AC 块的卡不再因渲染失败或空壳报告被硬拦,直接合法放行(FIX-1476)[loop]
|
|
10
|
+
|
|
5
11
|
## v4.720.1 — 2026-07-20
|
|
6
12
|
|
|
7
13
|
### 稳定性
|
package/dist/roll.mjs
CHANGED
|
@@ -14642,15 +14642,20 @@ function timeoutTeardownCommands(ctx) {
|
|
|
14642
14642
|
function cycleTimeoutVerdict(input) {
|
|
14643
14643
|
const wall = input.wallLimitSec ?? CYCLE_WALL_TIMEOUT_SEC;
|
|
14644
14644
|
const idle = input.noProgressLimitSec ?? CYCLE_NO_PROGRESS_SEC;
|
|
14645
|
+
const stateIdle = input.noStateChangeLimitSec ?? CYCLE_NO_STATE_CHANGE_SEC;
|
|
14645
14646
|
if (wall > 0 && input.elapsedSec >= wall) {
|
|
14646
14647
|
return { timedOut: true, reason: "wall", elapsedSec: input.elapsedSec, idleSec: input.idleSec };
|
|
14647
14648
|
}
|
|
14648
14649
|
if (idle > 0 && input.idleSec >= idle) {
|
|
14649
14650
|
return { timedOut: true, reason: "no-progress", elapsedSec: input.elapsedSec, idleSec: input.idleSec };
|
|
14650
14651
|
}
|
|
14652
|
+
if (input.stateIdleSec !== void 0 && stateIdle > 0 && input.stateIdleSec >= stateIdle) {
|
|
14653
|
+
return { timedOut: true, reason: "no-state-change", elapsedSec: input.elapsedSec, idleSec: input.idleSec };
|
|
14654
|
+
}
|
|
14651
14655
|
const wallRemain = wall > 0 ? wall - input.elapsedSec : Number.POSITIVE_INFINITY;
|
|
14652
14656
|
const idleRemain = idle > 0 ? idle - input.idleSec : Number.POSITIVE_INFINITY;
|
|
14653
|
-
|
|
14657
|
+
const stateRemain = input.stateIdleSec !== void 0 && stateIdle > 0 ? stateIdle - input.stateIdleSec : Number.POSITIVE_INFINITY;
|
|
14658
|
+
return { timedOut: false, remainingSec: Math.min(wallRemain, idleRemain, stateRemain) };
|
|
14654
14659
|
}
|
|
14655
14660
|
function stallVerdict(input) {
|
|
14656
14661
|
const threshold = input.stallThresholdSec ?? CYCLE_STALL_THRESHOLD_SEC;
|
|
@@ -15141,7 +15146,7 @@ function adversarialDegradedCmd(cycleId, storyId, cause, from = "adversarial") {
|
|
|
15141
15146
|
function initialCycleState(ctx) {
|
|
15142
15147
|
return { phase: "pick", ctx, attempt: 0, done: false };
|
|
15143
15148
|
}
|
|
15144
|
-
var CYCLE_TIMEOUT_SEC, WATCHDOG_KILL_GRACE_SEC, CYCLE_WALL_TIMEOUT_SEC, CYCLE_NO_PROGRESS_SEC, CYCLE_STALL_THRESHOLD_SEC, STALL_STARTUP_GRACE_SEC, MAX_AGENT_ATTEMPTS, RETRY_BASE_BACKOFF_SEC, EVENT_VALID_PHASES;
|
|
15149
|
+
var CYCLE_TIMEOUT_SEC, WATCHDOG_KILL_GRACE_SEC, CYCLE_WALL_TIMEOUT_SEC, CYCLE_NO_PROGRESS_SEC, CYCLE_NO_STATE_CHANGE_SEC, CYCLE_STALL_THRESHOLD_SEC, STALL_STARTUP_GRACE_SEC, MAX_AGENT_ATTEMPTS, RETRY_BASE_BACKOFF_SEC, EVENT_VALID_PHASES;
|
|
15145
15150
|
var init_orchestrator = __esm({
|
|
15146
15151
|
"packages/core/dist/loop/orchestrator.js"() {
|
|
15147
15152
|
"use strict";
|
|
@@ -15153,6 +15158,7 @@ var init_orchestrator = __esm({
|
|
|
15153
15158
|
WATCHDOG_KILL_GRACE_SEC = 5;
|
|
15154
15159
|
CYCLE_WALL_TIMEOUT_SEC = 2700;
|
|
15155
15160
|
CYCLE_NO_PROGRESS_SEC = 900;
|
|
15161
|
+
CYCLE_NO_STATE_CHANGE_SEC = 1500;
|
|
15156
15162
|
CYCLE_STALL_THRESHOLD_SEC = 600;
|
|
15157
15163
|
STALL_STARTUP_GRACE_SEC = 120;
|
|
15158
15164
|
MAX_AGENT_ATTEMPTS = 3;
|
|
@@ -251254,6 +251260,11 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks, sco
|
|
|
251254
251260
|
sinks.event({ cycleId, verdict: "skipped", reasons: reasons2 });
|
|
251255
251261
|
return { verdict: "skipped", mode, reasons: reasons2, blocked: blocked4 };
|
|
251256
251262
|
}
|
|
251263
|
+
if (storyHasAcBlock(specRepoCwd, storyId) === false) {
|
|
251264
|
+
const reasons2 = ["story has no AC block; acceptance report not required"];
|
|
251265
|
+
sinks.event({ cycleId, verdict: "produced", reasons: reasons2 });
|
|
251266
|
+
return { verdict: "produced", mode, reasons: reasons2, blocked: false };
|
|
251267
|
+
}
|
|
251257
251268
|
if (renderExitCode !== 0) {
|
|
251258
251269
|
const reasons2 = [`attest render failed for ${storyId} (exit ${renderExitCode})`];
|
|
251259
251270
|
const blocked4 = mode === "hard";
|
|
@@ -251261,11 +251272,6 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks, sco
|
|
|
251261
251272
|
sinks.event({ cycleId, verdict: "skipped", reasons: reasons2 });
|
|
251262
251273
|
return { verdict: "skipped", mode, reasons: reasons2, blocked: blocked4 };
|
|
251263
251274
|
}
|
|
251264
|
-
if (storyHasAcBlock(specRepoCwd, storyId) === false) {
|
|
251265
|
-
const reasons2 = ["story has no AC block; acceptance report not required"];
|
|
251266
|
-
sinks.event({ cycleId, verdict: "produced", reasons: reasons2 });
|
|
251267
|
-
return { verdict: "produced", mode, reasons: reasons2, blocked: false };
|
|
251268
|
-
}
|
|
251269
251275
|
const diagnostics = violatesMustDeclareSurface(specRepoCwd, storyId) ? [MUST_DECLARE_FAIL_REASON] : [];
|
|
251270
251276
|
const redAcs = redAcFailures(worktreeCwd, storyId, scoreRepoCwd);
|
|
251271
251277
|
if (redAcs.length > 0) {
|
|
@@ -284515,7 +284521,8 @@ function readCycleTimeoutThresholds(repoCwd) {
|
|
|
284515
284521
|
}
|
|
284516
284522
|
return {
|
|
284517
284523
|
wallSec: envNum("ROLL_CYCLE_WALL_TIMEOUT_SEC") ?? wallSec,
|
|
284518
|
-
noProgressSec: envNum("ROLL_CYCLE_NO_PROGRESS_SEC") ?? noProgressSec
|
|
284524
|
+
noProgressSec: envNum("ROLL_CYCLE_NO_PROGRESS_SEC") ?? noProgressSec,
|
|
284525
|
+
noStateChangeSec: envNum("ROLL_CYCLE_NO_STATE_CHANGE_SEC") ?? CYCLE_NO_STATE_CHANGE_SEC
|
|
284519
284526
|
};
|
|
284520
284527
|
}
|
|
284521
284528
|
function readStallThreshold(repoCwd) {
|
|
@@ -284531,15 +284538,18 @@ function readStallThreshold(repoCwd) {
|
|
|
284531
284538
|
}
|
|
284532
284539
|
function startSpawnTimeoutWatchdog(opts) {
|
|
284533
284540
|
const { cycleId, thresholds, clock: clock2, commitCount, appendEvent: appendEvent3 } = opts;
|
|
284541
|
+
const stateSignature = opts.stateSignature;
|
|
284534
284542
|
const kill = opts.kill ?? (() => killLiveAgents("SIGKILL"));
|
|
284535
284543
|
const pollMs = opts.pollMs ?? (Number((process.env["ROLL_TIMEOUT_POLL_MS"] ?? "").trim()) || TIMEOUT_POLL_MS);
|
|
284536
|
-
if (thresholds.wallSec <= 0 && thresholds.noProgressSec <= 0) {
|
|
284544
|
+
if (thresholds.wallSec <= 0 && thresholds.noProgressSec <= 0 && thresholds.noStateChangeSec <= 0) {
|
|
284537
284545
|
return { markProgress: () => {
|
|
284538
284546
|
}, stop: () => ({ firedReason: null }) };
|
|
284539
284547
|
}
|
|
284540
284548
|
const startSec = clock2();
|
|
284541
284549
|
let lastProgressSec = startSec;
|
|
284550
|
+
let lastStateSec = startSec;
|
|
284542
284551
|
let lastCommitCount = -1;
|
|
284552
|
+
let lastSignature;
|
|
284543
284553
|
let firedReason = null;
|
|
284544
284554
|
let running = false;
|
|
284545
284555
|
const markProgress = () => {
|
|
@@ -284554,16 +284564,32 @@ function startSpawnTimeoutWatchdog(opts) {
|
|
|
284554
284564
|
const n = await commitCount();
|
|
284555
284565
|
if (n > lastCommitCount) {
|
|
284556
284566
|
lastCommitCount = n;
|
|
284557
|
-
|
|
284567
|
+
const now3 = clock2();
|
|
284568
|
+
lastProgressSec = now3;
|
|
284569
|
+
lastStateSec = now3;
|
|
284558
284570
|
}
|
|
284559
284571
|
} catch {
|
|
284560
284572
|
}
|
|
284573
|
+
if (stateSignature !== void 0) {
|
|
284574
|
+
try {
|
|
284575
|
+
const sig = await stateSignature();
|
|
284576
|
+
if (lastSignature !== void 0 && sig !== lastSignature) {
|
|
284577
|
+
const now3 = clock2();
|
|
284578
|
+
lastProgressSec = now3;
|
|
284579
|
+
lastStateSec = now3;
|
|
284580
|
+
}
|
|
284581
|
+
lastSignature = sig;
|
|
284582
|
+
} catch {
|
|
284583
|
+
}
|
|
284584
|
+
}
|
|
284561
284585
|
const now2 = clock2();
|
|
284562
284586
|
const verdict = cycleTimeoutVerdict({
|
|
284563
284587
|
elapsedSec: now2 - startSec,
|
|
284564
284588
|
idleSec: now2 - lastProgressSec,
|
|
284589
|
+
stateIdleSec: now2 - lastStateSec,
|
|
284565
284590
|
wallLimitSec: thresholds.wallSec,
|
|
284566
|
-
noProgressLimitSec: thresholds.noProgressSec
|
|
284591
|
+
noProgressLimitSec: thresholds.noProgressSec,
|
|
284592
|
+
noStateChangeLimitSec: thresholds.noStateChangeSec
|
|
284567
284593
|
});
|
|
284568
284594
|
if (verdict.timedOut) {
|
|
284569
284595
|
firedReason = verdict.reason;
|
|
@@ -284593,6 +284619,12 @@ function startSpawnTimeoutWatchdog(opts) {
|
|
|
284593
284619
|
lastCommitCount = await commitCount();
|
|
284594
284620
|
} catch {
|
|
284595
284621
|
}
|
|
284622
|
+
if (stateSignature !== void 0) {
|
|
284623
|
+
try {
|
|
284624
|
+
lastSignature = await stateSignature();
|
|
284625
|
+
} catch {
|
|
284626
|
+
}
|
|
284627
|
+
}
|
|
284596
284628
|
})();
|
|
284597
284629
|
const timer = setInterval(() => void tick(), pollMs);
|
|
284598
284630
|
timer.unref?.();
|
|
@@ -284760,11 +284792,13 @@ async function executeSpawnAgentCommand(cmd, ports, ctx) {
|
|
|
284760
284792
|
appendEvent: (ev) => ports.events.appendEvent(ports.paths.eventsPath, ev),
|
|
284761
284793
|
thresholdSec: readStallThreshold(ports.repoCwd).thresholdSec
|
|
284762
284794
|
});
|
|
284795
|
+
const statusSignature = ports.git.worktreeStatusSignature;
|
|
284763
284796
|
const timeoutWatchdog = startSpawnTimeoutWatchdog({
|
|
284764
284797
|
cycleId: ctx.cycleId ?? "",
|
|
284765
284798
|
thresholds: readCycleTimeoutThresholds(ports.repoCwd),
|
|
284766
284799
|
clock: ports.clock,
|
|
284767
284800
|
commitCount: () => ports.git.commitsAhead(execCwd, observeBase),
|
|
284801
|
+
...statusSignature !== void 0 ? { stateSignature: () => statusSignature(execCwd) } : {},
|
|
284768
284802
|
appendEvent: (ev) => ports.events.appendEvent(ports.paths.eventsPath, ev)
|
|
284769
284803
|
});
|
|
284770
284804
|
const skillBodyForSpawn = maybeInjectProjectMap(
|
|
@@ -287752,6 +287786,13 @@ function nodePorts(opts) {
|
|
|
287752
287786
|
const n = Number((r.stdout ?? "0").trim());
|
|
287753
287787
|
return Number.isFinite(n) ? n : 0;
|
|
287754
287788
|
},
|
|
287789
|
+
async worktreeStatusSignature(worktreeCwd) {
|
|
287790
|
+
const r = await execFileAsync17("git", ["status", "--porcelain"], {
|
|
287791
|
+
cwd: worktreeCwd,
|
|
287792
|
+
encoding: "utf8"
|
|
287793
|
+
});
|
|
287794
|
+
return r.stdout ?? "";
|
|
287795
|
+
},
|
|
287755
287796
|
async mainAhead(repoCwd) {
|
|
287756
287797
|
const r = await execFileAsync17("git", ["rev-list", "--count", "origin/main..main"], {
|
|
287757
287798
|
cwd: repoCwd,
|