fix: implement stage_complete tool for LLM-driven stage completion
- Add STAGE_COMPLETE_TOOL constant and meta-tool handler in executeStage loop: when LLM calls stage_complete, break out of tool dispatch loop and proceed to normal validation/transition - Inject stage_complete tool definition in every inference request so the LLM always has the option to signal stage completion - Replace expand-all diff with scrollable diff window in approval surface (ctrl+x toggles, up/down scrolls, shows line range)
This commit is contained in:
@@ -27,6 +27,7 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
val pendingDecision = state.pendingDecision
|
||||
|
||||
val diffExpanded = state.diffExpanded
|
||||
val scrollOffset = state.diffScrollOffset
|
||||
|
||||
val lines = buildList<Line> {
|
||||
if (approval == null) {
|
||||
@@ -45,8 +46,11 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
if (preview != null) {
|
||||
val previewLines = preview.lines()
|
||||
if (previewLines.isNotEmpty()) {
|
||||
val totalLines = previewLines.size
|
||||
val visibleLines = if (diffExpanded) {
|
||||
previewLines
|
||||
val from = scrollOffset.coerceIn(0, maxOf(0, totalLines - 1))
|
||||
val to = minOf(from + PREVIEW_MAX_LINES, totalLines)
|
||||
previewLines.subList(from, to)
|
||||
} else {
|
||||
previewLines.take(PREVIEW_MAX_LINES)
|
||||
}
|
||||
@@ -69,12 +73,13 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
add(Line.from(Span.raw(" $line")))
|
||||
}
|
||||
}
|
||||
if (!diffExpanded && previewLines.size > PREVIEW_MAX_LINES) {
|
||||
val remaining = previewLines.size - PREVIEW_MAX_LINES
|
||||
add(Line.from(Span.styled(" (preview truncated, $remaining more lines — ctrl+x to expand)", dimStyle)))
|
||||
if (!diffExpanded && totalLines > PREVIEW_MAX_LINES) {
|
||||
val remaining = totalLines - PREVIEW_MAX_LINES
|
||||
add(Line.from(Span.styled(" (preview truncated, $remaining more lines — ctrl+x to expand, \u2191\u2193 to scroll)", dimStyle)))
|
||||
}
|
||||
if (diffExpanded) {
|
||||
add(Line.from(Span.styled(" (full diff — ctrl+x to collapse)", dimStyle)))
|
||||
val showingTo = minOf(scrollOffset + PREVIEW_MAX_LINES, totalLines)
|
||||
add(Line.from(Span.styled(" lines ${scrollOffset + 1}\u2013$showingTo of $totalLines — \u2191\u2193 scroll, ctrl+x collapse", dimStyle)))
|
||||
}
|
||||
} else {
|
||||
add(Line.from(Span.styled(" no preview available", dimStyle)))
|
||||
|
||||
@@ -72,6 +72,8 @@ object KeyResolver {
|
||||
KeyEvent.Enter -> Action.SubmitApprovalDecision
|
||||
KeyEvent.Escape -> Action.CancelInput
|
||||
KeyEvent.Cancel -> Action.CancelInput
|
||||
KeyEvent.NavUp -> Action.NavigateUp
|
||||
KeyEvent.NavDown -> Action.NavigateDown
|
||||
KeyEvent.ReturnToSessionList -> Action.ReturnToSessionList
|
||||
KeyEvent.ToggleEventStrip -> Action.ToggleEventStrip
|
||||
KeyEvent.ToggleDiffExpand -> Action.ToggleDiffExpand
|
||||
|
||||
@@ -118,7 +118,10 @@ object RootReducer {
|
||||
else withBgReset.copy(eventStripVisible = !withBgReset.eventStripVisible)
|
||||
}
|
||||
|
||||
is Action.ToggleDiffExpand -> withBgReset.copy(diffExpanded = !withBgReset.diffExpanded)
|
||||
is Action.ToggleDiffExpand -> withBgReset.copy(
|
||||
diffExpanded = !withBgReset.diffExpanded,
|
||||
diffScrollOffset = 0,
|
||||
)
|
||||
|
||||
// Input history append on SubmitInput in IN_SESSION + ROUTER (ARCH-HISTORY-1),
|
||||
// or enter a selected session from the list in IDLE.
|
||||
@@ -188,9 +191,11 @@ object RootReducer {
|
||||
}
|
||||
}
|
||||
|
||||
// Input history navigation on NavigateUp in IN_SESSION + ROUTER (ARCH-HISTORY-2/3).
|
||||
// Diff scrolling in APPROVAL mode when expanded.
|
||||
is Action.NavigateUp -> {
|
||||
if (withBgReset.displayState == DisplayState.IN_SESSION && prevInputMode == InputMode.ROUTER) {
|
||||
if (withBgReset.displayState == DisplayState.APPROVAL && withBgReset.diffExpanded) {
|
||||
withBgReset.copy(diffScrollOffset = maxOf(0, withBgReset.diffScrollOffset - 1))
|
||||
} else if (withBgReset.displayState == DisplayState.IN_SESSION && prevInputMode == InputMode.ROUTER) {
|
||||
val selectedId = withBgReset.sessions.selectedId
|
||||
if (selectedId != null) {
|
||||
val history = withBgReset.inputHistory[selectedId] ?: emptyList()
|
||||
@@ -227,9 +232,11 @@ object RootReducer {
|
||||
}
|
||||
}
|
||||
|
||||
// Input history navigation on NavigateDown in IN_SESSION + ROUTER (ARCH-HISTORY-2/3).
|
||||
// Diff scrolling in APPROVAL mode when expanded.
|
||||
is Action.NavigateDown -> {
|
||||
if (withBgReset.displayState == DisplayState.IN_SESSION && prevInputMode == InputMode.ROUTER) {
|
||||
if (withBgReset.displayState == DisplayState.APPROVAL && withBgReset.diffExpanded) {
|
||||
withBgReset.copy(diffScrollOffset = withBgReset.diffScrollOffset + 1)
|
||||
} else if (withBgReset.displayState == DisplayState.IN_SESSION && prevInputMode == InputMode.ROUTER) {
|
||||
val selectedId = withBgReset.sessions.selectedId
|
||||
if (selectedId != null) {
|
||||
val history = withBgReset.inputHistory[selectedId] ?: emptyList()
|
||||
|
||||
@@ -48,6 +48,7 @@ data class TuiState(
|
||||
val routerConnected: Boolean = false,
|
||||
val routerMessages: Map<String, List<String>> = emptyMap(),
|
||||
val diffExpanded: Boolean = false,
|
||||
val diffScrollOffset: Int = 0,
|
||||
val chatMode: ChatMode = ChatMode.CHAT,
|
||||
/**
|
||||
* True from connect until SnapshotComplete observed. Reset to true on Disconnected.
|
||||
|
||||
Reference in New Issue
Block a user