补齐Agent Runtime连续上下文
后台 Agent 规划上下文加入本 Agent 最近状态、任务和工具动作。 新后台 run 继承同 Agent 最近回复和工具动作,避免多轮任务断档。 补充同 Agent 连续性与跨 Agent 隔离测试,同步 Runtime V1 文档。
This commit is contained in:
@@ -1894,6 +1894,9 @@ pub(crate) fn start_game_creator_agent_runtime_task_at(
|
||||
return Err("Agent Runtime 任务不能为空".to_string());
|
||||
}
|
||||
let runtime_task = sanitize_agent_runtime_text(task, 180);
|
||||
let previous_state = read_game_creator_agent_runtime_at(root, &agent_id)
|
||||
.ok()
|
||||
.map(|result| result.state);
|
||||
let mut state = default_game_creator_agent_runtime_state(&agent_id, &run_id);
|
||||
state.source = source.trim().to_string();
|
||||
state.status = "running".to_string();
|
||||
@@ -1903,6 +1906,10 @@ pub(crate) fn start_game_creator_agent_runtime_task_at(
|
||||
state.next_step = "等待 Agent 输出计划或回复".to_string();
|
||||
state.plan = plan;
|
||||
state.observations = vec!["已创建本轮 Agent Runtime run。".to_string()];
|
||||
if let Some(previous_state) = previous_state {
|
||||
state.recent_tool_calls = previous_state.recent_tool_calls;
|
||||
state.last_response = previous_state.last_response;
|
||||
}
|
||||
refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?;
|
||||
state.updated_at = unix_timestamp();
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
@@ -2703,6 +2710,244 @@ fn redact_agent_runtime_project_paths(root: &Path, value: &str, max_chars: usize
|
||||
sanitize_agent_runtime_text(&redacted, max_chars)
|
||||
}
|
||||
|
||||
fn render_agent_runtime_tool_names(values: &[String], limit: usize) -> String {
|
||||
if values.is_empty() {
|
||||
return "无".to_string();
|
||||
}
|
||||
let mut names = values
|
||||
.iter()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.take(limit)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if values.len() > limit {
|
||||
names.push(format!("等{}项", values.len()));
|
||||
}
|
||||
names.join(", ")
|
||||
}
|
||||
|
||||
fn render_agent_runtime_prompt_context(root: &Path, agent_id: &str) -> Result<String, String> {
|
||||
let runtime = match read_game_creator_agent_runtime_at(root, agent_id) {
|
||||
Ok(runtime) => runtime,
|
||||
Err(error) => {
|
||||
return Ok(format!(
|
||||
"Agent Runtime 连续上下文读取失败:{}",
|
||||
redact_agent_runtime_project_paths(root, &error, 360)
|
||||
));
|
||||
}
|
||||
};
|
||||
let state = &runtime.state;
|
||||
let has_meaningful_state = !state.current_task.trim().is_empty()
|
||||
|| state
|
||||
.last_response
|
||||
.as_deref()
|
||||
.is_some_and(|value| !value.trim().is_empty())
|
||||
|| state
|
||||
.error
|
||||
.as_deref()
|
||||
.is_some_and(|value| !value.trim().is_empty())
|
||||
|| !state.observations.is_empty()
|
||||
|| !state.recent_tool_calls.is_empty()
|
||||
|| !runtime.recent_events.is_empty()
|
||||
|| !runtime.recent_tasks.is_empty();
|
||||
if !has_meaningful_state {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
||||
let mut lines = Vec::new();
|
||||
lines.push(format!(
|
||||
"当前状态:status={}, phase={}, source={}, runId={}",
|
||||
redact_agent_runtime_project_paths(root, &state.status, 80),
|
||||
redact_agent_runtime_project_paths(root, &state.phase, 80),
|
||||
redact_agent_runtime_project_paths(root, &state.source, 120),
|
||||
redact_agent_runtime_project_paths(root, &state.run_id, 160)
|
||||
));
|
||||
if !state.current_task.trim().is_empty() {
|
||||
lines.push(format!(
|
||||
"当前任务:{}",
|
||||
redact_agent_runtime_project_paths(root, &state.current_task, 220)
|
||||
));
|
||||
}
|
||||
if !state.current_action.trim().is_empty() {
|
||||
lines.push(format!(
|
||||
"当前动作:{}",
|
||||
redact_agent_runtime_project_paths(root, &state.current_action, 160)
|
||||
));
|
||||
}
|
||||
if !state.next_step.trim().is_empty() {
|
||||
lines.push(format!(
|
||||
"下一步:{}",
|
||||
redact_agent_runtime_project_paths(root, &state.next_step, 160)
|
||||
));
|
||||
}
|
||||
if let Some(last_response) = state
|
||||
.last_response
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
lines.push(format!(
|
||||
"最近回复:{}",
|
||||
redact_agent_runtime_project_paths(root, last_response, 360)
|
||||
));
|
||||
}
|
||||
if let Some(error) = state
|
||||
.error
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
lines.push(format!(
|
||||
"最近错误:{}",
|
||||
redact_agent_runtime_project_paths(root, error, 360)
|
||||
));
|
||||
}
|
||||
if !state.plan.is_empty() {
|
||||
lines.push("当前/最近计划:".to_string());
|
||||
for item in state
|
||||
.plan
|
||||
.iter()
|
||||
.filter(|item| !item.trim().is_empty())
|
||||
.take(5)
|
||||
{
|
||||
lines.push(format!(
|
||||
"- {}",
|
||||
redact_agent_runtime_project_paths(root, item, 180)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let recent_observations = state
|
||||
.observations
|
||||
.iter()
|
||||
.filter(|item| !item.trim().is_empty())
|
||||
.rev()
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
if !recent_observations.is_empty() {
|
||||
lines.push("最近观察:".to_string());
|
||||
for item in recent_observations.iter().rev() {
|
||||
lines.push(format!(
|
||||
"- {}",
|
||||
redact_agent_runtime_project_paths(root, item, 260)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let recent_tool_calls = state
|
||||
.recent_tool_calls
|
||||
.iter()
|
||||
.rev()
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
if !recent_tool_calls.is_empty() {
|
||||
lines.push("最近工具动作:".to_string());
|
||||
for call in recent_tool_calls.iter().rev() {
|
||||
let mut line = format!(
|
||||
"- {} [{}]:{}",
|
||||
redact_agent_runtime_project_paths(root, &call.tool, 80),
|
||||
redact_agent_runtime_project_paths(root, &call.status, 80),
|
||||
redact_agent_runtime_project_paths(root, &call.summary, 260)
|
||||
);
|
||||
if let Some(reason) = call
|
||||
.reason
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
line.push_str(&format!(
|
||||
";原因:{}",
|
||||
redact_agent_runtime_project_paths(root, reason, 160)
|
||||
));
|
||||
}
|
||||
if let Some(detail) = call
|
||||
.detail
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
line.push_str(&format!(
|
||||
";观察:{}",
|
||||
redact_agent_runtime_project_paths(root, detail, 260)
|
||||
));
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
let recent_events = runtime
|
||||
.recent_events
|
||||
.iter()
|
||||
.filter(|event| !event.summary.trim().is_empty())
|
||||
.rev()
|
||||
.take(4)
|
||||
.collect::<Vec<_>>();
|
||||
if !recent_events.is_empty() {
|
||||
lines.push("最近事件:".to_string());
|
||||
for event in recent_events.iter().rev() {
|
||||
let mut line = format!(
|
||||
"- {} [{} / {}]:{}",
|
||||
redact_agent_runtime_project_paths(root, &event.event_type, 120),
|
||||
redact_agent_runtime_project_paths(root, &event.status, 80),
|
||||
redact_agent_runtime_project_paths(root, &event.phase, 80),
|
||||
redact_agent_runtime_project_paths(root, &event.summary, 260)
|
||||
);
|
||||
if let Some(detail) = event
|
||||
.detail
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
line.push_str(&format!(
|
||||
";{}",
|
||||
redact_agent_runtime_project_paths(root, detail, 260)
|
||||
));
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
let recent_tasks = runtime
|
||||
.recent_tasks
|
||||
.iter()
|
||||
.rev()
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
if !recent_tasks.is_empty() {
|
||||
lines.push("最近任务:".to_string());
|
||||
for task in recent_tasks.iter().rev() {
|
||||
let mut line = format!(
|
||||
"- {} [{} / {}]:{}",
|
||||
redact_agent_runtime_project_paths(root, &task.run_id, 160),
|
||||
redact_agent_runtime_project_paths(root, &task.status, 80),
|
||||
redact_agent_runtime_project_paths(root, &task.phase, 80),
|
||||
redact_agent_runtime_project_paths(root, &task.task, 220)
|
||||
);
|
||||
if !task.current_action.trim().is_empty() {
|
||||
line.push_str(&format!(
|
||||
";动作:{}",
|
||||
redact_agent_runtime_project_paths(root, &task.current_action, 180)
|
||||
));
|
||||
}
|
||||
if let Some(error) = task
|
||||
.error
|
||||
.as_deref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
line.push_str(&format!(
|
||||
";错误:{}",
|
||||
redact_agent_runtime_project_paths(root, error, 220)
|
||||
));
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(format!(
|
||||
"工具策略:auto={};confirm={};denied={}",
|
||||
render_agent_runtime_tool_names(&state.tool_policy.auto_tools, 8),
|
||||
render_agent_runtime_tool_names(&state.tool_policy.confirm_tools, 8),
|
||||
render_agent_runtime_tool_names(&state.tool_policy.denied_tools, 8)
|
||||
));
|
||||
|
||||
Ok(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn unix_timestamp_nanos() -> u128 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -2747,6 +2992,7 @@ fn build_game_creator_role_agent_context(
|
||||
let long_memory = read_optional_text(&root.join("memory/project.md"))?;
|
||||
let project_blackboard = read_optional_text(&root.join(PROJECT_BLACKBOARD_MEMORY_PATH))?;
|
||||
let agent_memory = read_local_agent_memory_at(root, &agent_id)?.content;
|
||||
let runtime_context = render_agent_runtime_prompt_context(root, &agent_id)?;
|
||||
let asset_context = render_local_asset_prompt_context(root)?;
|
||||
let conversation_context = render_local_conversation_prompt_context(root, Some(&agent_id))?;
|
||||
let identity = format!(
|
||||
@@ -2756,6 +3002,7 @@ fn build_game_creator_role_agent_context(
|
||||
let context = [
|
||||
("Agent 身份", identity.as_str()),
|
||||
("Agent 私有记忆", agent_memory.as_str()),
|
||||
("Agent Runtime 连续上下文", runtime_context.as_str()),
|
||||
("短期记忆", short_memory.as_str()),
|
||||
("长期记忆", long_memory.as_str()),
|
||||
("项目黑板", project_blackboard.as_str()),
|
||||
|
||||
@@ -50,6 +50,22 @@ fn unique_project_path() -> PathBuf {
|
||||
))
|
||||
}
|
||||
|
||||
fn wait_for_agent_runtime_idle(root: &Path, agent_id: &str) -> AgentRuntimeState {
|
||||
let mut runtime = read_game_creator_agent_runtime_at(root, agent_id)
|
||||
.expect("read runtime while waiting")
|
||||
.state;
|
||||
for _ in 0..50 {
|
||||
if runtime.status == "idle" {
|
||||
return runtime;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
runtime = read_game_creator_agent_runtime_at(root, agent_id)
|
||||
.expect("read runtime while waiting")
|
||||
.state;
|
||||
}
|
||||
runtime
|
||||
}
|
||||
|
||||
fn test_local_config_path() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
@@ -1814,6 +1830,151 @@ async fn background_agent_runtime_can_replan_after_observation() {
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn background_agent_runtime_plan_request_includes_same_agent_continuity_context() {
|
||||
let root = unique_project_path();
|
||||
init_local_game_project_at(&root, "project-1", "月光厨房").expect("project init");
|
||||
fs::write(
|
||||
root.join("game/continuity-notes.txt"),
|
||||
"连续上下文笔记:第一轮已经确认月光厨房核心循环。",
|
||||
)
|
||||
.expect("write continuity notes");
|
||||
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let first_plan_json = serde_json::json!({
|
||||
"thinkingSummary": "首轮需要读取项目笔记",
|
||||
"plan": ["读取项目笔记", "把观察留给下一轮"],
|
||||
"actions": [
|
||||
{
|
||||
"tool": "file.read",
|
||||
"reason": "为连续上下文留下工具证据",
|
||||
"input": { "path": "game/continuity-notes.txt" }
|
||||
}
|
||||
],
|
||||
"response": ""
|
||||
})
|
||||
.to_string();
|
||||
let art_plan_json = serde_json::json!({
|
||||
"thinkingSummary": "美术任务不应继承策划 Agent 的 runtime",
|
||||
"plan": ["独立处理美术任务"],
|
||||
"actions": [],
|
||||
"response": "美术无关任务完成。"
|
||||
})
|
||||
.to_string();
|
||||
let second_design_plan_json = serde_json::json!({
|
||||
"thinkingSummary": "第二轮应参考本 Agent 上一轮 runtime 上下文",
|
||||
"plan": ["复用上一轮工具观察", "继续设计建议"],
|
||||
"actions": [],
|
||||
"response": "第二轮设计任务完成。"
|
||||
})
|
||||
.to_string();
|
||||
let base_url = spawn_mock_llm_server_responses_with_capture(
|
||||
vec![
|
||||
first_plan_json,
|
||||
"首轮完成:已经读取连续上下文笔记。".to_string(),
|
||||
art_plan_json,
|
||||
second_design_plan_json,
|
||||
],
|
||||
Some(sender),
|
||||
);
|
||||
let _config_guard = write_test_local_config(format!(
|
||||
r#"{{
|
||||
"agentLlm": {{
|
||||
"design-director": {{
|
||||
"apiKey": "design-key",
|
||||
"baseUrl": {base_url:?},
|
||||
"model": "design-runtime-model",
|
||||
"apiKind": "openai_responses"
|
||||
}},
|
||||
"art-director": {{
|
||||
"apiKey": "art-key",
|
||||
"baseUrl": {base_url:?},
|
||||
"model": "art-runtime-model",
|
||||
"apiKind": "openai_responses"
|
||||
}}
|
||||
}}
|
||||
}}"#
|
||||
));
|
||||
|
||||
start_game_creator_agent_background_task_at(
|
||||
&root,
|
||||
"design-director",
|
||||
"首轮检查连续上下文",
|
||||
"design-continuity-first",
|
||||
)
|
||||
.expect("start first design task");
|
||||
let first_plan_request = receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("first design plan request");
|
||||
assert!(first_plan_request.contains("首轮检查连续上下文"));
|
||||
let first_replan_request = receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("first design replan request");
|
||||
assert!(first_replan_request.contains("连续上下文笔记:第一轮已经确认月光厨房核心循环"));
|
||||
let first_runtime = wait_for_agent_runtime_idle(&root, "design-director");
|
||||
assert_eq!(first_runtime.run_id, "design-continuity-first");
|
||||
assert_eq!(
|
||||
first_runtime.last_response.as_deref(),
|
||||
Some("首轮完成:已经读取连续上下文笔记。")
|
||||
);
|
||||
assert!(first_runtime
|
||||
.recent_tool_calls
|
||||
.iter()
|
||||
.any(|call| call.tool == "file.read" && call.status == "ok"));
|
||||
|
||||
start_game_creator_agent_background_task_at(
|
||||
&root,
|
||||
"art-director",
|
||||
"美术无关任务",
|
||||
"art-continuity-check",
|
||||
)
|
||||
.expect("start art task");
|
||||
let art_request = receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("art plan request");
|
||||
assert!(art_request.contains("美术无关任务"));
|
||||
assert!(!art_request.contains("design-continuity-first"));
|
||||
assert!(!art_request.contains("首轮完成:已经读取连续上下文笔记"));
|
||||
assert!(!art_request.contains("file.read [ok]"));
|
||||
let art_runtime = wait_for_agent_runtime_idle(&root, "art-director");
|
||||
assert_eq!(art_runtime.run_id, "art-continuity-check");
|
||||
|
||||
start_game_creator_agent_background_task_at(
|
||||
&root,
|
||||
"design-director",
|
||||
"第二轮继续策划",
|
||||
"design-continuity-second",
|
||||
)
|
||||
.expect("start second design task");
|
||||
let second_design_request = receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("second design plan request");
|
||||
assert!(second_design_request.contains("# Agent Runtime 连续上下文"));
|
||||
assert!(second_design_request.contains("当前状态:status=running"));
|
||||
assert!(second_design_request.contains("runId=design-continuity-second"));
|
||||
assert!(second_design_request.contains("最近回复:首轮完成:已经读取连续上下文笔记。"));
|
||||
assert!(second_design_request.contains("最近工具动作"));
|
||||
assert!(second_design_request.contains("file.read [ok]"));
|
||||
assert!(second_design_request.contains("为连续上下文留下工具证据"));
|
||||
assert!(second_design_request.contains("连续上下文笔记:第一轮已经确认月光厨房核心循环"));
|
||||
assert!(second_design_request.contains("最近任务"));
|
||||
assert!(second_design_request.contains("design-continuity-first [completed / completed]"));
|
||||
assert!(second_design_request.contains("首轮检查连续上下文"));
|
||||
assert!(!second_design_request.contains(root.to_string_lossy().as_ref()));
|
||||
let second_runtime = wait_for_agent_runtime_idle(&root, "design-director");
|
||||
assert_eq!(second_runtime.run_id, "design-continuity-second");
|
||||
assert_eq!(
|
||||
second_runtime.last_response.as_deref(),
|
||||
Some("第二轮设计任务完成。")
|
||||
);
|
||||
assert!(second_runtime
|
||||
.recent_tool_calls
|
||||
.iter()
|
||||
.any(|call| call.tool == "file.read" && call.status == "ok"));
|
||||
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn background_agent_runtime_can_write_blackboard_and_message_other_agent() {
|
||||
let root = unique_project_path();
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `preview.start`,让 Agent 在完成写盘或静态自检后能按策略自行启动当前项目的 `127.0.0.1` 本地 HTTP 预览。该工具复用 `preview.start` 权限策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑;写入 `.agent/agent.db` 的审计类型为 `agent.runtime.preview.start`。发给 LLM 的 observation 只包含 localhost URL 和端口,不包含用户项目绝对路径。
|
||||
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `canvas.asset_generate`,让美术类 Agent 可在 loop 中自行请求生成首版美术素材。该工具读取 AppData / Tauri 配置中的 `editorApi`,复用 `canvas.asset_generate` 权限策略、项目写锁、External Editor API 生成和下载链路、manifest 资产登记以及 `canvas.asset_generate` 本地索引记录;另写 `agent.runtime.canvas.asset_generate` 记录到 `.agent/agent.db`,标明触发的 agent 与本地素材路径。API Key 不进入 prompt observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
|
||||
- 补充:规范 Agent ID 统一使用 manifest taskId,例如 `art-asset-plan` 和 `code-prototype`;历史前端曾使用的 `group-role` 别名只在 Tauri command 层兼容并映射到规范 taskId。主窗口 Agent 状态列表通过 `read_game_creator_agent_runtimes` 批量读取 `.agent/runtime/agents/<taskId>.json` 和最近任务,把每个 Agent 的 Runtime 状态、当前动作和最近 task 直接显示在状态卡片和 `/agents` 汇总里。
|
||||
- 2026-07-10 补充:单 Agent 聊天和后台 planning prompt 统一注入本 Agent 的 Runtime 连续上下文,包括最近状态、runId、当前任务、计划、观察、最近回复、最近工具动作、最近事件、最近任务和工具策略摘要;上下文只按规范 taskId 读取本 Agent runtime,进入 prompt 前过滤密钥和本机绝对路径。新后台 run 启动时继承同 Agent 上次 `recentToolCalls` 和 `lastResponse`,让下一轮任务能基于前一轮真实行动证据继续推理,同时不串入其他 Agent 的 runtime。
|
||||
- 影响范围:`apps/ai-game-creator-shell` 的 Tauri command、Agent Runtime state/event、开发窗口单 Agent 聊天、项目内 Agent 对话弹窗、`appSurface.test.ts` 和 AI 游戏创作 App 实施计划。
|
||||
- 验证方式:运行 Tauri Rust 后台 Agent 并行测试、壳前端 appSurface 测试、壳 typecheck、编码检查和 `git diff --check`。
|
||||
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
@@ -35,6 +35,7 @@ Agent Runtime 负责:
|
||||
- 后台任务能力:开发窗口单 Agent 聊天和项目内 Agent 对话弹窗可把当前输入投递为单 Agent 后台任务,Tauri 命令 `start_game_creator_agent_runtime_task` 会立即写入该 Agent 的 `.agent/runtime/agents/<agentId>.json`、`.agent/runtime/events/<agentId>.jsonl`、`.agent/runtime/tasks/<agentId>.jsonl` 和 `.agent/conversations/agents/<agentId>.jsonl`,随后在 App 进程内启动 tokio task 执行最小 Agent loop:每轮让该 Agent 输出 `thinkingSummary / plan / actions / response`,Runtime 按白名单和项目权限策略执行工具动作,写入 `action / observation` 事件,再把 observation 放入下一轮 prompt 让 Agent 修正计划、继续行动或用空 actions + response 收束;后台任务最多执行 3 轮 loop,仍未收束时再按最后计划和全部观察生成最终回复并追加回对话。工具箱包含只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`file.read`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write` 和 `agent.message`;`memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径并记录审计,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加 `memory/blackboard.md`,`agent.message` 给目标 `.agent/conversations/agents/<agentId>.jsonl` 写入 tool 留言,策略要求确认或拒绝时不执行写入或运行,只把策略结果作为 observation 回给 Agent。`read_game_creator_agent_runtime` 会按 `runId` 去重返回最近任务,`read_game_creator_agent_runtimes` 批量读取所有规范 taskId 的 runtime;开发窗口、项目内 Agent 对话弹窗和主窗口 Agent 状态列表展示最近任务、当前任务、当前动作、下一步和运行阶段。不同 Agent 使用各自 runtime 锁,可以并行运行;同一 Agent 已有运行任务时,新任务会先写成 `pending / queued`,由当前后台 drain 在完成后串行继续执行。该能力仍属于 Runtime V1 的进程内任务,不是独立 OS 进程或跨重启离线常驻 worker。
|
||||
- 2026-07-10 补充:Agent Runtime state 新增 `toolPolicy`,按当前项目 `.agent/policy.json` 派生工具级 `allowedTools / autoTools / confirmTools / deniedTools` 快照;后台 planning prompt 会带入该快照,让 Agent 在规划时知道哪些工具会自动执行、需要确认或被拒绝。`blackboard.write` 继承 `memory.write` 策略,`agent.message` 继承 `conversation.write` 策略;实际执行仍以 Runtime 的白名单和项目权限 gate 为准。
|
||||
- 2026-07-10 补充:Agent Runtime state 新增 `recentToolCalls`,每次后台工具执行后记录最近 20 条结构化工具动作,包含 tool、status、reason、summary、detail 和 updatedAt;开发窗口、项目内 Agent 对话弹窗和主窗口 Agent 状态列表可直接展示“最近动作”,不再只能从 observation 字符串里猜测 action / observation 对应关系。字段只保存过滤后的摘要和观察细节,不保存原始 API Key 或任意未过滤输入。
|
||||
- 2026-07-10 补充:单 Agent 聊天和后台 planning prompt 会读取同一个 Agent 的 Runtime 连续上下文,把本 Agent 最近 status / phase / runId / 当前任务 / 下一步、最近回复、计划、观察、最近 3 条工具动作、最近事件、最近 3 条任务记录和工具策略摘要带入下一轮推理;上下文按规范 taskId 隔离,不读取其他 Agent 的 runtime 文件,并在进入 prompt 前过滤密钥和本机绝对路径。新后台 run 启动时会继承本 Agent 上次 `recentToolCalls` 和 `lastResponse`,让多轮任务不丢失结构化行动证据。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `preview.start`。Agent 可在 loop 中自行请求启动当前项目的本地 HTTP 预览;Runtime 会复用 `preview.start` 策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑,并把 `agent.runtime.preview.start` 写入 `.agent/agent.db`。该 observation 只向 LLM 返回 localhost URL 与端口,不返回用户项目绝对路径。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `canvas.asset_generate`。Agent 可在 loop 中自行给出素材 prompt,通过 AppData / Tauri 配置里的 `editorApi` 调用 External Editor API 生成首版美术素材、下载到 `assets/canvas-generated/` 并登记 manifest;Runtime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `task.list`。Agent 可在 loop 中读取 manifest 任务图、每个 seed task 的状态 / 依赖 / 产物交接,以及按依赖计算的 `readyTaskIds`;Runtime 复用 `task.list` 项目权限策略,策略要求确认或拒绝时只返回策略 observation,不向 LLM 暴露任务图细节。
|
||||
@@ -78,6 +79,8 @@ game-project/
|
||||
<agentId>.json
|
||||
events/
|
||||
<agentId>.jsonl
|
||||
tasks/
|
||||
<agentId>.jsonl
|
||||
locks/
|
||||
<agentId>.lock
|
||||
activity.jsonl
|
||||
|
||||
Reference in New Issue
Block a user