feat(state.db): reasoning_content + api_call_count (Phase 4)

Hermes v2026.4.23 added two columns to state.db:
- messages.reasoning_content — newer richer reasoning channel some
  providers emit alongside the legacy messages.reasoning blob.
- sessions.api_call_count — distinct from tool_call_count; counts
  per-turn API round-trips so the user can see the cost breakdown.

ScarfCore models:
- HermesMessage gains reasoningContent: String? + computed
  preferredReasoning + updated hasReasoning to consider both
  channels.
- HermesSession gains apiCallCount: Int (default 0 for old hosts).

ScarfCore HermesDataService:
- hasV011Schema flag detects both new columns via PRAGMA
  table_info; only flips true when BOTH are present (partial
  migrations stay on the v0.7 path to avoid runtime "no such
  column" errors).
- sessionColumns / messageColumns / searchMessages SELECT lists
  conditionally append the new columns.
- sessionFromRow / messageFromRow read them defensively (column
  index 20 / 11 respectively when v0.11 schema is on).

UI surfacing:
- Mac SessionDetailView shows "<N> API" label next to msgs/tools
  when apiCallCount > 0.
- Mac Dashboard SessionRow + iOS Dashboard sessionRow add a
  network-icon chip with the API call count.
- Mac RichMessageBubble + iOS MessageBubble switch to
  message.preferredReasoning for the disclosure body.

Verified: ScarfCore + Mac + iOS build. 179/179 ScarfCore tests pass
unchanged (existing tests didn't construct sessions/messages with
the new fields; defaults preserve behaviour).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-25 09:27:22 +02:00
parent 751c9e6778
commit 8057beb001
8 changed files with 119 additions and 17 deletions
+3 -1
View File
@@ -990,7 +990,9 @@ private struct MessageBubble: View {
HStack(alignment: .bottom) {
if message.isUser { Spacer(minLength: 40) }
VStack(alignment: message.isUser ? .trailing : .leading, spacing: 4) {
if message.hasReasoning, let r = message.reasoning, !r.isEmpty {
// v2.5: prefer reasoning_content (Hermes v0.11+);
// fall back to legacy reasoning when only it's set.
if message.hasReasoning, let r = message.preferredReasoning, !r.isEmpty {
ReasoningDisclosure(reasoning: r)
}
// Only render the bubble when there's actual text
@@ -220,6 +220,11 @@ struct DashboardView: View {
.font(.caption)
.foregroundStyle(.secondary)
}
if session.apiCallCount > 0 {
Label("\(session.apiCallCount)", systemImage: "network")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
if let projectName = vm.projectName(for: session) {
Label(projectName, systemImage: "folder.fill")