Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/fix-interactive-tool-continue.md
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 13 additions & 7 deletions apps/perstack/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down