Skip to content

Commit 768a49a

Browse files
authored
Merge pull request #86 from edmondop/feat/agent-metadata
feat: support generic metadata in agent notifications
2 parents ebaf935 + 9078c8b commit 768a49a

4 files changed

Lines changed: 61 additions & 0 deletions

File tree

crates/arbor-daemon-client/src/types.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ pub struct AgentSessionDto {
106106
pub cwd: String,
107107
pub state: String,
108108
pub updated_at_unix_ms: u64,
109+
#[serde(skip_serializing_if = "Option::is_none")]
110+
pub metadata: Option<serde_json::Value>,
109111
}
110112

111113
#[derive(Debug, Clone, Deserialize)]
@@ -114,6 +116,7 @@ struct AgentSessionDtoWire {
114116
cwd: String,
115117
state: String,
116118
updated_at_unix_ms: u64,
119+
metadata: Option<serde_json::Value>,
117120
}
118121

119122
fn legacy_agent_session_id(cwd: &str) -> String {
@@ -133,6 +136,7 @@ impl<'de> Deserialize<'de> for AgentSessionDto {
133136
cwd: wire.cwd,
134137
state: wire.state,
135138
updated_at_unix_ms: wire.updated_at_unix_ms,
139+
metadata: wire.metadata,
136140
})
137141
}
138142
}
@@ -317,5 +321,53 @@ mod tests {
317321
assert_eq!(dto.cwd, "/tmp/repo/worktree");
318322
assert_eq!(dto.state, "waiting");
319323
assert_eq!(dto.updated_at_unix_ms, 99);
324+
assert!(dto.metadata.is_none());
325+
}
326+
327+
#[test]
328+
fn agent_session_dto_deserializes_with_metadata() {
329+
let dto: AgentSessionDto = serde_json::from_value(serde_json::json!({
330+
"session_id": "sess-1",
331+
"cwd": "/tmp/test",
332+
"state": "working",
333+
"updated_at_unix_ms": 42_u64,
334+
"metadata": {
335+
"terminal": { "type": "tmux", "server": "my-project", "pane_id": "%42" },
336+
"git": { "branch": "feat/cool" }
337+
}
338+
}))
339+
.expect("payload with metadata should deserialize");
340+
341+
let meta = dto.metadata.expect("metadata should be Some");
342+
assert_eq!(meta["terminal"]["type"], "tmux");
343+
assert_eq!(meta["terminal"]["server"], "my-project");
344+
assert_eq!(meta["terminal"]["pane_id"], "%42");
345+
assert_eq!(meta["git"]["branch"], "feat/cool");
346+
}
347+
348+
#[test]
349+
fn agent_session_dto_without_metadata_omits_field_in_json() {
350+
let dto = AgentSessionDto {
351+
session_id: "s1".to_owned(),
352+
cwd: "/tmp".to_owned(),
353+
state: "idle".to_owned(),
354+
updated_at_unix_ms: 0,
355+
metadata: None,
356+
};
357+
let json = serde_json::to_value(&dto).expect("should serialize");
358+
assert!(json.get("metadata").is_none(), "metadata should be omitted when None");
359+
}
360+
361+
#[test]
362+
fn agent_session_dto_with_metadata_includes_field_in_json() {
363+
let dto = AgentSessionDto {
364+
session_id: "s2".to_owned(),
365+
cwd: "/tmp".to_owned(),
366+
state: "working".to_owned(),
367+
updated_at_unix_ms: 1,
368+
metadata: Some(serde_json::json!({"foo": "bar"})),
369+
};
370+
let json = serde_json::to_value(&dto).expect("should serialize");
371+
assert_eq!(json["metadata"]["foo"], "bar");
320372
}
321373
}

crates/arbor-httpd/src/routes.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ pub(crate) async fn agent_notify(
12061206
request.session_id.clone(),
12071207
request.cwd.clone(),
12081208
agent_state,
1209+
request.metadata.clone(),
12091210
AgentSessionUpdateSource::Hook,
12101211
)
12111212
.await;
@@ -1225,6 +1226,7 @@ pub(crate) async fn apply_terminal_activity_event(state: &AppState, event: Termi
12251226
terminal_agent_session_key(&session_id),
12261227
cwd.display().to_string(),
12271228
agent_state,
1229+
None,
12281230
AgentSessionUpdateSource::TerminalActivity,
12291231
)
12301232
.await;
@@ -1240,6 +1242,7 @@ async fn upsert_agent_session(
12401242
session_id: String,
12411243
cwd: String,
12421244
agent_state: AgentState,
1245+
metadata: Option<serde_json::Value>,
12431246
source: AgentSessionUpdateSource,
12441247
) {
12451248
let now_ms = SystemTime::now()
@@ -1260,6 +1263,7 @@ async fn upsert_agent_session(
12601263
cwd: cwd.clone(),
12611264
state: agent_state,
12621265
updated_at_unix_ms: now_ms,
1266+
metadata: metadata.clone(),
12631267
});
12641268

12651269
(
@@ -1268,6 +1272,7 @@ async fn upsert_agent_session(
12681272
cwd: cwd.clone(),
12691273
state: agent_state_label(agent_state).to_owned(),
12701274
updated_at_unix_ms: now_ms,
1275+
metadata,
12711276
},
12721277
previous_state,
12731278
)
@@ -1663,6 +1668,7 @@ fn agent_session_snapshot(sessions: &mut HashMap<String, AgentSession>) -> Vec<A
16631668
AgentState::Waiting => "waiting".to_owned(),
16641669
},
16651670
updated_at_unix_ms: session.updated_at_unix_ms,
1671+
metadata: session.metadata.clone(),
16661672
})
16671673
.collect();
16681674
snapshot.sort_by(|left, right| {

crates/arbor-httpd/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ pub(crate) struct AgentSession {
3939
pub(crate) cwd: String,
4040
pub(crate) state: AgentState,
4141
pub(crate) updated_at_unix_ms: u64,
42+
pub(crate) metadata: Option<serde_json::Value>,
4243
}
4344

4445
#[derive(Debug, Clone, Deserialize)]
4546
pub(crate) struct AgentNotifyRequest {
4647
pub(crate) hook_event_name: String,
4748
pub(crate) session_id: String,
4849
pub(crate) cwd: String,
50+
pub(crate) metadata: Option<serde_json::Value>,
4951
}
5052

5153
#[derive(Debug, Clone, Serialize)]

crates/arbor-mcp/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ mod tests {
579579
cwd: "/tmp/repo".to_owned(),
580580
state: "working".to_owned(),
581581
updated_at_unix_ms: 1,
582+
metadata: None,
582583
}])
583584
}
584585

0 commit comments

Comments
 (0)