fix: address follow-up review comments (eval error-class test, elapsed sentinel, cancel diff)

- config-eval test: assert a malformed file throws a loader error, NOT a
  ConfigFileEvalError, so the documented "Failed to load config file" routing
  is actually protected (cubic)
- progress-content: guard useElapsedSeconds against the startedAt=0 "not started"
  sentinel so the counter shows 0 instead of ~epoch-since-1970 on first paint (vercel)
- config index: clear a cancelled run's captured diff so it can't linger in the
  API shape or be replayed by the commit route (greptile observation)
This commit is contained in:
mantrakp04 2026-06-26 18:13:17 -07:00
parent 8b09fa3479
commit 8837389870
3 changed files with 27 additions and 15 deletions

View File

@ -721,7 +721,9 @@ export async function cancelConfigAgentRun(options: {
}
await tx.configAgentRun.update({
where: { id: options.runId },
data: { status: "cancelled", finishedAt: new Date(options.nowMs), sandboxId: null, stage: null, baseCommitSha: null },
// Clear the captured change too: a cancelled run is abandoned, so its diff/base
// must not linger in the API shape or be replayable by the commit route.
data: { status: "cancelled", finishedAt: new Date(options.nowMs), sandboxId: null, stage: null, baseCommitSha: null, diff: null },
});
return { cancelled: true, sandboxId: run.sandboxId ?? undefined, previousStatus: run.status };
});

View File

@ -26,20 +26,27 @@ function stageIndex(stage: AgentStage | null | undefined): number {
/**
* Live "seconds since the run started" counter. The run's `startedAt` is a
* wall-clock epoch value; we capture the startnow offset against a fresh
* monotonic anchor and then advance on that monotonic clock. The anchor and
* offset are recomputed whenever `startedAt` changes (e.g. when a flow renders
* the box before its real start timestamp is known), so the counter resets
* instead of freezing a stale offset. Recomputing the offset every render
* which is what the old code did re-added the elapsed time on top of the
* monotonic delta and made the timer tick at ~2× speed.
* wall-clock epoch value, with `0` as the "not started yet" sentinel until a
* real start time arrives the counter stays at 0 (otherwise it would briefly
* read ~epoch-since-1970). For a real `startedAt` we capture the startnow offset
* against a fresh monotonic anchor and advance on that monotonic clock. Both are
* recomputed whenever `startedAt` changes (e.g. when a flow renders the box
* before its real start timestamp is known), so the counter resets instead of
* freezing a stale offset. Recomputing the offset every render which the old
* code did re-added the elapsed time on top of the monotonic delta and made
* the timer tick at ~2× speed.
*/
function elapsedOffsetMs(startedAt: number): number {
return startedAt > 0 ? Math.max(0, currentEpochMsFromPerformance() - startedAt) : 0;
}
function useElapsedSeconds(startedAt: number): number {
const [elapsedMs, setElapsedMs] = useState(() => Math.max(0, currentEpochMsFromPerformance() - startedAt));
const [elapsedMs, setElapsedMs] = useState(() => elapsedOffsetMs(startedAt));
useEffect(() => {
const anchorPerfMs = performance.now();
const offsetMs = Math.max(0, currentEpochMsFromPerformance() - startedAt);
const offsetMs = elapsedOffsetMs(startedAt);
setElapsedMs(offsetMs);
if (startedAt <= 0) return;
const anchorPerfMs = performance.now();
const t = setInterval(() => setElapsedMs(offsetMs + (performance.now() - anchorPerfMs)), 1000);
return () => clearInterval(t);
}, [startedAt]);

View File

@ -130,8 +130,11 @@ import.meta.vitest?.test("evalConfigFileContent rejects missing config import ta
`, "/tmp/hexclave-missing-import-config.ts")).toThrow();
});
import.meta.vitest?.test("evalConfigFileContent rejects syntactically invalid content", ({ expect }) => {
// jiti surfaces a ParseError (not a ConfigFileEvalError), so callers route this
// to "Failed to load config file" rather than "Invalid config".
expect(() => evalConfigFileContent("export const config = {", "stack.config.ts")).toThrow();
import.meta.vitest?.test("evalConfigFileContent surfaces invalid syntax as a loader error, not ConfigFileEvalError", ({ expect }) => {
// A malformed file fails inside jiti's parser, so the thrown error is a loader
// error — NOT a ConfigFileEvalError. Callers depend on that distinction to route
// it to "Failed to load config file" rather than "Invalid config".
const evalInvalid = () => evalConfigFileContent("export const config = {", "stack.config.ts");
expect(evalInvalid).toThrow();
expect(evalInvalid).not.toThrow(ConfigFileEvalError);
});