mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
feat(ios-memory): hermes memory reset on iOS too (cross-platform parity)
Mac shipped the toolbar Reset button in Phase 5; iOS gets it in the final verification pass for parity. iOS MemoryListView: - Toolbar button (counterclockwise icon) opens a destructive confirmation dialog matching the Mac copy. - resetMemory() shells out via context.makeTransport().runProcess, using the same PATH-prefix trick IOSSettingsViewModel.saveValue uses so non-interactive remote shells find hermes in ~/.local/bin / /opt/homebrew/bin / ~/.hermes/bin. - Success and failure both surface alerts (success message confirms the wipe; failure surfaces stderr+stdout combined). Verified: iOS build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@ import ScarfCore
|
|||||||
/// `IOSMemoryViewModel` which lives in ScarfCore.
|
/// `IOSMemoryViewModel` which lives in ScarfCore.
|
||||||
struct MemoryListView: View {
|
struct MemoryListView: View {
|
||||||
let config: IOSServerConfig
|
let config: IOSServerConfig
|
||||||
|
@State private var showResetConfirm = false
|
||||||
|
@State private var resetError: String?
|
||||||
|
@State private var resetSucceeded = false
|
||||||
|
|
||||||
private static let sharedContextID: ServerID = ServerID(
|
private static let sharedContextID: ServerID = ServerID(
|
||||||
uuidString: "00000000-0000-0000-0000-0000000000A1"
|
uuidString: "00000000-0000-0000-0000-0000000000A1"
|
||||||
@@ -32,6 +35,77 @@ struct MemoryListView: View {
|
|||||||
.scarfGoListDensity()
|
.scarfGoListDensity()
|
||||||
.navigationTitle("Memory")
|
.navigationTitle("Memory")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
// v2.5: `hermes memory reset` (Hermes v2026.4.23+) wipes
|
||||||
|
// both MEMORY.md and USER.md atomically. Surfaced as a
|
||||||
|
// toolbar button (smaller fat-finger target than a list
|
||||||
|
// row) gated behind a destructive confirmation dialog.
|
||||||
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
Button {
|
||||||
|
showResetConfirm = true
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "arrow.counterclockwise")
|
||||||
|
}
|
||||||
|
.accessibilityLabel("Reset memory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.confirmationDialog(
|
||||||
|
"Reset memory?",
|
||||||
|
isPresented: $showResetConfirm,
|
||||||
|
titleVisibility: .visible
|
||||||
|
) {
|
||||||
|
Button("Reset", role: .destructive) {
|
||||||
|
Task { await resetMemory(context: ctx) }
|
||||||
|
}
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
} message: {
|
||||||
|
Text("Wipes MEMORY.md and USER.md to empty via `hermes memory reset --yes`. The agent's accumulated knowledge for this server is gone immediately. Use this only when a session went off the rails.")
|
||||||
|
}
|
||||||
|
.alert("Couldn't reset memory", isPresented: Binding(
|
||||||
|
get: { resetError != nil },
|
||||||
|
set: { if !$0 { resetError = nil } }
|
||||||
|
)) {
|
||||||
|
Button("OK") { resetError = nil }
|
||||||
|
} message: {
|
||||||
|
Text(resetError ?? "")
|
||||||
|
}
|
||||||
|
.alert("Memory reset", isPresented: $resetSucceeded) {
|
||||||
|
Button("OK") {}
|
||||||
|
} message: {
|
||||||
|
Text("MEMORY.md and USER.md were cleared on the host.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run `hermes memory reset --yes` over the iOS context's transport
|
||||||
|
/// (Citadel SSH exec). Mirrors the PATH-prefix trick
|
||||||
|
/// IOSSettingsViewModel.saveValue uses so non-interactive shells
|
||||||
|
/// find hermes even when it's in `~/.local/bin` or `/opt/homebrew/bin`.
|
||||||
|
private func resetMemory(context: ServerContext) async {
|
||||||
|
let hermes = context.paths.hermesBinary
|
||||||
|
let script = "PATH=\"$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$HOME/.hermes/bin:$PATH\" \(hermes) memory reset --yes"
|
||||||
|
let ctx = context
|
||||||
|
do {
|
||||||
|
let result = try await Task.detached {
|
||||||
|
try ctx.makeTransport().runProcess(
|
||||||
|
executable: "/bin/sh",
|
||||||
|
args: ["-c", script],
|
||||||
|
stdin: nil,
|
||||||
|
timeout: 15
|
||||||
|
)
|
||||||
|
}.value
|
||||||
|
if result.exitCode == 0 {
|
||||||
|
resetSucceeded = true
|
||||||
|
} else {
|
||||||
|
let stderr = result.stderrString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let stdout = result.stdoutString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let combined = [stderr, stdout].filter { !$0.isEmpty }.joined(separator: "\n")
|
||||||
|
resetError = combined.isEmpty
|
||||||
|
? "hermes memory reset exited with status \(result.exitCode)."
|
||||||
|
: combined
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
resetError = "Couldn't reach Hermes: \(error.localizedDescription)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
Reference in New Issue
Block a user