Skip to content

Commit c75b119

Browse files
author
itsdodobitch
committed
Add SOUL.md support and fix window resizing
1 parent 469814c commit c75b119

12 files changed

Lines changed: 269 additions & 145 deletions

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ host.
2828
- remote editing for:
2929
- `~/.hermes/memories/USER.md`
3030
- `~/.hermes/memories/MEMORY.md`
31+
- `~/.hermes/SOUL.md`
3132
- session browsing from the canonical remote store at `~/.hermes/state.db`
3233
- fallback to `~/.hermes/sessions/*.jsonl` only if the SQLite store is not
3334
available
@@ -149,10 +150,10 @@ If `Test` passes, `Use Host` should be on solid ground.
149150
## What You Will See In The App
150151

151152
- `Overview`
152-
Confirms the remote `HOME`, the Hermes root, the tracked memory files, and
153+
Confirms the remote `HOME`, the Hermes root, the tracked Hermes files, and
153154
the session source.
154155
- `Files`
155-
Lets you edit `USER.md` and `MEMORY.md` on the host.
156+
Lets you edit `USER.md`, `MEMORY.md`, and `SOUL.md` on the host.
156157
- `Sessions`
157158
Reads the real remote session store from `~/.hermes/state.db`.
158159
- `Terminal`
@@ -222,9 +223,15 @@ fallback only when the SQLite store is not available.
222223

223224
This is the current direction for the next waves of work:
224225

226+
### Recently Shipped
227+
228+
- [x] richer workflows around the canonical Hermes files: `USER.md`, `MEMORY.md`, and `SOUL.md`
229+
230+
### Next
231+
225232
- skill management views for tracking, inspecting, editing, and organizing agent skills from the app
233+
- multi-profile support, aligned with Hermes Agent profiles and the app-side connection flow needed to select and use them cleanly
226234
- UI skins and appearance options to personalize the terminal and the broader chat-like workspace
227-
- expanded memory tracking, including `SOUL.md` alongside `USER.md` and `MEMORY.md`
228235
- clearer documentation, setup guides, and troubleshooting for new users
229236
- easier distribution for non-technical users through signed, notarized builds and, if realistic, App Store or similarly frictionless delivery
230237
- better first-run onboarding and connection diagnostics so SSH setup problems are easier to understand and fix

Sources/HermesDesktop/App/AppState.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ final class AppState: ObservableObject {
2020
@Published var selectedTrackedFile: RemoteTrackedFile = .memory
2121
@Published var memoryDocument = FileEditorDocument(trackedFile: .memory)
2222
@Published var userDocument = FileEditorDocument(trackedFile: .user)
23+
@Published var soulDocument = FileEditorDocument(trackedFile: .soul)
2324
@Published var pendingSectionSelection: AppSection?
2425
@Published var showDiscardChangesAlert = false
2526

@@ -66,7 +67,7 @@ final class AppState: ObservableObject {
6667
}
6768

6869
var hasUnsavedFileChanges: Bool {
69-
memoryDocument.isDirty || userDocument.isDirty
70+
memoryDocument.isDirty || userDocument.isDirty || soulDocument.isDirty
7071
}
7172

7273
func requestSectionSelection(_ section: AppSection) {
@@ -89,6 +90,7 @@ final class AppState: ObservableObject {
8990
func discardChangesAndContinue() {
9091
memoryDocument.discardChanges()
9192
userDocument.discardChanges()
93+
soulDocument.discardChanges()
9294
if let pendingSectionSelection {
9395
selectedSection = pendingSectionSelection
9496
handleSectionEntry(pendingSectionSelection)
@@ -342,25 +344,30 @@ final class AppState: ObservableObject {
342344
}
343345

344346
private func ensureInitialFileLoads() async {
345-
await loadTrackedFile(.memory, forceReload: true)
346347
await loadTrackedFile(.user, forceReload: true)
348+
await loadTrackedFile(.memory, forceReload: true)
349+
await loadTrackedFile(.soul, forceReload: true)
347350
}
348351

349352
private func document(for trackedFile: RemoteTrackedFile) -> FileEditorDocument {
350353
switch trackedFile {
351-
case .memory:
352-
return memoryDocument
353354
case .user:
354355
return userDocument
356+
case .memory:
357+
return memoryDocument
358+
case .soul:
359+
return soulDocument
355360
}
356361
}
357362

358363
private func setDocument(_ document: FileEditorDocument) {
359364
switch document.trackedFile {
360-
case .memory:
361-
memoryDocument = document
362365
case .user:
363366
userDocument = document
367+
case .memory:
368+
memoryDocument = document
369+
case .soul:
370+
soulDocument = document
364371
}
365372
}
366373

@@ -396,6 +403,7 @@ final class AppState: ObservableObject {
396403
private func resetDocuments() {
397404
memoryDocument = FileEditorDocument(trackedFile: .memory)
398405
userDocument = FileEditorDocument(trackedFile: .user)
406+
soulDocument = FileEditorDocument(trackedFile: .soul)
399407
selectedTrackedFile = .memory
400408
}
401409

Sources/HermesDesktop/Models/RemoteDiscovery.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,29 @@ struct RemoteDiscovery: Codable {
1919
}
2020

2121
struct RemoteHermesPaths: Codable {
22-
let memory: String
2322
let user: String
23+
let memory: String
24+
let soul: String
2425
let sessionsDir: String
2526

2627
enum CodingKeys: String, CodingKey {
27-
case memory
2828
case user
29+
case memory
30+
case soul
2931
case sessionsDir = "sessions_dir"
3032
}
3133
}
3234

3335
struct RemoteHermesPathExistence: Codable {
34-
let memory: Bool
3536
let user: Bool
37+
let memory: Bool
38+
let soul: Bool
3639
let sessionsDir: Bool
3740

3841
enum CodingKeys: String, CodingKey {
39-
case memory
4042
case user
43+
case memory
44+
case soul
4145
case sessionsDir = "sessions_dir"
4246
}
4347
}
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
import Foundation
22

33
enum RemoteTrackedFile: String, CaseIterable, Identifiable {
4-
case memory
54
case user
5+
case memory
6+
case soul
67

78
var id: String { rawValue }
89

910
var title: String {
1011
switch self {
11-
case .memory:
12-
"MEMORY.md"
1312
case .user:
1413
"USER.md"
14+
case .memory:
15+
"MEMORY.md"
16+
case .soul:
17+
"SOUL.md"
1518
}
1619
}
1720

1821
var fileName: String { title }
1922

2023
var remoteTildePath: String {
2124
switch self {
22-
case .memory:
23-
"~/.hermes/memories/MEMORY.md"
2425
case .user:
2526
"~/.hermes/memories/USER.md"
27+
case .memory:
28+
"~/.hermes/memories/MEMORY.md"
29+
case .soul:
30+
"~/.hermes/SOUL.md"
2631
}
2732
}
2833
}

Sources/HermesDesktop/Services/RemoteHermesService.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,22 +130,25 @@ final class RemoteHermesService: @unchecked Sendable {
130130
try:
131131
home = pathlib.Path.home()
132132
hermes_home = home / ".hermes"
133-
memory_path = hermes_home / "memories" / "MEMORY.md"
134133
user_path = hermes_home / "memories" / "USER.md"
134+
memory_path = hermes_home / "memories" / "MEMORY.md"
135+
soul_path = hermes_home / "SOUL.md"
135136
sessions_dir = hermes_home / "sessions"
136137
137138
result = {
138139
"ok": True,
139140
"remote_home": tilde(home, home),
140141
"hermes_home": tilde(hermes_home, home),
141142
"paths": {
142-
"memory": tilde(memory_path, home),
143143
"user": tilde(user_path, home),
144+
"memory": tilde(memory_path, home),
145+
"soul": tilde(soul_path, home),
144146
"sessions_dir": tilde(sessions_dir, home),
145147
},
146148
"exists": {
147-
"memory": memory_path.exists(),
148149
"user": user_path.exists(),
150+
"memory": memory_path.exists(),
151+
"soul": soul_path.exists(),
149152
"sessions_dir": sessions_dir.exists(),
150153
},
151154
"session_store": discover_session_store(hermes_home),

Sources/HermesDesktop/Services/Terminal/TerminalSession.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import Foundation
22

3+
@MainActor
34
final class TerminalSession: ObservableObject, @unchecked Sendable {
45
let connection: ConnectionProfile
56
let sshArguments: [String]
7+
private let viewHost = TerminalViewHost()
68

79
@Published var terminalTitle: String
810
@Published var currentDirectory: String?
@@ -15,6 +17,11 @@ final class TerminalSession: ObservableObject, @unchecked Sendable {
1517
self.connection = connection
1618
self.sshArguments = sshTransport.shellArguments(for: connection)
1719
self.terminalTitle = connection.label
20+
viewHost.bind(session: self)
21+
}
22+
23+
deinit {
24+
viewHost.terminate()
1825
}
1926

2027
func markStarted() {
@@ -39,7 +46,16 @@ final class TerminalSession: ObservableObject, @unchecked Sendable {
3946
launchToken = UUID()
4047
}
4148

49+
func mount(in container: TerminalMountContainerView, isActive: Bool) {
50+
viewHost.mount(in: container, session: self, isActive: isActive)
51+
}
52+
53+
func unmount(from container: TerminalMountContainerView) {
54+
viewHost.unmount(from: container)
55+
}
56+
4257
func stop() {
58+
viewHost.terminate()
4359
isRunning = false
4460
currentDirectory = nil
4561
}

0 commit comments

Comments
 (0)