diff --git a/.changeset/fix-interactive-tool-continue.md b/.changeset/fix-interactive-tool-continue.md new file mode 100644 index 00000000..4e895ea5 --- /dev/null +++ b/.changeset/fix-interactive-tool-continue.md @@ -0,0 +1,15 @@ +--- +"perstack": patch +--- + +fix(perstack): handle stoppedByInteractiveTool in TUI execution loop + +When an expert calls an interactive tool and the run stops with status +"stoppedByInteractiveTool", the TUI execution loop now correctly: + +1. Allows the user to continue by entering input (previously the loop exited) +2. Treats the user's input as an interactive tool result (previously it was + treated as a regular text query) + +This fixes the issue where the TUI would hang or display incorrectly when +an interactive tool was called during a conversation. diff --git a/apps/perstack/src/start.ts b/apps/perstack/src/start.ts index 52ea7190..9060b44c 100644 --- a/apps/perstack/src/start.ts +++ b/apps/perstack/src/start.ts @@ -157,6 +157,8 @@ export const startCommand = new Command() // Phase 3: Execution loop let currentQuery: string | null = selection.query let currentJobId = currentCheckpoint?.jobId ?? input.options.jobId + // Track if the next query should be treated as an interactive tool result + let isNextQueryInteractiveToolResult = input.options.interactiveToolCallResult ?? false // Track accumulated events across continues // On first iteration, load all events for the job up to the checkpoint @@ -192,7 +194,7 @@ export const startCommand = new Command() jobId: currentJobId, expertKey: selection.expertKey, input: - input.options.interactiveToolCallResult && currentCheckpoint + isNextQueryInteractiveToolResult && currentCheckpoint ? parseInteractiveToolCallResult(currentQuery, currentCheckpoint) : { text: currentQuery }, experts, @@ -220,16 +222,20 @@ export const startCommand = new Command() const result = await executionResult // Check if user wants to continue - if ( - result.nextQuery && - (runResult.status === "completed" || - runResult.status === "stoppedByExceededMaxSteps" || - runResult.status === "stoppedByError") - ) { + const canContinue = + runResult.status === "completed" || + runResult.status === "stoppedByExceededMaxSteps" || + runResult.status === "stoppedByError" || + runResult.status === "stoppedByInteractiveTool" + + if (result.nextQuery && canContinue) { currentQuery = result.nextQuery currentCheckpoint = runResult currentJobId = runResult.jobId + // If the run stopped for interactive tool, the next query is an interactive tool result + isNextQueryInteractiveToolResult = runResult.status === "stoppedByInteractiveTool" + // Accumulate events from the completed run for the next iteration const newRunEvents = getEventContents(runResult.jobId, runResult.runId) if (accumulatedEvents) {