2
0

feat(agent): inject identity, session history and diff content into agent context

This commit is contained in:
2026-03-24 14:19:17 +09:00
parent 5fd97f1e57
commit 6a5250ef02
2 changed files with 64 additions and 10 deletions

View File

@@ -71,11 +71,21 @@ 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 git context if available // Inject self-awareness + git context
let full_task = match git_context(cwd) { let identity = format!(
Some(ctx) => format!("{task}\n\n[git context]\n{ctx}"), "[system]\n\
None => task.to_string(), 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\
If you find issues or have follow-up work, you can propose: @agent-name task -c dir\n\
Your feedback about aishell itself is also valuable.\n"
);
let git_ctx = git_context(cwd)
.map(|ctx| format!("\n[git context]\n{ctx}"))
.unwrap_or_default();
let history = recent_session_summary();
let history_ctx = if history.is_empty() { String::new() }
else { format!("\n[session history]\n{history}") };
let full_task = format!("{identity}\n[task]\n{task}{git_ctx}{history_ctx}");
claude::send_message(&mut stdin, &full_task); claude::send_message(&mut stdin, &full_task);
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
claude::spawn_reader(child, stdout, tx); claude::spawn_reader(child, stdout, tx);
@@ -271,6 +281,40 @@ impl Drop for Agent {
} }
} }
/// Load last 3 session summaries for agent context.
fn recent_session_summary() -> String {
let dir = if cfg!(target_os = "macos") {
let home = std::env::var("HOME").unwrap_or_default();
format!("{home}/Library/Application Support/ai.syui.gpt/sessions")
} else {
let home = std::env::var("HOME").unwrap_or_default();
format!("{home}/.local/share/aishell/sessions")
};
let mut files: Vec<_> = std::fs::read_dir(&dir)
.into_iter().flatten().flatten()
.filter(|e| {
let s = e.file_name().to_string_lossy().to_string();
s.ends_with(".json") && s != "cmd_history.json"
})
.collect();
files.sort_by_key(|e| std::cmp::Reverse(e.file_name()));
files.iter().take(3).filter_map(|f| {
let content = std::fs::read_to_string(f.path()).ok()?;
let d: serde_json::Value = serde_json::from_str(&content).ok()?;
let decision = d["decision"].as_str().unwrap_or("");
let first_line = decision.lines()
.filter(|l| !l.starts_with('#') && !l.is_empty())
.next()
.unwrap_or("");
let agents: Vec<&str> = d["agents"].as_array()
.map(|a| a.iter().filter_map(|x| x["name"].as_str()).collect())
.unwrap_or_default();
Some(format!("- agents: {} | {}", agents.join(","), first_line))
}).collect::<Vec<_>>().join("\n")
}
pub fn git_context(cwd: &str) -> Option<String> { pub fn git_context(cwd: &str) -> Option<String> {
use std::process::Command; use std::process::Command;
let run = |args: &[&str]| -> String { let run = |args: &[&str]| -> String {
@@ -290,13 +334,19 @@ pub fn git_context(cwd: &str) -> Option<String> {
let status = run(&["status", "--short"]); let status = run(&["status", "--short"]);
let log = run(&["log", "--oneline", "-5"]); let log = run(&["log", "--oneline", "-5"]);
let diff_stat = run(&["diff", "--stat", "HEAD"]); let diff_stat = run(&["diff", "--stat", "HEAD"]);
// Actual diff content (truncated)
let diff_content = run(&["diff", "HEAD"]);
let diff_short: String = diff_content.lines().take(100).collect::<Vec<_>>().join("\n");
let mut ctx = format!("branch: {branch}"); let mut ctx = format!("branch: {branch}");
if !status.is_empty() { if !status.is_empty() {
ctx.push_str(&format!("\nchanged:\n{status}")); ctx.push_str(&format!("\nchanged:\n{status}"));
} }
if !diff_stat.is_empty() { if !diff_stat.is_empty() {
ctx.push_str(&format!("\ndiff:\n{diff_stat}")); ctx.push_str(&format!("\ndiff stat:\n{diff_stat}"));
}
if !diff_short.is_empty() {
ctx.push_str(&format!("\ndiff:\n{diff_short}"));
} }
if !log.is_empty() { if !log.is_empty() {
ctx.push_str(&format!("\nrecent:\n{log}")); ctx.push_str(&format!("\nrecent:\n{log}"));

View File

@@ -54,10 +54,14 @@ pub struct App {
should_quit: bool, should_quit: bool,
} }
/// Protocol prompt. Personality comes from aigpt MCP. This only teaches @agent syntax. /// Protocol + self-awareness. Personality comes from aigpt MCP.
const AI_IDENTITY: &str = "エージェント起動コマンド(応答に書くと自動実行される): const AI_IDENTITY: &str = "\
@name task -c dir You are the main AI in aishell, a multi-agent development tool.
例: @test cargo test -c ~/ai/shell Your personality comes from aigpt MCP (core.md).
You oversee agents, integrate their results, and guide the user.
You can spawn agents with: @name task -c dir
Report any usability issues you notice about aishell itself.
一言だけ挨拶してください。"; 一言だけ挨拶してください。";