Add a Cancel control to the AI panel - #159
Merged
Merged
Conversation
Cancel existed only in HarnessChainPanel, which drives a chain - and a chain is refused under autonomyLevel: "assisted", the setting this repository itself uses. AiPanel, where a single stage runs, had no cancel control despite its header comment claiming one, and openspec-ui.cancelProcess routes to implementationSessions.cancel, a different lifecycle bound to a tree item. So the cancellation that shipped in harness-cancel-stops-the-run - an AbortSignal through spawnAndStream, process-tree termination, and a runner that no longer spawns a second agent to ask the first to stop - was unreachable from the path most runs take. Killing the process by hand remained the only option there, which is exactly what had to be done twice on 2026-09-01. The button mirrors the chain panel rather than routing through the extension's RunController: AiPanel is shared by both delivery targets and RunController exists only in one, so routing through it would make the standalone UI behave differently for a control whose point is behaving the same everywhere. It sends a cancel naming the active runId rather than a fresh one, and does not re-check isRunning at click time - agent-runner.ts already treats a cancel for an unknown runId as a no-op yielding cancelled, deliberately, and a second guard here would drift from that decision. openspec-ui.cancelProcess is retitled to name what it cancels. Its id, behavior and when-clause are untouched: an id is referenced from menus and possibly from a user's own keybindings. git diff packages/core/ is empty, as task 4.3 requires - this change makes existing behavior reachable, it does not change it. Implemented via the Agentic Harness (claude-cli, apply stage), 19/20. Typecheck clean; webui 215 tests green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
ai-panel-cancel-run. 19/20 — the open task is human-only, and it is the checkharness-cancel-stops-the-runcould not perform because this control did not exist.What was wrong
Cancellived in exactly one place:HarnessChainPanel.tsx, underisRunning && !pendingCheckpoint. That panel drives a chain, andHarnessChainRunnerrefuses to chain underautonomyLevel: "assisted"— the setting this repository's ownopenspec/agent-harness.jsonuses.AiPanel.tsx, where a single stage runs, had no cancel control at all — despite its own header comment reading "Displays the event stream with the ability to cancel." No command-palette route either:openspec-ui.cancelProcesscallsdeps.implementationSessions.cancel(...), a different lifecycle, bound to a tree item.So the cancellation that shipped in
harness-cancel-stops-the-run— anAbortSignalthroughspawnAndStream, process-tree termination, a runner that no longer spawns a second agent to ask the first to stop — was unreachable from the path most runs take. Killing the process by hand from the process list remained the only option there, which is exactly what had to be done twice on 2026-09-01.Three decisions worth naming
It mirrors the chain panel rather than routing through
RunController.AiPanelis shared by both delivery targets andRunControllerexists only in the extension; routing through it would make the standalone UI behave differently — for a control whose entire point is behaving the same everywhere.It sends the active
runId, not a fresh one. Task 3.2 asserts this explicitly, because a test that only checkskind: "cancel"passes even when the button generates a new id and cancels nothing.It does not re-check
isRunningat click time.agent-runner.tsalready treats a cancel for an unknownrunIdas a no-op that yieldscancelled— deliberately, because the race between rendering and clicking is inherent. A second guard here would duplicate a decision already made in core and drift from it.The command retitle
openspec-ui.cancelProcess→ "OpenSpec UI: Cancel Implementation Session". Its id, behavior andwhenclause are untouched: an id is referenced from menus and possibly from a user's own keybindings, so this is a title and nothing more. The diff onpackage.jsonis exactly those two lines.Making it also cancel harness runs was rejected in design.md: that is a decision about what the Processes view represents, not a naming fix.
Test plan
typecheckcleangit diff --cached packages/core/is empty — this change makes existing behavior reachable, it does not change itopenspec change validate --strict ai-panel-cancel-run→ validautonomyLevel: "assisted", start a single stage from the AI panel, press Cancel, and confirm from the process list that the agent's process is gone and no second agent process was spawned by the cancel. Needsnpm run reinstall:localfirst.🤖 Generated with Claude Code