Skip to content

Commit e856b3f

Browse files
committed
feat(ui): improve prompt queue persistence and status display
- Persist prompt queue state across emissions in input emitter - Update status footer text for autonomous mode toggle - Preserve queue info when clearing input state for UI visibility - Emit queue state immediately in controller for better UX
1 parent 3245f97 commit e856b3f

File tree

4 files changed

+60
-24
lines changed

4 files changed

+60
-24
lines changed

src/cli/tui/routes/workflow/components/output/status-footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function StatusFooter(props: StatusFooterProps) {
2121
return (
2222
<box paddingLeft={1} paddingRight={1}>
2323
<text fg={themeCtx.theme.textMuted}>
24-
[↑↓] Navigate [ENTER] Expand/View [Tab] Toggle Panel [H] History [P] Pause [Ctrl+S] Skip [Esc] Stop{props.autonomousMode ? ' [Shift+Tab] Disable Auto' : ''}
24+
[↑↓] Navigate [ENTER] Expand/View [Tab] Toggle Panel [H] History [P] Pause [Ctrl+S] Skip [Esc] Stop [Shift+Tab] {props.autonomousMode ? 'Disable' : 'Enable'} Auto
2525
</text>
2626
</box>
2727
)

src/cli/tui/routes/workflow/context/ui-state/actions/workflow-actions.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,22 @@ export function createWorkflowActions(ctx: WorkflowActionsContext) {
4444

4545
function setInputState(inputState: InputState | null): void {
4646
const state = ctx.getState()
47-
ctx.setState({ ...state, inputState })
48-
if (inputState && inputState.active) {
47+
const prevQueue = state.inputState?.queuedPrompts
48+
const prevIndex = state.inputState?.currentIndex
49+
50+
// Persist queue from previous state if new state doesn't have one
51+
let mergedState = inputState
52+
if (inputState && !inputState.queuedPrompts && prevQueue && prevQueue.length > 0) {
53+
mergedState = { ...inputState, queuedPrompts: prevQueue, currentIndex: prevIndex }
54+
} else if (!inputState && prevQueue && prevQueue.length > 0) {
55+
// Even when clearing, preserve queue info for UI display
56+
mergedState = { active: false, queuedPrompts: prevQueue, currentIndex: prevIndex }
57+
}
58+
59+
ctx.setState({ ...state, inputState: mergedState })
60+
if (mergedState && mergedState.active) {
4961
// Only show "Paused" for manual pause (no queue), not for chained prompts
50-
const hasQueue = inputState.queuedPrompts && inputState.queuedPrompts.length > 0
62+
const hasQueue = mergedState.queuedPrompts && mergedState.queuedPrompts.length > 0
5163
if (!hasQueue) {
5264
setWorkflowStatus("paused")
5365
}

src/workflows/input/emitter.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,27 @@ import type {
1717
* Create an input event emitter that uses the workflow event bus
1818
*/
1919
export function createInputEmitter(workflowEmitter: WorkflowEventEmitter): InputEventEmitter {
20+
// Persist queue state across emissions - only clear on explicit cancel/workflow end
21+
let persistedQueue: { name: string; label: string; content: string }[] = [];
22+
let persistedIndex = 0;
23+
2024
return {
2125
emitWaiting(data: WaitingEventData): void {
2226
debug('[InputEmitter] Emitting waiting: step=%d, queueLength=%d',
2327
data.stepIndex, data.promptQueue.length);
2428

29+
// Update persisted queue
30+
persistedQueue = data.promptQueue.map(p => ({
31+
name: p.name,
32+
label: p.label,
33+
content: p.content,
34+
}));
35+
persistedIndex = data.promptQueueIndex;
36+
2537
workflowEmitter.setInputState({
2638
active: true,
27-
queuedPrompts: data.promptQueue.map(p => ({
28-
name: p.name,
29-
label: p.label,
30-
content: p.content,
31-
})),
32-
currentIndex: data.promptQueueIndex,
39+
queuedPrompts: persistedQueue,
40+
currentIndex: persistedIndex,
3341
monitoringId: data.monitoringId,
3442
});
3543
},
@@ -38,27 +46,33 @@ export function createInputEmitter(workflowEmitter: WorkflowEventEmitter): Input
3846
debug('[InputEmitter] Emitting received: source=%s, inputLength=%d',
3947
data.source, data.input.length);
4048

41-
// Preserve queue info with active=false so step indicator shows while agent runs
49+
// Update persisted state if queue provided
4250
if (data.promptQueue && data.promptQueue.length > 0) {
43-
workflowEmitter.setInputState({
44-
active: false,
45-
queuedPrompts: data.promptQueue.map(p => ({
46-
name: p.name,
47-
label: p.label,
48-
content: p.content,
49-
})),
50-
currentIndex: data.promptQueueIndex ?? 0,
51-
});
52-
} else {
53-
// No queue - clear input state
54-
workflowEmitter.setInputState(null);
51+
persistedQueue = data.promptQueue.map(p => ({
52+
name: p.name,
53+
label: p.label,
54+
content: p.content,
55+
}));
56+
persistedIndex = data.promptQueueIndex ?? 0;
57+
} else if (data.promptQueueIndex !== undefined) {
58+
// Just update index if provided
59+
persistedIndex = data.promptQueueIndex;
5560
}
61+
62+
// Always emit with persisted queue (never clear mid-workflow)
63+
workflowEmitter.setInputState({
64+
active: false,
65+
queuedPrompts: persistedQueue,
66+
currentIndex: persistedIndex,
67+
});
5668
},
5769

5870
emitCanceled(): void {
5971
debug('[InputEmitter] Emitting canceled');
6072

61-
// Clear input state
73+
// Clear persisted state and input state
74+
persistedQueue = [];
75+
persistedIndex = 0;
6276
workflowEmitter.setInputState(null);
6377
},
6478
};

src/workflows/input/providers/controller.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ export class ControllerInputProvider implements InputProvider {
6868
async getInput(context: InputContext): Promise<InputResult> {
6969
this.aborted = false;
7070

71+
// Emit queue state immediately so UI shows step info while controller runs
72+
if (context.promptQueue.length > 0) {
73+
this.emitter.emitWaiting({
74+
stepIndex: context.stepIndex,
75+
promptQueue: context.promptQueue,
76+
promptQueueIndex: context.promptQueueIndex,
77+
monitoringId: context.stepOutput.monitoringId,
78+
});
79+
}
80+
7181
const config = await this.getControllerConfig();
7282
if (!config) {
7383
debug('[Controller] No controller config, falling back to skip');

0 commit comments

Comments
 (0)