mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
feat: Show tool output in Activity inspector (#12)
Add tool result display to the Activity detail pane. When selecting a tool call, the inspector now shows Arguments → Output → Assistant Message, giving full visibility into what was requested, what came back, and how the assistant interpreted it. - Add fetchToolResult(callId:) query to HermesDataService - Fetch tool result on entry selection in ActivityViewModel - Display output in styled monospaced box in detail pane - Render assistant message with MarkdownContentView Closes #12 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,17 @@ actor HermesDataService {
|
||||
return messages
|
||||
}
|
||||
|
||||
func fetchToolResult(callId: String) -> String? {
|
||||
guard let db else { return nil }
|
||||
let sql = "SELECT content FROM messages WHERE role = 'tool' AND tool_call_id = ? LIMIT 1"
|
||||
var stmt: OpaquePointer?
|
||||
guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else { return nil }
|
||||
defer { sqlite3_finalize(stmt) }
|
||||
sqlite3_bind_text(stmt, 1, callId, -1, sqliteTransient)
|
||||
guard sqlite3_step(stmt) == SQLITE_ROW else { return nil }
|
||||
return columnText(stmt!, 0)
|
||||
}
|
||||
|
||||
func fetchRecentToolCalls(limit: Int = QueryDefaults.toolCallLimit) -> [HermesMessage] {
|
||||
guard let db else { return [] }
|
||||
let sql = """
|
||||
|
||||
@@ -8,6 +8,7 @@ final class ActivityViewModel {
|
||||
var filterKind: ToolKind?
|
||||
var filterSessionId: String?
|
||||
var selectedEntry: ActivityEntry?
|
||||
var toolResult: String?
|
||||
var sessionPreviews: [String: String] = [:]
|
||||
var isLoading = true
|
||||
|
||||
@@ -54,6 +55,15 @@ final class ActivityViewModel {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func selectEntry(_ entry: ActivityEntry?) async {
|
||||
selectedEntry = entry
|
||||
if let entry {
|
||||
toolResult = await dataService.fetchToolResult(callId: entry.id)
|
||||
} else {
|
||||
toolResult = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup() async {
|
||||
await dataService.close()
|
||||
}
|
||||
|
||||
@@ -57,11 +57,8 @@ struct ActivityView: View {
|
||||
List(selection: Binding(
|
||||
get: { viewModel.selectedEntry?.id },
|
||||
set: { id in
|
||||
if let id {
|
||||
viewModel.selectedEntry = viewModel.filteredActivity.first(where: { $0.id == id })
|
||||
} else {
|
||||
viewModel.selectedEntry = nil
|
||||
}
|
||||
let entry = id.flatMap { id in viewModel.filteredActivity.first(where: { $0.id == id }) }
|
||||
Task { await viewModel.selectEntry(entry) }
|
||||
}
|
||||
)) {
|
||||
ForEach(viewModel.filteredActivity) { entry in
|
||||
@@ -146,14 +143,32 @@ struct ActivityView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
}
|
||||
|
||||
if let result = viewModel.toolResult, !result.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Output")
|
||||
.font(.caption.bold())
|
||||
.foregroundStyle(.secondary)
|
||||
Text(result)
|
||||
.font(.system(.caption, design: .monospaced))
|
||||
.textSelection(.enabled)
|
||||
.lineLimit(50)
|
||||
.padding(8)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(.textBackgroundColor).opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.strokeBorder(.quaternary, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if !entry.messageContent.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Assistant Message")
|
||||
.font(.caption.bold())
|
||||
.foregroundStyle(.secondary)
|
||||
Text(entry.messageContent)
|
||||
.font(.caption)
|
||||
.textSelection(.enabled)
|
||||
MarkdownContentView(content: entry.messageContent)
|
||||
.padding(8)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(.quaternary.opacity(0.5))
|
||||
|
||||
Reference in New Issue
Block a user