fix: orchestrator race conditions, diff preview, session snapshot tools, and test fixes
- Fix duplicate inference requests after approval resume by registering orchestrator jobs in activeSessionJobs (ServerModule.launchSessionRun), so the guard in the OrchestrationResumedEvent subscription prevents spurious duplicate resume. - Replace blocking Files.readString in computeToolPreview with withContext(Dispatchers.IO) by making the function suspend — no blocking I/O on the coroutine dispatcher. - Guard orderedIds.add() in rebuildTools against duplicate invocation IDs on replayed ToolInvocationRequestedEvent to prevent duplicate tool records in snapshots. - Add unified-diff preview for file_write tools: computeToolPreview reads existing file and generates ---/+++ diff before emitting ApprovalRequestedEvent. - Render diff preview with color coding in ApprovalSurface (@@ yellow, +/- green/red). - Include current-stage tool records in SessionSnapshot via rebuildTools event replay. - Fix RouterContextBuilderTest: recursive buildPack helper and 2 tests using class-level builder instead of their local builder instances (conversationKeepLast mismatch). - Add suspend keyword to RouterFacadeTest mock, fix buildPack recursion.
This commit is contained in:
@@ -15,9 +15,14 @@ import dev.tamboui.widgets.block.Title
|
||||
import dev.tamboui.widgets.paragraph.Paragraph
|
||||
|
||||
private const val PREVIEW_MAX_LINES = 20
|
||||
private const val DIFF_LINE_MAX_LENGTH = 100
|
||||
|
||||
private val dimStyle = Style.create().dim().gray()
|
||||
private val greenStyle = Style.create().green()
|
||||
private val redStyle = Style.create().red()
|
||||
private val yellowStyle = Style.create().yellow()
|
||||
|
||||
fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
val dimStyle = Style.create().dim().gray()
|
||||
val approval = state.selectedPendingApproval()
|
||||
val pendingDecision = state.pendingDecision
|
||||
|
||||
@@ -32,13 +37,31 @@ fun approvalSurfaceWidget(state: TuiState): Paragraph {
|
||||
val riskSpan = Span.styled(approval.riskSummary, dimStyle)
|
||||
add(Line.from(toolSpan, Span.raw(" "), tierBadge, Span.raw(" "), riskSpan))
|
||||
|
||||
// Command preview (monospace, max 20 lines, truncation notice)
|
||||
if (approval.preview != null) {
|
||||
val previewLines = approval.preview.lines()
|
||||
// Preview: unified diff for file_write/file_edit, raw text otherwise
|
||||
val preview = approval.preview
|
||||
val isDiff = preview?.startsWith("--- a/") == true
|
||||
if (preview != null) {
|
||||
val previewLines = preview.lines()
|
||||
if (previewLines.isNotEmpty()) {
|
||||
val visibleLines = previewLines.take(PREVIEW_MAX_LINES)
|
||||
for (line in visibleLines) {
|
||||
add(Line.from(Span.raw(" $line")))
|
||||
if (isDiff) {
|
||||
val truncated = if (line.length > DIFF_LINE_MAX_LENGTH) {
|
||||
line.take(DIFF_LINE_MAX_LENGTH - 3) + "..."
|
||||
} else {
|
||||
line
|
||||
}
|
||||
val span = when {
|
||||
truncated.startsWith("@@") -> Span.styled(" $truncated", yellowStyle)
|
||||
truncated.startsWith("+++") || truncated.startsWith("---") -> Span.styled(" $truncated", dimStyle)
|
||||
truncated.startsWith("+") -> Span.styled(" $truncated", greenStyle)
|
||||
truncated.startsWith("-") -> Span.styled(" $truncated", redStyle)
|
||||
else -> Span.raw(" $truncated")
|
||||
}
|
||||
add(Line.from(span))
|
||||
} else {
|
||||
add(Line.from(Span.raw(" $line")))
|
||||
}
|
||||
}
|
||||
if (previewLines.size > PREVIEW_MAX_LINES) {
|
||||
val remaining = previewLines.size - PREVIEW_MAX_LINES
|
||||
|
||||
@@ -274,6 +274,21 @@ object SessionsReducer {
|
||||
detail = entry.detail,
|
||||
)
|
||||
}
|
||||
val tools = msg.tools.map { tool ->
|
||||
val displayStatus = when (tool.status) {
|
||||
"COMPLETED" -> ToolDisplayStatus.COMPLETED
|
||||
"FAILED" -> ToolDisplayStatus.FAILED
|
||||
"REJECTED" -> ToolDisplayStatus.REJECTED
|
||||
else -> ToolDisplayStatus.STARTED
|
||||
}
|
||||
TuiToolRecord(
|
||||
name = tool.name,
|
||||
tier = tool.tier,
|
||||
status = displayStatus,
|
||||
argsPreview = null,
|
||||
diff = tool.diff,
|
||||
)
|
||||
}
|
||||
val summary = SessionSummary(
|
||||
id = msg.sessionId.value,
|
||||
status = status,
|
||||
@@ -284,6 +299,7 @@ object SessionsReducer {
|
||||
currentStageId = msg.state.currentStageId,
|
||||
pendingApproval = approvalInfo,
|
||||
recentEvents = eventEntries,
|
||||
tools = tools,
|
||||
)
|
||||
val selected = sessionState.selectedId ?: msg.sessionId.value
|
||||
return sessionState.copy(
|
||||
|
||||
Reference in New Issue
Block a user