From b30ca890bbaaafd183592778f8c774dad85a9554 Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Fri, 10 Jul 2026 06:16:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=B2=E8=A1=8C=E5=8C=96Agent=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0JSONL=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增按目标文件路径串行的本地追加写入helper。 让agent.db、对话、Runtime事件任务和Agent运行日志统一写完整JSON行。 补充并发追加测试覆盖JSONL逐行可解析。 同步Agent Runtime技术方案和决策记录。 --- .../src-tauri/src/agent.rs | 30 ++----- .../src-tauri/src/project.rs | 53 +++++++++---- .../src-tauri/src/tests.rs | 79 +++++++++++++++++++ .../shared-memory/decision-log.md | 1 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 3 +- 5 files changed, 126 insertions(+), 40 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent.rs b/apps/ai-game-creator-shell/src-tauri/src/agent.rs index 79661deab..778f18bc2 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent.rs @@ -3733,15 +3733,9 @@ fn append_game_creator_agent_runtime_event( detail: detail.map(|value| sanitize_agent_runtime_text(value, 500)), updated_at: unix_timestamp(), }; - let mut file = fs::OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .map_err(|error| format!("打开 Agent Runtime 事件失败:{}: {error}", path.display()))?; - serde_json::to_writer(&mut file, &event) + let line = serde_json::to_string(&event) .map_err(|error| format!("序列化 Agent Runtime 事件失败:{error}"))?; - file.write_all(b"\n") - .map_err(|error| format!("写入 Agent Runtime 事件失败:{}: {error}", path.display()))?; + append_jsonl_line(&path, &line, "Agent Runtime 事件")?; emit_game_creator_agent_runtime_update(root, &state.agent_id); Ok(()) } @@ -3884,15 +3878,9 @@ fn append_game_creator_agent_runtime_task_record( ) })?; } - let mut file = fs::OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .map_err(|error| format!("打开 Agent Runtime 任务失败:{}: {error}", path.display()))?; - serde_json::to_writer(&mut file, &record) + let line = serde_json::to_string(&record) .map_err(|error| format!("序列化 Agent Runtime 任务失败:{error}"))?; - file.write_all(b"\n") - .map_err(|error| format!("写入 Agent Runtime 任务失败:{}: {error}", path.display())) + append_jsonl_line(&path, &line, "Agent Runtime 任务") } fn refresh_game_creator_agent_runtime_task_queue( @@ -7550,15 +7538,9 @@ pub(crate) fn append_agent_run_jsonl( fs::create_dir_all(parent) .map_err(|error| format!("创建 Agent 事件目录失败:{}: {error}", parent.display()))?; } - let mut line = + let line = serde_json::to_string(value).map_err(|error| format!("序列化 Agent 事件失败:{error}"))?; - line.push('\n'); - fs::OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .and_then(|mut file| file.write_all(line.as_bytes())) - .map_err(|error| format!("写入 Agent 事件失败:{}: {error}", path.display())) + append_jsonl_line(&path, &line, "Agent 事件") } pub(crate) fn write_agent_run_context_bundle( diff --git a/apps/ai-game-creator-shell/src-tauri/src/project.rs b/apps/ai-game-creator-shell/src-tauri/src/project.rs index 0a04da8d8..c29e32712 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project.rs @@ -85,15 +85,44 @@ pub(crate) fn append_agent_db_record( "updatedAt".to_string(), serde_json::Value::Number(serde_json::Number::from(unix_timestamp())), ); + let line = serde_json::to_string(&record) + .map_err(|error| format!("序列化 Agent 本地索引失败:{error}"))?; + append_jsonl_line(&path, &line, "Agent 本地索引") +} + +static PROJECT_APPEND_LOCKS: OnceLock>>>> = OnceLock::new(); + +fn project_append_locks() -> &'static Mutex>>> { + PROJECT_APPEND_LOCKS.get_or_init(|| Mutex::new(BTreeMap::new())) +} + +fn project_append_lock_for(path: &Path) -> Result>, String> { + let mut locks = project_append_locks() + .lock() + .map_err(|_| "获取本地追加写锁失败:锁已损坏".to_string())?; + Ok(locks + .entry(path.to_path_buf()) + .or_insert_with(|| Arc::new(Mutex::new(()))) + .clone()) +} + +pub(crate) fn append_jsonl_line(path: &Path, line: &str, error_label: &str) -> Result<(), String> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|error| format!("创建{error_label}目录失败:{}: {error}", parent.display()))?; + } + let append_lock = project_append_lock_for(path)?; + let _append_guard = append_lock + .lock() + .map_err(|_| format!("获取{error_label}追加写锁失败:锁已损坏"))?; let mut file = fs::OpenOptions::new() .create(true) .append(true) - .open(&path) - .map_err(|error| format!("打开 Agent 本地索引失败:{}: {error}", path.display()))?; - let line = serde_json::to_string(&record) - .map_err(|error| format!("序列化 Agent 本地索引失败:{error}"))?; - file.write_all(format!("{line}\n").as_bytes()) - .map_err(|error| format!("写入 Agent 本地索引失败:{}: {error}", path.display())) + .open(path) + .map_err(|error| format!("打开{error_label}失败:{}: {error}", path.display()))?; + file.write_all(line.as_bytes()) + .and_then(|_| file.write_all(b"\n")) + .map_err(|error| format!("写入{error_label}失败:{}: {error}", path.display())) } #[derive(Debug)] @@ -310,15 +339,9 @@ pub(crate) fn append_local_conversation_message_at( agent_id: normalized_agent_id.clone(), updated_at: unix_timestamp(), }; - let mut file = fs::OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .map_err(|error| format!("打开对话记录失败:{}: {error}", path.display()))?; - serde_json::to_writer(&mut file, &record) - .map_err(|error| format!("序列化对话记录失败:{error}"))?; - file.write_all(b"\n") - .map_err(|error| format!("写入对话记录失败:{}: {error}", path.display()))?; + let line = + serde_json::to_string(&record).map_err(|error| format!("序列化对话记录失败:{error}"))?; + append_jsonl_line(&path, &line, "对话记录")?; append_agent_db_record( root, serde_json::json!({ diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests.rs b/apps/ai-game-creator-shell/src-tauri/src/tests.rs index 781ab63df..8c50f872b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests.rs @@ -125,6 +125,85 @@ fn use_test_runtime_config_dir(path: PathBuf) -> TestRuntimeConfigDirGuard { } } +#[test] +fn parallel_jsonl_appends_keep_records_line_delimited() { + let root = unique_project_path(); + init_local_game_project_at(&root, "project-1", "并行追加测试").expect("project init"); + let conversation_path = root.join(".agent/conversations/agents/design-director.jsonl"); + let worker_count = 8; + let records_per_worker = 20; + let gate = Arc::new((StdMutex::new(false), Condvar::new())); + let mut handles = Vec::new(); + + for worker_index in 0..worker_count { + let root = root.clone(); + let conversation_path = conversation_path.clone(); + let gate = Arc::clone(&gate); + handles.push(std::thread::spawn(move || { + let (lock, cvar) = &*gate; + let mut started = lock.lock().expect("start gate lock"); + while !*started { + started = cvar.wait(started).expect("wait start gate"); + } + drop(started); + + for record_index in 0..records_per_worker { + let payload = "x".repeat(4096); + let line = serde_json::json!({ + "schemaVersion": LOCAL_CONVERSATION_SCHEMA_VERSION, + "role": "assistant", + "content": format!("worker-{worker_index}-record-{record_index}-{payload}"), + "agentId": "design-director", + "updatedAt": unix_timestamp(), + }) + .to_string(); + append_jsonl_line(&conversation_path, &line, "测试对话记录") + .expect("append conversation line"); + append_agent_db_record( + &root, + serde_json::json!({ + "recordType": "test.parallel.append", + "workerIndex": worker_index, + "recordIndex": record_index, + }), + ) + .expect("append agent db record"); + } + })); + } + + { + let (lock, cvar) = &*gate; + let mut started = lock.lock().expect("start gate lock"); + *started = true; + cvar.notify_all(); + } + for handle in handles { + handle.join().expect("append worker"); + } + + let expected_records = worker_count * records_per_worker; + let conversation = fs::read_to_string(&conversation_path).expect("conversation file"); + let conversation_records = conversation.lines().collect::>(); + assert_eq!(conversation_records.len(), expected_records); + for line in conversation_records { + serde_json::from_str::(line).expect("conversation line should be valid json"); + } + + let agent_db = fs::read_to_string(root.join(".agent/agent.db")).expect("agent db"); + let mut parallel_records = 0; + for line in agent_db.lines() { + let value = + serde_json::from_str::(line).expect("agent db line should be valid json"); + if value.get("recordType").and_then(Value::as_str) == Some("test.parallel.append") { + parallel_records += 1; + } + } + assert_eq!(parallel_records, expected_records); + + fs::remove_dir_all(root).ok(); +} + #[test] fn agent_runtime_lock_status_keeps_fresh_same_process_locks_busy() { let root = unique_project_path(); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 59e6a4bcd..5c28789c5 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4065,6 +4065,7 @@ - 2026-07-10 调整:Agent Runtime 后台任务支持按 Agent / runId 取消和重试。取消通过 `.agent/runtime/cancel//.json` 写入本地取消请求,并向任务 JSONL、事件流和 `agent.db` 追加 `cancelled` 审计;pending 任务被取消后不会被 drain 消费,running 任务会在当前 LLM 或工具调用返回后的检查点停止,不再继续执行工具或保存最终 assistant 回复。重试只能基于已有非 running / pending / waiting-for-confirmation 任务创建新的 run,并继续走 `agent.resume` 自动权限和同一 Agent 队列锁。 - 2026-07-10 调整:Agent Runtime 后台任务的 `runId` 是同一 Agent 任务历史的身份,不允许复用覆盖。`start_game_creator_agent_runtime_task`、`agent.delegate` 和 retry 进入后台队列前会读取该 Agent 全量 task JSONL 历史;若调用方传入的规范化 runId 已存在,Runtime 自动追加 `-dup--` 生成实际 runId。任务队列、delegate observation 和 `agent.db` 审计都必须使用实际 runId,避免 `latest_game_creator_agent_runtime_tasks` 按 runId 去重时折叠掉不同任务。 - 2026-07-10 调整:Agent Runtime 的 `memory.write scope=agent` 只能写当前 Agent 自己的私有记忆。若 action 指定其他 `agentId / targetAgentId`,Runtime 返回 `blocked` observation,不写目标 Agent 私有记忆、不写 `agent.runtime.memory.write` 审计;跨 Agent 共享稳定结论必须走 `blackboard.write`,给单个 Agent 留上下文必须走 `agent.message`。 +- 2026-07-10 调整:Agent Runtime 和本地对话使用 append-only JSONL 作为事实源时,进程内必须按目标文件路径串行追加整行。`.agent/agent.db`、`.agent/conversations/**/*.jsonl`、`.agent/runtime/events/*.jsonl`、`.agent/runtime/tasks/*.jsonl`、`.agent/activity.jsonl` 和 `.agent/output.jsonl` 统一走共享追加 helper,避免多个后台 Agent 并行完成时 JSON record 与换行交错。 - 2026-07-10 调整:Agent Runtime state 新增 `recentToolCalls`,后台 loop 每次执行白名单工具后记录最近 20 条结构化动作,包含 tool、status、reason、summary、detail 和 updatedAt。状态面板展示最近动作时使用该字段,不解析 observation 文本;写入前继续过滤敏感上下文,不保存原始密钥或任意未过滤输入。 - 2026-07-10 调整:Agent Runtime state 新增 `currentGoal` 和 `waitingOn`。`currentGoal` 固定表达本轮任务目标,`waitingOn` 表达当前等待 LLM、工具观察、开发者输入或失败处理;后台任务生命周期、`agent.run_status` observation、下一轮 planning prompt、开发单 Agent 对话页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都必须展示同一份目标 / 等待状态。 - 2026-07-10 调整:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`。后台 Agent loop 每轮规划前刷新当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,字段只做运行观测,不改变 loop 上限或权限 gate。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 7c5c8b163..d8a9a8d4b 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -39,6 +39,7 @@ Agent Runtime 负责: - 2026-07-10 补充:后台 Agent 任务支持按 Agent / runId 取消和重试。取消会写 `.agent/runtime/cancel//.json`,并把任务 JSONL、事件流和 `agent.db` 记录到 `cancelled`;pending 任务被取消后不会再被同一 Agent drain 消费,running 任务会在当前 LLM / 工具调用返回后的检查点停止,不继续执行工具或保存最终 assistant 回复。重试只能基于已有非 running / pending / waiting-for-confirmation 任务创建新的 run,继续复用同一 Agent 队列锁和 `agent.resume` 自动权限。 - 2026-07-10 补充:后台 Agent 任务的 `runId` 在同一 Agent 内必须唯一,因为任务快照按 runId 去重表示同一 run 的最新状态。`start_game_creator_agent_runtime_task`、`agent.delegate` 和 retry 入队前会读取该 Agent 全量 task JSONL 历史;如果调用方传入的 runId 已存在,Runtime 自动追加 `-dup--` 后缀生成实际 runId,并在任务队列、delegate observation 和 `agent.db` 审计中使用该实际值,避免两个独立任务互相折叠。 - 2026-07-10 补充:后台 Agent 任务的 `memory.write scope=agent` 只允许写当前 Agent 自己的私有记忆。若 action 指定其他 `agentId / targetAgentId`,Runtime 返回 `blocked` observation,不写目标 Agent 私有记忆,也不写 `agent.runtime.memory.write` 审计;跨 Agent 共享稳定结论必须使用 `blackboard.write`,给单个 Agent 留上下文必须使用 `agent.message`。 +- 2026-07-10 补充:本地 append-only JSONL 追加写入按目标文件路径做进程内串行化。`.agent/agent.db`、项目 / Agent 对话、Runtime events、Runtime tasks、Agent activity 和 output 都通过共享 helper 写入完整 JSON 行,防止多个后台 Agent 并行运行时 record 内容与换行交错;该约束服务于当前单客户端进程内并行,不把跨进程同项目写入作为 v1 支持目标。 - 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 Runtime state 新增 `currentGoal` 和 `waitingOn`,把本轮目标与当前等待对象从 `currentTask / currentAction / nextStep` 中显式拆出来;后台任务启动、工具 observation、完成和失败都会刷新该状态,开发窗口、项目内 Agent 对话弹窗、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示同一份目标 / 等待信息,避免开发者只能从动作文本里猜 Agent 卡在 LLM、工具、同伴还是人工输入。 - 2026-07-10 补充:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`,结构化记录后台 Agent loop 当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,帮助判断 Agent 是刚开始规划、正在 replan,还是接近本轮 loop 上限。该字段只做运行观测,不改变后台 loop 的执行上限或工具权限。 @@ -277,7 +278,7 @@ game-project/ - Tauri Rust 入口保持薄壳:`src-tauri/src/main.rs` 只保留共享类型 / 常量、模块声明、CLI preflight、`tauri::Builder`、运行时配置初始化和 `invoke_handler` 清单;命令行入口放在 `cli.rs`,Tauri command 包装放在 `commands.rs`,运行时配置与 LLM 配置检查放在 `config.rs`,Agent loop 与生成编排放在 `agent.rs`,上传 / 画板 / 平台美术生成接入放在 `assets.rs`,本地项目文件、记忆、对话、权限、checkpoint、manifest 和通用路径工具放在 `project.rs`,本地 HTTP 预览与 preview 命令放在 `preview.rs`,旧窗口兼容命令放在 `windows.rs`,Rust 单测放在 `tests.rs`。后续继续拆分时保持 Tauri command 名、JSON 字段、`.agent/*` 路径和错误语义不变。 - 本地项目初始化会创建 `game/`、`assets/`、`memory/`、`memory/agents/`、`exports/`、`.agent/logs/`,写入 `.agent/manifest.json`,生成 append-only JSONL 本地项目索引 `.agent/agent.db`,并生成默认 `game/index.html`。 - v1 conversation 记录使用 append-only JSONL,每行带 `schemaVersion`、`role`、`content`、`agentId` 和 `updatedAt`,作为聊天历史和单 agent 对话历史的事实源;目录在首次写入时创建。 -- 开发窗口和项目内 Agent 对话弹窗的“后台运行”只启动或排队单 Agent 后台任务,不阻塞等待回复;用户可刷新同一 Agent 对话或 runtime 状态查看进度和结果,也可对当前 run 执行取消 / 重试。后台任务会向 `.agent/runtime/tasks/.jsonl` 追加任务视角记录,任务状态使用 `pending / running / waiting-for-confirmation / cancelled / completed / failed`,读取时按 `runId` 去重返回最近任务;`runId` 在同一 Agent 内是单个 run 的身份,后台入队会自动把重复 runId 改写为唯一实际 runId,防止不同任务互相覆盖;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行;若某个工具动作命中确认策略,该 Agent 会停在 `waiting-for-confirmation` 并暂停继续消费队列,等待后续确认或策略调整;若用户取消 pending 任务,drain 不再消费该 run,若取消 running 任务,则在当前 LLM 或工具调用返回后的检查点停止,不继续执行工具或保存最终 assistant 回复。客户端重开项目时会对当前项目路径自动尝试一次 Runtime 恢复;恢复命令必须通过 `agent.resume` 自动权限,默认确认策略下不会静默启动;同一 Agent 同时存在上一进程遗留 `running` 和 `pending` 时,先重接 `running`,再由 drain 继续 `pending`。后台任务的核心 loop 最多 3 轮:每轮把已有 observation 带回 LLM 让 Agent 重新规划,只有 actions 为空且 response 非空时提前收束,否则继续执行白名单工具,跑满后再进入最终回复整理。后台任务完成后会把 assistant 回复追加到对应 `.agent/conversations/agents/.jsonl`,并向 `.agent/agent.db` 写入 `agent.runtime.background_task.queued` / `agent.runtime.background_task` / `agent.runtime.background_task.recovered` / `agent.runtime.tool_observation` / `agent.runtime.tool_confirmation_required` / `agent.runtime.memory.write` / `agent.runtime.file.write` / `agent.runtime.command.run_limited` / `agent.runtime.blackboard.write` / `agent.runtime.agent.message` / `agent.runtime.agent.delegate` / `agent.runtime.background_task.cancelled` / `agent.runtime.background_task.retry` / `agent.runtime.background_task.completed` / `agent.runtime.background_task.failed` 审计记录。当前工具箱开放只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate`;`memory.write scope=agent` 只允许写当前 Agent 自己的私有记忆,跨 Agent 共享必须改用 `blackboard.write` 或 `agent.message`;若项目策略拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent 修正计划;若项目策略要求确认,Runtime 会保留待确认状态而不执行该工具。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;`.agent/agent.db` 追加写入按整行 JSONL 写入,减少多个 Agent 同时完成时的行交错风险。 +- 开发窗口和项目内 Agent 对话弹窗的“后台运行”只启动或排队单 Agent 后台任务,不阻塞等待回复;用户可刷新同一 Agent 对话或 runtime 状态查看进度和结果,也可对当前 run 执行取消 / 重试。后台任务会向 `.agent/runtime/tasks/.jsonl` 追加任务视角记录,任务状态使用 `pending / running / waiting-for-confirmation / cancelled / completed / failed`,读取时按 `runId` 去重返回最近任务;`runId` 在同一 Agent 内是单个 run 的身份,后台入队会自动把重复 runId 改写为唯一实际 runId,防止不同任务互相覆盖;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行;若某个工具动作命中确认策略,该 Agent 会停在 `waiting-for-confirmation` 并暂停继续消费队列,等待后续确认或策略调整;若用户取消 pending 任务,drain 不再消费该 run,若取消 running 任务,则在当前 LLM 或工具调用返回后的检查点停止,不继续执行工具或保存最终 assistant 回复。客户端重开项目时会对当前项目路径自动尝试一次 Runtime 恢复;恢复命令必须通过 `agent.resume` 自动权限,默认确认策略下不会静默启动;同一 Agent 同时存在上一进程遗留 `running` 和 `pending` 时,先重接 `running`,再由 drain 继续 `pending`。后台任务的核心 loop 最多 3 轮:每轮把已有 observation 带回 LLM 让 Agent 重新规划,只有 actions 为空且 response 非空时提前收束,否则继续执行白名单工具,跑满后再进入最终回复整理。后台任务完成后会把 assistant 回复追加到对应 `.agent/conversations/agents/.jsonl`,并向 `.agent/agent.db` 写入 `agent.runtime.background_task.queued` / `agent.runtime.background_task` / `agent.runtime.background_task.recovered` / `agent.runtime.tool_observation` / `agent.runtime.tool_confirmation_required` / `agent.runtime.memory.write` / `agent.runtime.file.write` / `agent.runtime.command.run_limited` / `agent.runtime.blackboard.write` / `agent.runtime.agent.message` / `agent.runtime.agent.delegate` / `agent.runtime.background_task.cancelled` / `agent.runtime.background_task.retry` / `agent.runtime.background_task.completed` / `agent.runtime.background_task.failed` 审计记录。当前工具箱开放只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate`;`memory.write scope=agent` 只允许写当前 Agent 自己的私有记忆,跨 Agent 共享必须改用 `blackboard.write` 或 `agent.message`;若项目策略拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent 修正计划;若项目策略要求确认,Runtime 会保留待确认状态而不执行该工具。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;append-only JSONL 写入按目标文件路径在当前进程内串行追加完整行,覆盖 `.agent/agent.db`、对话、Runtime events/tasks、activity 和 output,减少多个 Agent 同时完成时的行交错风险。 - 2026-07-10 补充:当前工具箱还开放 `preview.start`,审计记录类型为 `agent.runtime.preview.start`;该工具不会打开任意 URL,只启动当前授权项目的 `127.0.0.1` 本地预览,并和 Tauri 用户命令共用同一个 `PreviewRegistry`。 - 2026-07-10 补充:当前工具箱还开放 `canvas.asset_generate`,审计记录类型为 `agent.runtime.canvas.asset_generate`;该工具只通过配置好的 External Editor API 生成并回流素材,不暴露任意上传 / 任意网络请求能力。 - 2026-07-10 补充:当前工具箱还开放 `task.list`;该工具只读取 manifest 任务图、状态、依赖、产物和 `readyTaskIds`,并受 `task.list` 项目权限策略保护。