Skip to content

Add a Cancel control to the AI panel - #159

Merged
VeryComplexAndLongName merged 1 commit into
mainfrom
feat/ai-panel-cancel-run
Sep 2, 2026
Merged

Add a Cancel control to the AI panel#159
VeryComplexAndLongName merged 1 commit into
mainfrom
feat/ai-panel-cancel-run

Conversation

@VeryComplexAndLongName

Copy link
Copy Markdown
Owner

Implements ai-panel-cancel-run. 19/20 — the open task is human-only, and it is the check harness-cancel-stops-the-run could not perform because this control did not exist.

What was wrong

Cancel lived in exactly one place: HarnessChainPanel.tsx, under isRunning && !pendingCheckpoint. That panel drives a chain, and HarnessChainRunner refuses to chain under autonomyLevel: "assisted" — the setting this repository's own openspec/agent-harness.json uses.

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.cancelProcess calls deps.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, 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. AiPanel is shared by both delivery targets and RunController exists 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 checks kind: "cancel" passes even when the button generates a new id and cancels nothing.

It does not re-check isRunning at click time. agent-runner.ts already treats a cancel for an unknown runId as a no-op that yields cancelled — 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 and when clause 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 on package.json is 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

  • typecheck clean
  • webui 215 tests green (35 files)
  • Task 4.3: git diff --cached packages/core/ is empty — this change makes existing behavior reachable, it does not change it
  • openspec change validate --strict ai-panel-cancel-run → valid
  • 4.5 — human-only: with autonomyLevel: "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. Needs npm run reinstall:local first.
  • CI green

🤖 Generated with Claude Code

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>
@VeryComplexAndLongName
VeryComplexAndLongName merged commit 11ffc75 into main Sep 2, 2026
7 checks passed
@VeryComplexAndLongName
VeryComplexAndLongName deleted the feat/ai-panel-cancel-run branch September 3, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant