diff --git a/mac/Sources/Byre/Chat.swift b/mac/Sources/Byre/Chat.swift index 45962bc..f8df547 100644 --- a/mac/Sources/Byre/Chat.swift +++ b/mac/Sources/Byre/Chat.swift @@ -945,7 +945,7 @@ struct RowView: View { } content: { VStack(alignment: .leading, spacing: 6) { PreBox(text: input) - ToolResult(result: row.result, unfinished: row.unfinished) + ToolResult(result: row.result, unfinished: row.unfinished, started: row.started) } .padding(.vertical, 6) } @@ -961,7 +961,7 @@ struct RowView: View { ForEach(row.body!.rows) { child in RowView(row: child, model: model, nested: true) } - ToolResult(result: row.result, unfinished: row.unfinished) + ToolResult(result: row.result, unfinished: row.unfinished, started: row.started) } } @@ -1026,17 +1026,34 @@ struct RowView: View { private struct ToolResult: View { let result: String? let unfinished: Bool + let started: Date? var body: some View { if let result { PreBox(text: result) + } else if unfinished || started == nil { + label(unfinished ? "no result" : "running…") } else { - Text(unfinished ? "no result" : "running…") - .font(Fonts.font(12, italic: true)) - .foregroundStyle(Palette.muted) - .padding(.vertical, 4) + TimelineView(.periodic(from: .now, by: 1)) { ctx in + label("running… \(elapsed(ctx.date.timeIntervalSince(started!)))") + } } } + + private func label(_ text: String) -> some View { + Text(text) + .font(Fonts.font(12, italic: true)) + .foregroundStyle(Palette.muted) + .padding(.vertical, 4) + } + + private func elapsed(_ t: TimeInterval) -> String { + let s = max(0, Int(t)) + if s < 60 { + return "\(s)s" + } + return "\(s / 60)m \(s % 60 < 10 ? "0" : "")\(s % 60)s" + } } private struct RunSummary: View { diff --git a/mac/Sources/Byre/TranscriptBuilder.swift b/mac/Sources/Byre/TranscriptBuilder.swift index 809e216..b26b6c9 100644 --- a/mac/Sources/Byre/TranscriptBuilder.swift +++ b/mac/Sources/Byre/TranscriptBuilder.swift @@ -73,6 +73,7 @@ final class Row: Identifiable { var tag = false var result: String? var unfinished = false + var started: Date? var status = "starting…" var statusState = SubagentState.running var active = false @@ -457,6 +458,7 @@ final class TranscriptBuilder { let sub = ctx.subagents[parent] if ev.kind == "agent.tool_call" && isSubagentCall(p) { let row = renderSubagent(p) + row.started = ev.createdAt if let sub { sub.run = nil sub.row.body!.rows.append(row) @@ -468,7 +470,9 @@ final class TranscriptBuilder { } if ev.kind == "agent.tool_call" { let name = toolLabel(jsString(p["name"])) - addToRun(renderToolCall(p), Call(name: name, target: toolTarget(p["input"] ?? nil, worktree())), parent) + let row = renderToolCall(p) + row.started = ev.createdAt + addToRun(row, Call(name: name, target: toolTarget(p["input"] ?? nil, worktree())), parent) continue } guard let row = renderEvent(ev) else {