Skip to content

Commit ce456c8

Browse files
committed
feat(workflows): add pause/resume functionality to workflow execution
- Add RESUME event type and paused flag to workflow state - Implement pause/resume logic in state machine and runner - Clear paused flag when advancing steps or receiving input - Ensure proper state transitions when resuming execution
1 parent 1a54287 commit ce456c8

File tree

3 files changed

+44
-10
lines changed

3 files changed

+44
-10
lines changed

src/workflows/execution/runner.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export class WorkflowRunner {
113113
autoMode: false,
114114
promptQueue: [],
115115
promptQueueIndex: 0,
116+
paused: false,
116117
currentOutput: null,
117118
});
118119

@@ -269,6 +270,9 @@ export class WorkflowRunner {
269270
// Track step start for resume
270271
await markStepStarted(this.cmRoot, ctx.currentStepIndex);
271272

273+
// Clear paused flag for new step
274+
ctx.paused = false;
275+
272276
// Reset all behaviors
273277
this.behaviorManager.resetAll();
274278

@@ -384,11 +388,17 @@ export class WorkflowRunner {
384388
private async handleWaiting(): Promise<void> {
385389
const ctx = this.machine.context;
386390

387-
debug('[Runner] Handling waiting state, autoMode=%s, promptQueue=%d items, queueIndex=%d',
388-
ctx.autoMode, ctx.promptQueue.length, ctx.promptQueueIndex);
391+
debug('[Runner] Handling waiting state, autoMode=%s, paused=%s, promptQueue=%d items, queueIndex=%d',
392+
ctx.autoMode, ctx.paused, ctx.promptQueue.length, ctx.promptQueueIndex);
393+
394+
// If paused, force user input provider (not controller)
395+
const provider = ctx.paused ? this.userInput : this.activeProvider;
396+
if (ctx.paused) {
397+
debug('[Runner] Workflow is paused, using user input provider');
398+
}
389399

390-
// No chained prompts - auto-advance to next step
391-
if (ctx.promptQueue.length === 0) {
400+
if (!ctx.paused && ctx.promptQueue.length === 0) {
401+
// No chained prompts and not paused - auto-advance to next step
392402
debug('[Runner] No chained prompts, auto-advancing to next step');
393403
const step = this.moduleSteps[ctx.currentStepIndex];
394404
const uniqueAgentId = `${step.agentId}-step-${ctx.currentStepIndex}`;
@@ -409,8 +419,8 @@ export class WorkflowRunner {
409419
cwd: this.cwd,
410420
};
411421

412-
// Get input from active provider
413-
const result = await this.activeProvider.getInput(inputContext);
422+
// Get input from provider (user input if paused, otherwise active provider)
423+
const result = await provider.getInput(inputContext);
414424

415425
debug('[Runner] Got input result: type=%s', result.type);
416426

@@ -443,6 +453,7 @@ export class WorkflowRunner {
443453
if (result.value === '') {
444454
// Empty input = advance to next step
445455
debug('[Runner] Empty input, marking agent completed and advancing');
456+
ctx.paused = false; // Clear paused flag
446457
this.emitter.updateAgentStatus(uniqueAgentId, 'completed');
447458
this.emitter.logMessage(uniqueAgentId, `${step.agentName} has completed their work.`);
448459
this.emitter.logMessage(uniqueAgentId, '\n' + '═'.repeat(80) + '\n');
@@ -457,6 +468,7 @@ export class WorkflowRunner {
457468

458469
case 'skip':
459470
debug('[Runner] Skip requested, marking agent skipped');
471+
ctx.paused = false; // Clear paused flag
460472
this.emitter.updateAgentStatus(uniqueAgentId, 'skipped');
461473
this.emitter.logMessage(uniqueAgentId, `${step.agentName} was skipped.`);
462474
this.emitter.logMessage(uniqueAgentId, '\n' + '═'.repeat(80) + '\n');
@@ -508,13 +520,20 @@ export class WorkflowRunner {
508520
}
509521
}
510522

523+
// Clear paused flag since we're resuming
524+
ctx.paused = false;
525+
511526
this.abortController = new AbortController();
512527
this.behaviorManager.setAbortController(this.abortController);
513528
this.behaviorManager.setStepContext({
514529
stepIndex: ctx.currentStepIndex,
515530
agentId: uniqueAgentId,
516531
agentName: step.agentName,
517532
});
533+
534+
// Transition state machine to running so pause works
535+
this.machine.send({ type: 'RESUME' });
536+
518537
this.emitter.updateAgentStatus(uniqueAgentId, 'running');
519538
this.emitter.setWorkflowStatus('running');
520539

@@ -547,11 +566,14 @@ export class WorkflowRunner {
547566
};
548567
ctx.currentMonitoringId = output.monitoringId;
549568

569+
// Transition back to awaiting state
570+
this.machine.send({
571+
type: 'STEP_COMPLETE',
572+
output: { output: output.output, monitoringId: output.monitoringId },
573+
});
574+
550575
// Back to checkpoint while waiting for next input
551576
this.emitter.updateAgentStatus(uniqueAgentId, 'awaiting');
552-
553-
// Stay in waiting state - will get more input
554-
// (The waiting handler will be called again)
555577
} catch (error) {
556578
if (error instanceof Error && error.name === 'AbortError') {
557579
// Handle mode switch during execution

src/workflows/state/machine.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export function createWorkflowMachine(initialContext: Partial<WorkflowContext> =
122122
autoMode: false,
123123
promptQueue: [],
124124
promptQueueIndex: 0,
125+
paused: false,
125126
cwd: process.cwd(),
126127
cmRoot: '',
127128
...initialContext,
@@ -188,8 +189,9 @@ export function createWorkflowMachine(initialContext: Partial<WorkflowContext> =
188189
PAUSE: {
189190
target: 'awaiting',
190191
action: (ctx) => {
191-
// Pause forces manual mode
192+
// Pause forces manual mode and sets paused flag
192193
ctx.autoMode = false;
194+
ctx.paused = true;
193195
debug('[FSM] Paused - auto mode disabled');
194196
},
195197
},
@@ -204,6 +206,12 @@ export function createWorkflowMachine(initialContext: Partial<WorkflowContext> =
204206
debug('[FSM] Entering awaiting state, autoMode: %s', ctx.autoMode);
205207
},
206208
on: {
209+
RESUME: {
210+
target: 'running',
211+
action: () => {
212+
debug('[FSM] Resuming execution from awaiting state');
213+
},
214+
},
207215
INPUT_RECEIVED: [
208216
// If more steps, go to running
209217
{

src/workflows/state/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type WorkflowEvent =
2626
| { type: 'STEP_COMPLETE'; output: StepOutput }
2727
| { type: 'STEP_ERROR'; error: Error }
2828
| { type: 'INPUT_RECEIVED'; input: string }
29+
| { type: 'RESUME' }
2930
| { type: 'SKIP' }
3031
| { type: 'PAUSE' }
3132
| { type: 'STOP' };
@@ -57,6 +58,9 @@ export interface WorkflowContext {
5758
promptQueue: QueuedPrompt[];
5859
promptQueueIndex: number;
5960

61+
// Pause state
62+
paused: boolean;
63+
6064
// Paths
6165
cwd: string;
6266
cmRoot: string;

0 commit comments

Comments
 (0)