2
0

feat(agent): inject user/bot identity and recent chat context into agent and TUI sessions

This commit is contained in:
2026-03-24 16:05:38 +09:00
parent ca4b9c8a6f
commit aa52327345
2 changed files with 94 additions and 7 deletions

View File

@@ -73,13 +73,13 @@ impl Agent {
let (child, mut stdin, stdout) = claude::spawn_claude(Some(cwd))?; let (child, mut stdin, stdout) = claude::spawn_claude(Some(cwd))?;
let pid = child.id(); let pid = child.id();
// Inject self-awareness + git context // Inject self-awareness + user/bot context
let user_ctx = load_user_context();
let identity = format!( let identity = format!(
"[system]\n\ "[system]\n\
You are agent '{name}' running inside aishell.\n\ You are agent '{name}' running inside aishell.\n\
aishell is a multi-agent development tool. You are one of potentially several agents working in parallel.\n\ {user_ctx}\
If you find issues or have follow-up work, you can propose: @agent-name task -c dir\n\ If you find issues or have follow-up work, propose: @agent-name task -c dir\n"
Your feedback about aishell itself is also valuable.\n"
); );
let git_ctx = git_context(cwd) let git_ctx = git_context(cwd)
.map(|ctx| format!("\n[git context]\n{ctx}")) .map(|ctx| format!("\n[git context]\n{ctx}"))
@@ -290,6 +290,24 @@ impl Drop for Agent {
} }
} }
/// Load user/bot identity from shared config.
fn load_user_context() -> String {
let home = std::env::var("HOME").unwrap_or_default();
let path = if cfg!(target_os = "macos") {
format!("{home}/Library/Application Support/ai.syui.log/config.json")
} else {
format!("{home}/.config/ai.syui.log/config.json")
};
let config: serde_json::Value = std::fs::read_to_string(&path).ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
let user = config["handle"].as_str().unwrap_or("");
let bot = config["bot"]["handle"].as_str().unwrap_or("");
if user.is_empty() { return String::new(); }
format!("user: {user}, ai: {bot}\n")
}
/// Load last 3 session summaries for agent context. /// Load last 3 session summaries for agent context.
fn recent_session_summary() -> String { fn recent_session_summary() -> String {
let dir = if cfg!(target_os = "macos") { let dir = if cfg!(target_os = "macos") {

View File

@@ -92,13 +92,14 @@ impl App {
should_quit: false, should_quit: false,
}; };
// Send protocol + project context // Send protocol + identity + project context
if let Some(ref mut claude) = app.claude { if let Some(ref mut claude) = app.claude {
let cwd = std::env::current_dir() let cwd = std::env::current_dir()
.map(|p| p.display().to_string()) .map(|p| p.display().to_string())
.unwrap_or_else(|_| ".".to_string()); .unwrap_or_else(|_| ".".to_string());
let ctx = crate::agent::git_context(&cwd).unwrap_or_default(); let git_ctx = crate::agent::git_context(&cwd).unwrap_or_default();
let msg = format!("{AI_IDENTITY}\n\n[project context]\n{ctx}"); let identity_ctx = load_identity_context();
let msg = format!("{AI_IDENTITY}\n\n{identity_ctx}[project]\n{git_ctx}");
claude.send(&msg); claude.send(&msg);
} }
@@ -661,6 +662,74 @@ fn parse_agent_commands(text: &str) -> Vec<(String, String, String)> {
.collect() .collect()
} }
/// Load identity context from atproto config + recent chat.
fn load_identity_context() -> String {
let home = std::env::var("HOME").unwrap_or_default();
let config_path = if cfg!(target_os = "macos") {
format!("{home}/Library/Application Support/ai.syui.log/config.json")
} else {
format!("{home}/.config/ai.syui.log/config.json")
};
let config: serde_json::Value = match std::fs::read_to_string(&config_path) {
Ok(s) => serde_json::from_str(&s).unwrap_or_default(),
Err(_) => return String::new(),
};
let mut ctx = String::new();
// User & bot identity
let user_handle = config["handle"].as_str().unwrap_or("?");
let bot_handle = config["bot"]["handle"].as_str().unwrap_or("?");
let bot_did = config["bot"]["did"].as_str().unwrap_or("?");
let network = config["network"].as_str().unwrap_or("?");
ctx.push_str(&format!(
"[identity]\nuser: {user_handle}\nyou: {bot_handle} ({bot_did})\nnetwork: {network}\n\n"
));
// Recent chat (last 2 entries for context)
let bot_path = config["bot"]["path"].as_str().unwrap_or("");
let expanded = expand_tilde(bot_path);
let chat_dir = format!(
"{}/{}/ai.syui.log.chat",
if expanded.is_empty() { format!("{home}/.config/ai.syui.log/at") } else { expanded.clone() },
config["bot"]["did"].as_str().unwrap_or("did")
);
// Also check user's chat dir
let user_did = config["did"].as_str().unwrap_or("");
let user_chat_dir = format!("{}/{}/ai.syui.log.chat",
if expanded.is_empty() { format!("{home}/.config/ai.syui.log/at") } else { expanded },
user_did
);
for dir in [&chat_dir, &user_chat_dir] {
if let Ok(mut entries) = std::fs::read_dir(dir) {
let mut files: Vec<_> = entries
.flatten()
.filter(|e| e.path().extension().is_some_and(|x| x == "json"))
.collect();
files.sort_by_key(|e| std::cmp::Reverse(e.file_name()));
for f in files.iter().take(1) {
if let Ok(content) = std::fs::read_to_string(f.path()) {
if let Ok(record) = serde_json::from_str::<serde_json::Value>(&content) {
let text = record["value"]["content"]["text"].as_str().unwrap_or("");
if !text.is_empty() {
let short: String = text.chars().take(200).collect();
ctx.push_str(&format!("[recent chat]\n{short}\n\n"));
}
}
}
}
break; // Use first available dir
}
}
ctx
}
fn expand_tilde(path: &str) -> String { fn expand_tilde(path: &str) -> String {
if let Some(rest) = path.strip_prefix('~') { if let Some(rest) = path.strip_prefix('~') {
if let Ok(home) = std::env::var("HOME") { if let Ok(home) = std::env::var("HOME") {