feat: add cancelled run state and fix extension pause/resume flow - #221
feat: add cancelled run state and fix extension pause/resume flow#221achuvyas-kv wants to merge 2 commits into
Conversation
WalkthroughThe extension now distinguishes pause and cancellation intents, persists resumable or cancelled outcomes, restores paused evaluator queues and resumed round numbering, and presents ChangesInterruption lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Popup
participant ServiceWorker
participant Orchestrator
participant Storage
Popup->>ServiceWorker: Send OPFOR_UI_STOP with pause or cancel intent
ServiceWorker->>Orchestrator: Set stop state and intent
Orchestrator->>Storage: Persist paused snapshot or cancelled result
Storage-->>Popup: Return interruption status
Popup->>Popup: Render paused or CANCELLED state
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@runners/extension/orchestrator.js`:
- Around line 672-683: The resumed-round field is written as resumedFromRound by
setRunStatus but read as lastRound by the popup. In
runners/extension/orchestrator.js:672-683 and
runners/extension/popup.js:3476-3483, align the writer and reader on one field
name—prefer updating the popup recovery logic to read
opforRunStatus.resumedFromRound if the orchestrator schema remains unchanged—so
resumed runs use priorRounds instead of deriving the counter from the truncated
transcript.
- Around line 898-921: Update the degrade-to-cancel branch in
finalizeUserInterruption to remove any existing opforPausedRun after persisting
the partial result and before marking the response cancelled. Reuse the same
pause-state cleanup mechanism used by the normal cancel path, ensuring stale
resume data cannot remain.
In `@runners/extension/popup.html`:
- Line 2416: Update the pause and stop spinner wrappers in the button markup
from div elements to span elements, and mark both spinner elements as decorative
while preserving their existing classes and hidden state.
In `@runners/extension/popup.js`:
- Around line 1700-1706: Use the existing judged-count fallback used by
renderHistoryList when generating the report summary, and replace direct
summary.judged references in the summary text and breach-rate card (including
the lines around 1700, 1706, and 1722) with judgedCount. Preserve the existing
wording and calculations while ensuring reports persisted before this PR display
a numeric evaluator count.
- Around line 1108-1126: Update the summary calculation around safetyScore and
the rendering that consumes it so no judged evaluators produces a null/unknown
score instead of 0. Preserve numeric scoring when weightedTotal is nonzero, and
render an em dash rather than a red 0% bar when summary.judged is 0.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 766f6fba-226b-45d1-85a7-7753d2eaad3c
📒 Files selected for processing (6)
runners/extension/domTarget.jsrunners/extension/orchestrator.jsrunners/extension/popup.htmlrunners/extension/popup.jsrunners/extension/service_worker.jsrunners/extension/state.js
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@runners/extension/popup.js`:
- Around line 1284-1290: Update the empty-findings message in the report to use
a neutral unassessed/cancelled message when judgedCount(summary) is zero, and
retain the existing “passed all evaluated attack patterns” message only when at
least one evaluator was judged. Reuse the existing judged value and keep the
riskLevel behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0e9999d7-e93b-4994-8469-25f168b5dfef
📒 Files selected for processing (3)
runners/extension/orchestrator.jsrunners/extension/popup.htmlrunners/extension/popup.js
🚧 Files skipped from review as they are similar to previous changes (2)
- runners/extension/popup.html
- runners/extension/orchestrator.js
Problem
Stopping a run in the browser extension left the user with nothing useful:
FAIL. When a partial result did survive, it was hardcoded toFAIL, so a run the user interrupted was indistinguishable from a genuine vulnerability.OPFOR_STOPsignal, and the stop handler did both things at once — wrote a terminalCANCELLEDresult and saved a resumable snapshot. Everything downstream then guessed, and guessed inconsistently:CANCELLEDin the report.completedresult could resolve the next evaluator instantly, with the wrong verdict.Solution
Make cancellation a real, first-class outcome, and give Pause and Stop opposite, explicit contracts.
Cancellation is its own state. A new
CANCELLEDverdict sits alongsidePASS/FAIL, styled with the existing amber--warntoken — same card, pill, and banner layout, only a third colour. Cancelling now always produces a report of the evaluators completed up to that point.Never judge what was cancelled. The judge call is skipped entirely on cancel. Because an unfinished evaluator has no verdict, no score, confidence, or reasoning is fabricated for it — the report shows
—rather than a plausible-looking number.Pause and Stop carry explicit intent. The popup tags the
OPFOR_UI_STOPmessage withintent: "pause" | "cancel", and a singlefinalizeUserInterruption()helper in the background enforces the split, so the two can't drift apart:CANCELLEDThe reply carries the intent back, so the popup never infers pause-vs-cancel from the ambiguous
pausedflag.Changes
All in
runners/extension/— no core or CLI changes.orchestrator.jsfinalizeUserInterruption()+cancelledJudgment(); both stop paths route through them. Removed the judge call on stop (and the now-unusedjudgeResponseimport). Carry the restored transcript andresumedFromRoundthroughsetRunStatuson resume instead of writing[]. ClearopforLastResultat the start of every run, including resume. Degrade an unresumable pause to a cancel.popup.jsCANCELLEDverdict throughoutrenderDone,buildReport,generateHtmlReport, and run history. Exclude cancelled evaluators from the weighted score and add ajudgedcount.nullscore/confidence for cancelled rows. AddeduserInterrupted()and guarded all threeOPFOR_JUDGE_PARTIALcall sites. Added a fast interrupt path to the storage poller. Ownership-check persisted records viamatchesEvaluator(). Park the queue on pause instead of clearing it; restore the full queue incheckPausedRun. AddedrenderPausedScreen()andsetPauseBusy/setStopBusy.popup.htmlCANCELLEDstyling for the verdict card, result rows/pills, and history stripe. Spinner + label markup for Pause/Stop,:disabledstyles, and fixed-width labels so the icon doesn't shift when the label swaps.service_worker.jsstate.OPFOR_STOP_INTENTfrom the stop message.state.jsOPFOR_STOP_INTENTfield, defaulting to"cancel"so an untagged stop never leaves a resumable run behind.domTarget.jsroundOffsetoption so round numbers continue after a resume rather than restarting at 1.Notable bug found along the way
The verdict check keyed off
result.stopped, butstopped: trueis also set on legitimately-judged"recovered"partials — so runs recovered after an interruption were being mislabelledCANCELLEDand losing their real verdict. All three sites now key offstopReason === "user_stop".Issue
N/A
How to test
popup.js/popup.htmlare loaded directly by the manifest, so no build step is needed — but the background service worker does need a full reload:chrome://extensions→ reload icon. Reopening the popup alone is not enough.Cancel produces a report
CANCELLED.—for score and confidence, and reads "Cancelled by user before this evaluator could finish." — not an LLM rationale.Stop does not leave a resumable run
7. Close and reopen the extension. It should return to idle — not a "Run paused / Resume" screen.
Pause and resume
8. Start a 3-evaluator suite. Let the first finish, click Pause during the second.
9. Pause shows its own spinner and "Pausing…", then the paused screen reports real progress, e.g.
evaluator 2 of 3 · 3 of 10 turns done · saved.10. Close and reopen the popup, then click Resume.
11. The running screen shows the prior turn count (not 0) and the counter continues (e.g. turn 4), rather than restarting at 1.
12. The run continues through evaluator 3 — the rest of the suite is not dropped.
13. The final report shows the paused evaluator with its real PASS/FAIL verdict, never
CANCELLED, and no evaluator resolves instantly with a previous evaluator's verdict.Edge cases
14. Pause, then click Stop from the paused screen → a
CANCELLEDreport of what completed, and no resumable run on reopen.15. Pause, then Discard → idle, with no resumable run offered on reopen.
Summary by CodeRabbit
New Features
Bug Fixes