Skip to content

Commit 9e33583

Browse files
darrenhindeclaude
andcommitted
fix(abilities): fix cancel/abort propagation and execution lifecycle
- cancelActive() no longer double-aborts; delegates to cancel() which owns both abort signal and state mutation - onSessionDeleted() and cleanup() now abort the controller so in-flight execution loops actually stop - Set activeExecution before awaiting executeAbility so getActive() returns a live reference during execution (fixes chat-context injection) - Add early abort check in executeAbility before the step loop starts - opencode-plugin.ts uses cancelActive() instead of cancel() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ee4fadf commit 9e33583

3 files changed

Lines changed: 41 additions & 7 deletions

File tree

packages/plugin-abilities/src/executor/execution-manager.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { executeAbility } from './index.js'
33

44
/**
55
* Minimal ExecutionManager
6-
*
6+
*
77
* Simplified to track SINGLE execution at a time.
88
* No session management, no cleanup timers, no multi-execution.
9-
*
9+
*
1010
* This is the bare minimum to test the core concept.
1111
*/
1212
export class ExecutionManager {
@@ -28,6 +28,22 @@ export class ExecutionManager {
2828
console.log(`[abilities] Starting execution: ${ability.name}`)
2929

3030
this.abortController = new AbortController()
31+
32+
// Set activeExecution BEFORE awaiting so getActive() returns a live
33+
// reference during execution (needed for chat-context injection and
34+
// concurrent-execution guards).
35+
this.activeExecution = {
36+
id: `exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
37+
ability,
38+
inputs,
39+
status: 'running',
40+
currentStep: null,
41+
currentStepIndex: -1,
42+
completedSteps: [],
43+
pendingSteps: [...ability.steps],
44+
startedAt: Date.now(),
45+
}
46+
3147
const execution = await executeAbility(ability, inputs, ctx, this.abortController.signal)
3248
this.activeExecution = execution
3349

@@ -61,7 +77,6 @@ export class ExecutionManager {
6177
if (!this.activeExecution) return false
6278

6379
if (this.activeExecution.status === 'running') {
64-
// Signal the in-flight executeAbility loop to stop at the next iteration
6580
this.abortController?.abort()
6681
this.abortController = null
6782
this.activeExecution.status = 'failed'
@@ -75,14 +90,13 @@ export class ExecutionManager {
7590
}
7691

7792
cancelActive(): boolean {
78-
// Signal the in-flight executeAbility loop to stop at the next iteration
79-
this.abortController?.abort()
80-
this.abortController = null
8193
return this.cancel()
8294
}
8395

8496
onSessionDeleted(sessionId: string): void {
8597
if (this.activeExecution && this.activeExecution.status === 'running') {
98+
this.abortController?.abort()
99+
this.abortController = null
86100
this.activeExecution.status = 'failed'
87101
this.activeExecution.error = `Session ${sessionId} deleted`
88102
this.activeExecution.completedAt = Date.now()
@@ -91,6 +105,8 @@ export class ExecutionManager {
91105
}
92106

93107
cleanup(): void {
108+
this.abortController?.abort()
109+
this.abortController = null
94110
this.activeExecution = null
95111
}
96112
}

packages/plugin-abilities/src/executor/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,24 @@ export async function executeAbility(
493493

494494
// Build execution order based on dependencies
495495
const orderedSteps = buildExecutionOrder(ability.steps)
496+
497+
// Check for cancellation before starting execution
498+
if (signal?.aborted) {
499+
return {
500+
id: generateExecutionId(),
501+
ability,
502+
inputs: resolvedInputs,
503+
status: 'failed',
504+
currentStep: null,
505+
currentStepIndex: -1,
506+
completedSteps: [],
507+
pendingSteps: orderedSteps,
508+
startedAt: Date.now(),
509+
completedAt: Date.now(),
510+
error: 'Cancelled',
511+
}
512+
}
513+
496514
const stepOutputs = new Map<string, string>()
497515

498516
const execution: AbilityExecution = {

packages/plugin-abilities/src/opencode-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export const AbilitiesPlugin: Plugin = async (ctx) => {
214214
description: 'Cancel the active ability execution',
215215
args: {},
216216
async execute() {
217-
const cancelled = executionManager.cancel()
217+
const cancelled = executionManager.cancelActive()
218218
return JSON.stringify(cancelled
219219
? { status: 'cancelled', message: 'Ability cancelled' }
220220
: { status: 'none', message: 'No active ability' })

0 commit comments

Comments
 (0)