|
6 | 6 | */ |
7 | 7 |
|
8 | 8 | import { debug } from '../../shared/logging/logger.js'; |
| 9 | +import { DEFAULT_CONTINUATION_PROMPT } from '../../shared/prompts/index.js'; |
9 | 10 | import type { InputContext } from '../input/index.js'; |
10 | 11 | import { getUniqueAgentId } from '../context/index.js'; |
11 | 12 | import { runStepResume } from '../step/run.js'; |
@@ -81,10 +82,61 @@ export async function handleDelegated(ctx: RunnerContext, callbacks: DelegatedCa |
81 | 82 | debug('[Runner:delegated] Scenario=%d, shouldWait=%s, runAutonomousLoop=%s', |
82 | 83 | behavior.scenario, behavior.shouldWait, behavior.runAutonomousLoop); |
83 | 84 |
|
| 85 | + // Auto mode continuation: Send continuation prompt FIRST before any scenario handling |
| 86 | + // This ensures agent gets a chance to continue when auto mode is enabled |
| 87 | + if (!machineCtx.continuationPromptSent && machineCtx.currentOutput) { |
| 88 | + debug('[Runner:delegated] Sending continuation prompt (first entry into auto mode)'); |
| 89 | + |
| 90 | + // Mark as sent to prevent re-sending on next entry |
| 91 | + machineCtx.continuationPromptSent = true; |
| 92 | + |
| 93 | + // Update status and transition to running |
| 94 | + ctx.emitter.updateAgentStatus(stepUniqueAgentId, 'running'); |
| 95 | + ctx.machine.send({ type: 'RESUME' }); |
| 96 | + |
| 97 | + // Track mode switch during execution (so pause works mid-turn) |
| 98 | + let modeSwitchRequested: 'manual' | null = null; |
| 99 | + const modeChangeHandler = (data: { autonomousMode: boolean }) => { |
| 100 | + if (!data.autonomousMode) { |
| 101 | + debug('[Runner:delegated] Mode change to manual during continuation prompt'); |
| 102 | + modeSwitchRequested = 'manual'; |
| 103 | + ctx.getAbortController()?.abort(); |
| 104 | + } |
| 105 | + }; |
| 106 | + process.on('workflow:mode-change', modeChangeHandler); |
| 107 | + |
| 108 | + try { |
| 109 | + // Send continuation prompt and wait for agent response |
| 110 | + await runStepResume(ctx, { |
| 111 | + resumePrompt: DEFAULT_CONTINUATION_PROMPT, |
| 112 | + resumeMonitoringId: machineCtx.currentMonitoringId, |
| 113 | + source: 'controller', |
| 114 | + }); |
| 115 | + |
| 116 | + debug('[Runner:delegated] Continuation prompt sent, agent responded'); |
| 117 | + } catch (error) { |
| 118 | + if (error instanceof Error && error.name === 'AbortError') { |
| 119 | + if (modeSwitchRequested === 'manual') { |
| 120 | + debug('[Runner:delegated] Continuation prompt aborted due to mode switch to manual'); |
| 121 | + ctx.emitter.updateAgentStatus(stepUniqueAgentId, 'awaiting'); |
| 122 | + await callbacks.setAutoMode(false); |
| 123 | + } |
| 124 | + return; |
| 125 | + } |
| 126 | + throw error; |
| 127 | + } finally { |
| 128 | + process.removeListener('workflow:mode-change', modeChangeHandler); |
| 129 | + } |
| 130 | + |
| 131 | + // After agent responds, state machine transitions back to delegated |
| 132 | + // Next call to handleDelegated will proceed with scenario handling |
| 133 | + return; |
| 134 | + } |
| 135 | + |
84 | 136 | // Handle Scenario 5: Fully autonomous prompt loop (interactive:false + autoMode + chainedPrompts) |
85 | 137 | if (behavior.runAutonomousLoop) { |
86 | 138 | debug('[Runner:delegated] Running autonomous prompt loop (Scenario 5)'); |
87 | | - await runAutonomousPromptLoop(ctx); |
| 139 | + await runAutonomousPromptLoop(ctx, callbacks); |
88 | 140 | return; |
89 | 141 | } |
90 | 142 |
|
@@ -263,7 +315,7 @@ export async function handleDelegated(ctx: RunnerContext, callbacks: DelegatedCa |
263 | 315 | * |
264 | 316 | * Automatically sends the next chained prompt without controller involvement. |
265 | 317 | */ |
266 | | -async function runAutonomousPromptLoop(ctx: RunnerContext): Promise<void> { |
| 318 | +async function runAutonomousPromptLoop(ctx: RunnerContext, callbacks: DelegatedCallbacks): Promise<void> { |
267 | 319 | const machineCtx = ctx.machine.context; |
268 | 320 | const stepIndex = machineCtx.currentStepIndex; |
269 | 321 | const step = ctx.moduleSteps[stepIndex]; |
@@ -375,11 +427,37 @@ async function runAutonomousPromptLoop(ctx: RunnerContext): Promise<void> { |
375 | 427 |
|
376 | 428 | // Resume step with the prompt |
377 | 429 | ctx.machine.send({ type: 'RESUME' }); |
378 | | - await runStepResume(ctx, { |
379 | | - resumePrompt: nextPrompt.content, |
380 | | - resumeMonitoringId: machineCtx.currentMonitoringId, |
381 | | - source: 'controller', |
382 | | - }); |
| 430 | + |
| 431 | + // Track mode switch during execution (so pause works mid-turn) |
| 432 | + let modeSwitchRequested: 'manual' | null = null; |
| 433 | + const modeChangeHandler = (data: { autonomousMode: boolean }) => { |
| 434 | + if (!data.autonomousMode) { |
| 435 | + debug('[Runner:delegated:autonomous] Mode change to manual during prompt execution'); |
| 436 | + modeSwitchRequested = 'manual'; |
| 437 | + ctx.getAbortController()?.abort(); |
| 438 | + } |
| 439 | + }; |
| 440 | + process.on('workflow:mode-change', modeChangeHandler); |
| 441 | + |
| 442 | + try { |
| 443 | + await runStepResume(ctx, { |
| 444 | + resumePrompt: nextPrompt.content, |
| 445 | + resumeMonitoringId: machineCtx.currentMonitoringId, |
| 446 | + source: 'controller', |
| 447 | + }); |
| 448 | + } catch (error) { |
| 449 | + if (error instanceof Error && error.name === 'AbortError') { |
| 450 | + if (modeSwitchRequested === 'manual') { |
| 451 | + debug('[Runner:delegated:autonomous] Prompt execution aborted due to mode switch to manual'); |
| 452 | + ctx.emitter.updateAgentStatus(uniqueAgentId, 'awaiting'); |
| 453 | + await callbacks.setAutoMode(false); |
| 454 | + } |
| 455 | + return; |
| 456 | + } |
| 457 | + throw error; |
| 458 | + } finally { |
| 459 | + process.removeListener('workflow:mode-change', modeChangeHandler); |
| 460 | + } |
383 | 461 | } |
384 | 462 |
|
385 | 463 | /** |
|
0 commit comments