Skip to content

Commit 9dbf828

Browse files
jb55claude
andcommitted
add source hostname to session state events and session list UI
Sessions now include a hostname tag in kind-31988 events, parsed on restore, and displayed as hostname:cwd in the session list sidebar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23c27ff commit 9dbf828

File tree

8 files changed

+74
-10
lines changed

8 files changed

+74
-10
lines changed

Cargo.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/notedeck_dave/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ egui_extras = { workspace = true }
3030
md-stream = { workspace = true }
3131
similar = "2"
3232
dirs = "5"
33+
gethostname = "1"
3334

3435
[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies]
3536
rfd = { workspace = true }

crates/notedeck_dave/src/lib.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ pub struct Dave {
141141
/// Sessions pending deletion state event publication.
142142
/// Populated in delete_session(), drained in the update loop where AppContext is available.
143143
pending_deletions: Vec<DeletedSessionInfo>,
144+
/// Local machine hostname, included in session state events.
145+
hostname: String,
144146
}
145147

146148
/// A permission response queued for relay publishing.
@@ -306,13 +308,20 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
306308
// Create IPC listener for external spawn-agent commands
307309
let ipc_listener = ipc::create_listener(ctx);
308310

311+
let hostname = gethostname::gethostname()
312+
.to_string_lossy()
313+
.into_owned();
314+
309315
// In Chat mode, create a default session immediately and skip directory picker
310316
// In Agentic mode, show directory picker on startup
311317
let (session_manager, active_overlay) = match ai_mode {
312318
AiMode::Chat => {
313319
let mut manager = SessionManager::new();
314320
// Create a default session with current directory
315-
manager.new_session(std::env::current_dir().unwrap_or_default(), ai_mode);
321+
let sid = manager.new_session(std::env::current_dir().unwrap_or_default(), ai_mode);
322+
if let Some(session) = manager.get_mut(sid) {
323+
session.hostname = hostname.clone();
324+
}
316325
(manager, DaveOverlay::None)
317326
}
318327
AiMode::Agentic => (SessionManager::new(), DaveOverlay::DirectoryPicker),
@@ -346,6 +355,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
346355
session_state_sub: None,
347356
pending_perm_responses: Vec::new(),
348357
pending_deletions: Vec::new(),
358+
hostname,
349359
}
350360
}
351361

@@ -894,6 +904,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
894904
self.show_scene,
895905
self.ai_mode,
896906
cwd,
907+
&self.hostname,
897908
);
898909
}
899910

@@ -913,6 +924,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
913924
cwd,
914925
resume_session_id,
915926
title,
927+
&self.hostname,
916928
)
917929
}
918930

@@ -924,6 +936,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
924936
&mut self.scene,
925937
self.show_scene,
926938
self.ai_mode,
939+
&self.hostname,
927940
);
928941
}
929942

@@ -943,6 +956,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
943956

944957
// Focus on new session
945958
if let Some(session) = self.session_manager.get_mut(id) {
959+
session.hostname = self.hostname.clone();
946960
session.focus_requested = true;
947961
if self.show_scene {
948962
self.scene.select(id);
@@ -1107,6 +1121,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
11071121
&session.title,
11081122
&cwd,
11091123
status,
1124+
&self.hostname,
11101125
&sk,
11111126
) {
11121127
Ok(evt) => {
@@ -1140,6 +1155,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
11401155
&info.title,
11411156
&info.cwd,
11421157
"deleted",
1158+
&self.hostname,
11431159
&sk,
11441160
) {
11451161
Ok(evt) => {
@@ -1265,6 +1281,14 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
12651281
}
12661282
let is_remote = session.is_remote();
12671283

1284+
// Local sessions use the current machine's hostname;
1285+
// remote sessions use what was stored in the event.
1286+
session.hostname = if is_remote {
1287+
state.hostname.clone()
1288+
} else {
1289+
self.hostname.clone()
1290+
};
1291+
12681292
if let Some(agentic) = &mut session.agentic {
12691293
if let (Some(root), Some(last)) = (loaded.root_note_id, loaded.last_note_id) {
12701294
agentic.live_threading.seed(root, last, loaded.event_count);
@@ -1395,11 +1419,15 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
13951419
.to_string();
13961420
let cwd_str = session_events::get_tag_value(&note, "cwd").unwrap_or("");
13971421
let cwd = std::path::PathBuf::from(cwd_str);
1422+
let hostname = session_events::get_tag_value(&note, "hostname")
1423+
.unwrap_or("")
1424+
.to_string();
13981425

13991426
tracing::info!(
1400-
"discovered new session from relay: '{}' ({})",
1427+
"discovered new session from relay: '{}' ({}) on {}",
14011428
title,
1402-
claude_sid
1429+
claude_sid,
1430+
hostname,
14031431
);
14041432

14051433
existing_ids.insert(claude_sid.to_string());
@@ -1415,6 +1443,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
14151443
let loaded = session_loader::load_session_messages(ctx.ndb, &txn, claude_sid);
14161444

14171445
if let Some(session) = self.session_manager.get_mut(dave_sid) {
1446+
session.hostname = hostname;
14181447
if !loaded.messages.is_empty() {
14191448
tracing::info!(
14201449
"loaded {} messages for discovered session",

crates/notedeck_dave/src/session.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ pub struct ChatSession {
184184
pub agentic: Option<AgenticSessionData>,
185185
/// Whether this session is local (has a Claude process) or remote (relay-only).
186186
pub source: SessionSource,
187+
/// Hostname of the machine where this session originated.
188+
pub hostname: String,
187189
}
188190

189191
impl Drop for ChatSession {
@@ -214,6 +216,7 @@ impl ChatSession {
214216
ai_mode,
215217
agentic,
216218
source: SessionSource::Local,
219+
hostname: String::new(),
217220
}
218221
}
219222

crates/notedeck_dave/src/session_events.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ pub fn build_session_state_event(
621621
title: &str,
622622
cwd: &str,
623623
status: &str,
624+
hostname: &str,
624625
secret_key: &[u8; 32],
625626
) -> Result<BuiltEvent, EventBuildError> {
626627
let mut builder = init_note_builder(AI_SESSION_STATE_KIND, "", Some(now_secs()));
@@ -632,6 +633,7 @@ pub fn build_session_state_event(
632633
builder = builder.start_tag().tag_str("title").tag_str(title);
633634
builder = builder.start_tag().tag_str("cwd").tag_str(cwd);
634635
builder = builder.start_tag().tag_str("status").tag_str(status);
636+
builder = builder.start_tag().tag_str("hostname").tag_str(hostname);
635637

636638
// Discoverability
637639
builder = builder.start_tag().tag_str("t").tag_str("ai-session-state");
@@ -1081,6 +1083,7 @@ mod tests {
10811083
"Fix the login bug",
10821084
"/tmp/project",
10831085
"working",
1086+
"my-laptop",
10841087
&sk,
10851088
)
10861089
.unwrap();
@@ -1099,6 +1102,7 @@ mod tests {
10991102
assert!(json.contains("Fix the login bug"));
11001103
assert!(json.contains("working"));
11011104
assert!(json.contains("/tmp/project"));
1105+
assert!(json.contains(r#""hostname","my-laptop"#));
11021106
}
11031107

11041108
#[test]

crates/notedeck_dave/src/session_loader.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ pub struct SessionState {
234234
pub title: String,
235235
pub cwd: String,
236236
pub status: String,
237+
pub hostname: String,
237238
}
238239

239240
/// Load all session states from kind-31988 events in ndb.
@@ -279,6 +280,7 @@ pub fn load_session_states(ndb: &Ndb, txn: &Transaction) -> Vec<SessionState> {
279280
.to_string(),
280281
cwd: get_tag_value(&note, "cwd").unwrap_or("").to_string(),
281282
status: get_tag_value(&note, "status").unwrap_or("idle").to_string(),
283+
hostname: get_tag_value(&note, "hostname").unwrap_or("").to_string(),
282284
});
283285
}
284286

crates/notedeck_dave/src/ui/session_list.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl<'a> SessionListUi<'a> {
158158
ui,
159159
&session.title,
160160
cwd,
161+
&session.hostname,
161162
is_active,
162163
shortcut_hint,
163164
session.status(),
@@ -184,6 +185,7 @@ impl<'a> SessionListUi<'a> {
184185
ui: &mut egui::Ui,
185186
title: &str,
186187
cwd: &Path,
188+
hostname: &str,
187189
is_active: bool,
188190
shortcut_hint: Option<usize>,
189191
status: AgentStatus,
@@ -286,22 +288,28 @@ impl<'a> SessionListUi<'a> {
286288
// Draw cwd below title - only in Agentic mode
287289
if show_cwd {
288290
let cwd_pos = rect.left_center() + egui::vec2(text_start_x, 7.0);
289-
cwd_ui(ui, cwd, cwd_pos, max_text_width);
291+
cwd_ui(ui, cwd, hostname, cwd_pos, max_text_width);
290292
}
291293

292294
response
293295
}
294296
}
295297

296-
/// Draw cwd text (monospace, weak+small) with clipping
297-
fn cwd_ui(ui: &mut egui::Ui, cwd_path: &Path, pos: egui::Pos2, max_width: f32) {
298-
let cwd_text = cwd_path.to_string_lossy();
298+
/// Draw cwd text (monospace, weak+small) with clipping.
299+
/// Shows "hostname:cwd" when hostname is non-empty.
300+
fn cwd_ui(ui: &mut egui::Ui, cwd_path: &Path, hostname: &str, pos: egui::Pos2, max_width: f32) {
301+
let cwd_str = cwd_path.to_string_lossy();
302+
let display_text = if hostname.is_empty() {
303+
cwd_str.to_string()
304+
} else {
305+
format!("{}:{}", hostname, cwd_str)
306+
};
299307
let cwd_font = egui::FontId::monospace(10.0);
300308
let cwd_color = ui.visuals().weak_text_color();
301309

302310
let cwd_galley = ui
303311
.painter()
304-
.layout_no_wrap(cwd_text.to_string(), cwd_font.clone(), cwd_color);
312+
.layout_no_wrap(display_text.clone(), cwd_font.clone(), cwd_color);
305313

306314
if cwd_galley.size().x > max_width {
307315
let clip_rect = egui::Rect::from_min_size(
@@ -317,7 +325,7 @@ fn cwd_ui(ui: &mut egui::Ui, cwd_path: &Path, pos: egui::Pos2, max_width: f32) {
317325
ui.painter().text(
318326
pos,
319327
egui::Align2::LEFT_CENTER,
320-
&cwd_text,
328+
&display_text,
321329
cwd_font,
322330
cwd_color,
323331
);

crates/notedeck_dave/src/update.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,11 +895,13 @@ pub fn create_session_with_cwd(
895895
show_scene: bool,
896896
ai_mode: AiMode,
897897
cwd: PathBuf,
898+
hostname: &str,
898899
) -> SessionId {
899900
directory_picker.add_recent(cwd.clone());
900901

901902
let id = session_manager.new_session(cwd, ai_mode);
902903
if let Some(session) = session_manager.get_mut(id) {
904+
session.hostname = hostname.to_string();
903905
session.focus_requested = true;
904906
if show_scene {
905907
scene.select(id);
@@ -922,11 +924,13 @@ pub fn create_resumed_session_with_cwd(
922924
cwd: PathBuf,
923925
resume_session_id: String,
924926
title: String,
927+
hostname: &str,
925928
) -> SessionId {
926929
directory_picker.add_recent(cwd.clone());
927930

928931
let id = session_manager.new_resumed_session(cwd, resume_session_id, title, ai_mode);
929932
if let Some(session) = session_manager.get_mut(id) {
933+
session.hostname = hostname.to_string();
930934
session.focus_requested = true;
931935
if show_scene {
932936
scene.select(id);
@@ -945,6 +949,7 @@ pub fn clone_active_agent(
945949
scene: &mut AgentScene,
946950
show_scene: bool,
947951
ai_mode: AiMode,
952+
hostname: &str,
948953
) -> Option<SessionId> {
949954
let cwd = session_manager
950955
.get_active()
@@ -956,6 +961,7 @@ pub fn clone_active_agent(
956961
show_scene,
957962
ai_mode,
958963
cwd,
964+
hostname,
959965
))
960966
}
961967

0 commit comments

Comments
 (0)