Skip to content

Commit f9cfeef

Browse files
committed
feat(workflows): add WorkflowMode and StepSession for state management
Introduce WorkflowMode as single source of truth for mode state and StepSession to manage step lifecycle and prompt queues. Refactor signal handlers and runner to use these new modules for consistent state management. - Add WorkflowMode to centralize autoMode and paused state - Implement StepSession to handle step execution and prompt queues - Update signal handlers to delegate to WorkflowMode - Refactor runner to use StepSession for queue management - Maintain backwards compatibility during transition
1 parent a4b9dd4 commit f9cfeef

File tree

15 files changed

+802
-62
lines changed

15 files changed

+802
-62
lines changed

src/workflows/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export * from './templates/index.js';
33
export * from './runner/index.js';
44
export * from './step/index.js';
55
export * from './directives/index.js';
6+
export * from './mode/index.js';
7+
export * from './session/index.js';

src/workflows/mode/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* WorkflowMode Module
3+
*
4+
* Single source of truth for workflow mode state.
5+
*/
6+
7+
export { WorkflowMode } from './mode.js';
8+
export type {
9+
WorkflowModeState,
10+
ModeProviders,
11+
ModeEvent,
12+
ModeChangedEvent,
13+
PausedEvent,
14+
ResumedEvent,
15+
ModeEventListener,
16+
} from './types.js';

src/workflows/mode/mode.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* WorkflowMode
3+
*
4+
* Single source of truth for workflow mode state.
5+
* Owns autoMode and paused state, derives which provider to use.
6+
* Eliminates sync issues between autoMode and activeProvider.
7+
*/
8+
9+
import { debug } from '../../shared/logging/logger.js';
10+
import type { InputProvider } from '../input/types.js';
11+
import type { ModeProviders, ModeEvent, ModeEventListener } from './types.js';
12+
13+
export class WorkflowMode {
14+
private _autoMode: boolean = false;
15+
private _paused: boolean = false;
16+
private readonly providers: ModeProviders;
17+
private readonly listeners: Set<ModeEventListener> = new Set();
18+
19+
constructor(providers: ModeProviders) {
20+
this.providers = providers;
21+
}
22+
23+
/**
24+
* Whether auto (controller) mode is enabled
25+
*/
26+
get autoMode(): boolean {
27+
return this._autoMode;
28+
}
29+
30+
/**
31+
* Whether workflow is paused
32+
*/
33+
get paused(): boolean {
34+
return this._paused;
35+
}
36+
37+
/**
38+
* Whether user input should be used (derived from autoMode and paused)
39+
*/
40+
get shouldUseUserInput(): boolean {
41+
// Use user input if paused OR if not in auto mode
42+
return this._paused || !this._autoMode;
43+
}
44+
45+
/**
46+
* Get the active input provider (derived from state)
47+
* This is the key improvement - no separate activeProvider state to sync
48+
*/
49+
getActiveProvider(): InputProvider {
50+
return this.shouldUseUserInput
51+
? this.providers.userInput
52+
: this.providers.controllerInput;
53+
}
54+
55+
/**
56+
* Get the user input provider directly
57+
*/
58+
getUserInput(): InputProvider {
59+
return this.providers.userInput;
60+
}
61+
62+
/**
63+
* Get the controller input provider directly
64+
*/
65+
getControllerInput(): InputProvider {
66+
return this.providers.controllerInput;
67+
}
68+
69+
/**
70+
* Enable auto (controller) mode
71+
*/
72+
enableAutoMode(): void {
73+
if (this._autoMode) {
74+
return;
75+
}
76+
77+
debug('[WorkflowMode] Enabling auto mode');
78+
79+
// Deactivate old provider
80+
const oldProvider = this.getActiveProvider();
81+
oldProvider.deactivate?.();
82+
83+
this._autoMode = true;
84+
85+
// Activate new provider
86+
const newProvider = this.getActiveProvider();
87+
newProvider.activate?.();
88+
89+
this.emit({ type: 'mode-changed', autoMode: true });
90+
}
91+
92+
/**
93+
* Disable auto mode (switch to manual/user mode)
94+
*/
95+
disableAutoMode(): void {
96+
if (!this._autoMode) {
97+
return;
98+
}
99+
100+
debug('[WorkflowMode] Disabling auto mode');
101+
102+
// Deactivate old provider
103+
const oldProvider = this.getActiveProvider();
104+
oldProvider.deactivate?.();
105+
106+
this._autoMode = false;
107+
108+
// Activate new provider
109+
const newProvider = this.getActiveProvider();
110+
newProvider.activate?.();
111+
112+
this.emit({ type: 'mode-changed', autoMode: false });
113+
}
114+
115+
/**
116+
* Set auto mode on/off
117+
*/
118+
setAutoMode(enabled: boolean): void {
119+
if (enabled) {
120+
this.enableAutoMode();
121+
} else {
122+
this.disableAutoMode();
123+
}
124+
}
125+
126+
/**
127+
* Pause the workflow (switches to user input until resumed)
128+
*/
129+
pause(): void {
130+
if (this._paused) {
131+
return;
132+
}
133+
134+
debug('[WorkflowMode] Pausing');
135+
136+
// If we were in auto mode, deactivate controller and activate user
137+
if (this._autoMode) {
138+
this.providers.controllerInput.deactivate?.();
139+
this.providers.userInput.activate?.();
140+
}
141+
142+
this._paused = true;
143+
this.emit({ type: 'paused' });
144+
}
145+
146+
/**
147+
* Resume from pause
148+
*/
149+
resume(): void {
150+
if (!this._paused) {
151+
return;
152+
}
153+
154+
debug('[WorkflowMode] Resuming');
155+
156+
// If in auto mode, switch back to controller
157+
if (this._autoMode) {
158+
this.providers.userInput.deactivate?.();
159+
this.providers.controllerInput.activate?.();
160+
}
161+
162+
this._paused = false;
163+
this.emit({ type: 'resumed' });
164+
}
165+
166+
/**
167+
* Subscribe to mode events
168+
*/
169+
subscribe(listener: ModeEventListener): () => void {
170+
this.listeners.add(listener);
171+
return () => this.listeners.delete(listener);
172+
}
173+
174+
private emit(event: ModeEvent): void {
175+
for (const listener of this.listeners) {
176+
listener(event);
177+
}
178+
}
179+
}

src/workflows/mode/types.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* WorkflowMode Types
3+
*
4+
* Single source of truth for mode state.
5+
* Eliminates sync issues between autoMode and activeProvider.
6+
*/
7+
8+
import type { InputProvider } from '../input/types.js';
9+
10+
/**
11+
* Mode state - tracks auto mode and pause state
12+
*/
13+
export interface WorkflowModeState {
14+
/** Whether auto (controller) mode is enabled */
15+
autoMode: boolean;
16+
17+
/** Whether workflow is paused */
18+
paused: boolean;
19+
}
20+
21+
/**
22+
* Input providers required by WorkflowMode
23+
*/
24+
export interface ModeProviders {
25+
/** User input provider (terminal input) */
26+
userInput: InputProvider;
27+
28+
/** Controller input provider (autonomous mode) */
29+
controllerInput: InputProvider;
30+
}
31+
32+
/**
33+
* Events emitted by WorkflowMode
34+
*/
35+
export type ModeEventType = 'mode-changed' | 'paused' | 'resumed';
36+
37+
export interface ModeChangedEvent {
38+
type: 'mode-changed';
39+
autoMode: boolean;
40+
}
41+
42+
export interface PausedEvent {
43+
type: 'paused';
44+
}
45+
46+
export interface ResumedEvent {
47+
type: 'resumed';
48+
}
49+
50+
export type ModeEvent = ModeChangedEvent | PausedEvent | ResumedEvent;
51+
52+
/**
53+
* Listener for mode events
54+
*/
55+
export type ModeEventListener = (event: ModeEvent) => void;

0 commit comments

Comments
 (0)