Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion native/agent-server-rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use anyhow::{Context, Result};
use chrono::{SecondsFormat, Utc};
use native_host_rust::context_packet::{context_packet_paths, generate_source_context};
use native_host_rust::context_packet::{
context_packet_paths, generate_source_context, prepare_agent_context_handoff,
AgentContextHandoff, AgentContextHandoffInput,
};
use native_host_rust::protocol::{SourceContextResult, SummaryProviderSettings};
use serde::{Deserialize, Serialize};
use std::fs;
Expand All @@ -25,6 +28,10 @@ struct AgentRequest {
id: String,
command: AgentCommand,
target_directory: Option<String>,
meetings_root: Option<String>,
task: Option<String>,
project_hint: Option<String>,
working_directory: Option<String>,
summary_settings: Option<SummaryProviderSettings>,
}

Expand All @@ -33,6 +40,7 @@ struct AgentRequest {
enum AgentCommand {
Capabilities,
GenerateContext,
PrepareAgentContext,
JobStatus,
Shutdown,
}
Expand All @@ -58,6 +66,8 @@ struct AgentPayload {
job: Option<JobStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
source_context: Option<SourceContextResult>,
#[serde(skip_serializing_if = "Option::is_none")]
handoff: Option<AgentContextHandoff>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -156,6 +166,7 @@ fn handle_request(request: AgentRequest, running: &Arc<AtomicBool>) -> AgentResp
agent_version: Some(env!("CARGO_PKG_VERSION")),
job: None,
source_context: None,
handoff: None,
}),
),
AgentCommand::GenerateContext => match generate_context_job(&request) {
Expand All @@ -165,6 +176,19 @@ fn handle_request(request: AgentRequest, running: &Arc<AtomicBool>) -> AgentResp
agent_version: None,
job: Some(job),
source_context: Some(result),
handoff: None,
}),
),
Err(error) => failure_response(request.id, format!("{error:#}")),
},
AgentCommand::PrepareAgentContext => match prepare_agent_context(&request) {
Ok(handoff) => success_response(
request.id,
Some(AgentPayload {
agent_version: None,
job: None,
source_context: None,
handoff: Some(handoff),
}),
),
Err(error) => failure_response(request.id, format!("{error:#}")),
Expand All @@ -176,6 +200,7 @@ fn handle_request(request: AgentRequest, running: &Arc<AtomicBool>) -> AgentResp
agent_version: None,
job,
source_context: None,
handoff: None,
}),
),
Err(error) => failure_response(request.id, format!("{error:#}")),
Expand All @@ -188,12 +213,34 @@ fn handle_request(request: AgentRequest, running: &Arc<AtomicBool>) -> AgentResp
agent_version: Some(env!("CARGO_PKG_VERSION")),
job: None,
source_context: None,
handoff: None,
}),
)
}
}
}

fn prepare_agent_context(request: &AgentRequest) -> Result<AgentContextHandoff> {
let meetings_root = request
.meetings_root
.as_deref()
.context("A meetings root directory is required.")?;
let task = request
.task
.as_deref()
.filter(|task| !task.trim().is_empty())
.context("A task is required to prepare agent context.")?;
let result = prepare_agent_context_handoff(
Path::new(meetings_root),
AgentContextHandoffInput {
task: task.to_string(),
project_hint: request.project_hint.clone(),
working_directory: request.working_directory.clone(),
},
)?;
Ok(result.handoff)
}

fn generate_context_job(request: &AgentRequest) -> Result<(JobStatus, SourceContextResult)> {
let target_directory = request
.target_directory
Expand Down Expand Up @@ -364,4 +411,46 @@ mod tests {
let temp = tempfile::tempdir().unwrap();
assert!(read_job_status(temp.path()).unwrap().is_none());
}

#[test]
fn prepare_agent_context_request_writes_handoff_files() {
let temp = tempfile::tempdir().unwrap();
let meetings_root = temp.path();
let project = meetings_root
.join("projects")
.join("mirrornote-agent-runtime");
fs::create_dir_all(&project).unwrap();
fs::write(
project.join("source-index.jsonl"),
"{\"id\":\"meeting:note-1\",\"type\":\"meeting\",\"title\":\"Agent Runtime Sync\",\"observedAt\":\"2026-05-05T01:00:00Z\",\"projectHint\":\"MirrorNote Agent Runtime\"}\n",
)
.unwrap();
fs::write(
project.join("memory-objects.jsonl"),
"{\"id\":\"meeting:note-1::decision-1\",\"type\":\"decision\",\"title\":\"Keep context automatic\",\"body\":\"Keep context automatic\",\"status\":\"active\",\"confidence\":0.95,\"evidenceCoverage\":\"direct\",\"sourceRefs\":[{\"sourceId\":\"meeting:note-1\",\"sourceType\":\"meeting\",\"location\":{\"kind\":\"metadata\"}}]}\n",
)
.unwrap();

let response = handle_request(
AgentRequest {
id: "request-1".to_string(),
command: AgentCommand::PrepareAgentContext,
target_directory: None,
meetings_root: Some(meetings_root.to_string_lossy().into_owned()),
task: Some("Run Codex with prior context".to_string()),
project_hint: Some("MirrorNote Agent Runtime".to_string()),
working_directory: Some("/tmp/MirrorNote".to_string()),
summary_settings: None,
},
&Arc::new(AtomicBool::new(true)),
);

assert!(response.ok);
let payload = response.payload.unwrap();
let handoff = payload.handoff.unwrap();
assert_eq!(handoff.project_id, "mirrornote-agent-runtime");
assert_eq!(handoff.decisions.len(), 1);
assert!(project.join("handoffs").join("handoff.json").exists());
assert!(project.join("handoffs").join("handoff.md").exists());
}
}
Loading