Debug menu: session controls and thinking/verbose
This commit is contained in:
@@ -4,6 +4,7 @@ import SwiftUI
|
|||||||
|
|
||||||
enum DebugActions {
|
enum DebugActions {
|
||||||
private static let verboseDefaultsKey = "clawdis.debug.verboseMain"
|
private static let verboseDefaultsKey = "clawdis.debug.verboseMain"
|
||||||
|
private static let sessionMenuLimit = 12
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static func openAgentEventsWindow() {
|
static func openAgentEventsWindow() {
|
||||||
@@ -183,6 +184,72 @@ enum DebugActions {
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Sessions (thinking / verbose)
|
||||||
|
|
||||||
|
static func recentSessions(limit: Int = sessionMenuLimit) async -> [SessionRow] {
|
||||||
|
let hints = SessionLoader.configHints()
|
||||||
|
let store = SessionLoader.resolveStorePath(override: hints.storePath)
|
||||||
|
let defaults = SessionDefaults(
|
||||||
|
model: hints.model ?? SessionLoader.fallbackModel,
|
||||||
|
contextTokens: hints.contextTokens ?? SessionLoader.fallbackContextTokens)
|
||||||
|
guard let rows = try? await SessionLoader.loadRows(at: store, defaults: defaults) else { return [] }
|
||||||
|
return Array(rows.prefix(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func updateSession(
|
||||||
|
key: String,
|
||||||
|
thinking: String?,
|
||||||
|
verbose: String?) async throws
|
||||||
|
{
|
||||||
|
let hints = SessionLoader.configHints()
|
||||||
|
let store = SessionLoader.resolveStorePath(override: hints.storePath)
|
||||||
|
let url = URL(fileURLWithPath: store)
|
||||||
|
guard FileManager.default.fileExists(atPath: store) else {
|
||||||
|
throw DebugActionError.message("Session store missing at \(store)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
var decoded = try JSONDecoder().decode([String: SessionEntryRecord].self, from: data)
|
||||||
|
var entry = decoded[key] ?? SessionEntryRecord(
|
||||||
|
sessionId: nil,
|
||||||
|
updatedAt: Date().timeIntervalSince1970 * 1000,
|
||||||
|
systemSent: nil,
|
||||||
|
abortedLastRun: nil,
|
||||||
|
thinkingLevel: nil,
|
||||||
|
verboseLevel: nil,
|
||||||
|
inputTokens: nil,
|
||||||
|
outputTokens: nil,
|
||||||
|
totalTokens: nil,
|
||||||
|
model: nil,
|
||||||
|
contextTokens: nil)
|
||||||
|
|
||||||
|
entry = SessionEntryRecord(
|
||||||
|
sessionId: entry.sessionId,
|
||||||
|
updatedAt: Date().timeIntervalSince1970 * 1000,
|
||||||
|
systemSent: entry.systemSent,
|
||||||
|
abortedLastRun: entry.abortedLastRun,
|
||||||
|
thinkingLevel: thinking,
|
||||||
|
verboseLevel: verbose,
|
||||||
|
inputTokens: entry.inputTokens,
|
||||||
|
outputTokens: entry.outputTokens,
|
||||||
|
totalTokens: entry.totalTokens,
|
||||||
|
model: entry.model,
|
||||||
|
contextTokens: entry.contextTokens)
|
||||||
|
|
||||||
|
decoded[key] = entry
|
||||||
|
let encoded = try JSONEncoder().encode(decoded)
|
||||||
|
try encoded.write(to: url, options: [.atomic])
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
static func openSessionStoreInCode() {
|
||||||
|
let path = SessionLoader.defaultStorePath
|
||||||
|
let proc = Process()
|
||||||
|
proc.launchPath = "/usr/bin/env"
|
||||||
|
proc.arguments = ["code", path]
|
||||||
|
try? proc.run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DebugActionError: LocalizedError {
|
enum DebugActionError: LocalizedError {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct MenuContent: View {
|
|||||||
@Environment(\.openSettings) private var openSettings
|
@Environment(\.openSettings) private var openSettings
|
||||||
@State private var availableMics: [AudioInputDevice] = []
|
@State private var availableMics: [AudioInputDevice] = []
|
||||||
@State private var loadingMics = false
|
@State private var loadingMics = false
|
||||||
|
@State private var sessionMenu: [SessionRow] = []
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
@@ -43,6 +44,49 @@ struct MenuContent: View {
|
|||||||
}
|
}
|
||||||
if self.state.debugPaneEnabled {
|
if self.state.debugPaneEnabled {
|
||||||
Menu("Debug") {
|
Menu("Debug") {
|
||||||
|
Menu("Sessions") {
|
||||||
|
ForEach(self.sessionMenu) { row in
|
||||||
|
Menu(row.key) {
|
||||||
|
Menu("Thinking") {
|
||||||
|
ForEach(["low", "medium", "high", "default"], id: \.self) { level in
|
||||||
|
let normalized = level == "default" ? nil : level
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
try? await DebugActions.updateSession(
|
||||||
|
key: row.key,
|
||||||
|
thinking: normalized,
|
||||||
|
verbose: row.verboseLevel)
|
||||||
|
await self.reloadSessionMenu()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(level.capitalized, systemImage: row.thinkingLevel == normalized ? "checkmark" : "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Menu("Verbose") {
|
||||||
|
ForEach(["on", "off", "default"], id: \.self) { level in
|
||||||
|
let normalized = level == "default" ? nil : level
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
try? await DebugActions.updateSession(
|
||||||
|
key: row.key,
|
||||||
|
thinking: row.thinkingLevel,
|
||||||
|
verbose: normalized)
|
||||||
|
await self.reloadSessionMenu()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(level.capitalized, systemImage: row.verboseLevel == normalized ? "checkmark" : "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
DebugActions.openSessionStoreInCode()
|
||||||
|
} label: {
|
||||||
|
Label("Open Session Log", systemImage: "doc.text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Button {
|
Button {
|
||||||
DebugActions.openConfigFolder()
|
DebugActions.openConfigFolder()
|
||||||
} label: {
|
} label: {
|
||||||
@@ -113,6 +157,9 @@ struct MenuContent: View {
|
|||||||
await self.loadMicrophones(force: true)
|
await self.loadMicrophones(force: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.task {
|
||||||
|
await self.reloadSessionMenu()
|
||||||
|
}
|
||||||
.task {
|
.task {
|
||||||
VoicePushToTalkHotkey.shared.setEnabled(voiceWakeSupported && self.state.voicePushToTalkEnabled)
|
VoicePushToTalkHotkey.shared.setEnabled(voiceWakeSupported && self.state.voicePushToTalkEnabled)
|
||||||
}
|
}
|
||||||
@@ -306,6 +353,11 @@ struct MenuContent: View {
|
|||||||
return "System default"
|
return "System default"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func reloadSessionMenu() async {
|
||||||
|
self.sessionMenu = await DebugActions.recentSessions()
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func loadMicrophones(force: Bool = false) async {
|
private func loadMicrophones(force: Bool = false) async {
|
||||||
guard self.showVoiceWakeMicPicker else {
|
guard self.showVoiceWakeMicPicker else {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SessionEntryRecord: Decodable {
|
struct SessionEntryRecord: Codable {
|
||||||
let sessionId: String?
|
let sessionId: String?
|
||||||
let updatedAt: Double?
|
let updatedAt: Double?
|
||||||
let systemSent: Bool?
|
let systemSent: Bool?
|
||||||
|
|||||||
Reference in New Issue
Block a user