补齐Agent Runtime目标等待状态

Agent Runtime state 新增 currentGoal 和 waitingOn,写入后台任务生命周期。

状态面板、Agent 状态卡和 agent.run_status 展示当前目标与等待对象。

补充 Runtime 状态测试和 wire JSON 断言,同步 Runtime V1 文档。
This commit is contained in:
AIGameCreator App
2026-07-10 03:11:05 +08:00
parent 0326cc819c
commit 113d80f305
7 changed files with 126 additions and 6 deletions
@@ -542,6 +542,7 @@ async fn run_game_creator_agent_background_task(
let observation_summary = observation.summary();
runtime.observations.push(observation_summary.clone());
append_agent_runtime_tool_call_record(&mut runtime, action, &observation);
runtime.waiting_on = "Agent 根据工具观察修正计划".to_string();
runtime.next_step = "把工具观察交给 Agent 修正计划".to_string();
runtime.updated_at = unix_timestamp();
let _ = write_game_creator_agent_runtime_state(&root, &runtime);
@@ -2120,8 +2121,10 @@ fn agent_runtime_status_target_agent_id(agent_id: &str, input: &serde_json::Valu
fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> String {
let state = &result.state;
let current_goal = agent_runtime_status_text(&state.current_goal, 160);
let current_task = agent_runtime_status_text(&state.current_task, 160);
let current_action = agent_runtime_status_text(&state.current_action, 160);
let waiting_on = agent_runtime_status_text(&state.waiting_on, 160);
let next_step = agent_runtime_status_text(&state.next_step, 160);
let plan = if state.plan.is_empty() {
"-".to_string()
@@ -2151,13 +2154,15 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "-".to_string());
format!(
"agentId: {}\nstatus: {}\nphase: {}\nrunId: {}\n当前任务: {}\n当前动作: {}\n下一步: {}\n计划: {}\n最近任务: {}\n最近工具: {}\n错误: {}",
"agentId: {}\nstatus: {}\nphase: {}\nrunId: {}\n当前目标: {}\n当前任务: {}\n当前动作: {}\n等待: {}\n下一步: {}\n计划: {}\n最近任务: {}\n最近工具: {}\n错误: {}",
state.agent_id,
state.status,
state.phase,
state.run_id,
current_goal,
current_task,
current_action,
waiting_on,
next_step,
plan,
recent_task,
@@ -2304,7 +2309,9 @@ pub(crate) fn start_game_creator_agent_runtime_task_at(
state.status = "running".to_string();
state.phase = "planning".to_string();
state.current_task = runtime_task.clone();
state.current_goal = runtime_task.clone();
state.current_action = current_action.trim().to_string();
state.waiting_on = agent_runtime_waiting_on_for_phase(&state.phase).to_string();
state.next_step = "等待 Agent 输出计划或回复".to_string();
state.plan = plan;
state.observations = vec!["已创建本轮 Agent Runtime run。".to_string()];
@@ -2351,6 +2358,7 @@ pub(crate) fn advance_game_creator_agent_runtime_turn_at(
) -> Result<AgentRuntimeState, String> {
state.phase = phase.trim().to_string();
state.current_action = action.trim().to_string();
state.waiting_on = agent_runtime_waiting_on_for_phase(&state.phase).to_string();
state.next_step = agent_runtime_next_step_for_phase(&state.phase).to_string();
if !observation.trim().is_empty() {
state.observations.push(observation.trim().to_string());
@@ -2379,6 +2387,7 @@ pub(crate) fn finish_game_creator_agent_runtime_turn_at(
state.status = "idle".to_string();
state.phase = "completed".to_string();
state.current_action = "等待下一轮输入".to_string();
state.waiting_on = "开发者下一轮输入".to_string();
state.next_step = "等待下一轮输入".to_string();
state.last_response = Some(sanitize_agent_runtime_text(response, 500));
state.error = None;
@@ -2421,6 +2430,7 @@ pub(crate) fn fail_game_creator_agent_runtime_turn_at(
state.status = "failed".to_string();
state.phase = "failed".to_string();
state.current_action = "等待开发者处理失败".to_string();
state.waiting_on = "开发者处理失败".to_string();
state.next_step = "等待开发者处理失败".to_string();
state.error = Some(sanitize_agent_runtime_text(error, 500));
let _ = refresh_game_creator_agent_runtime_tool_policy(root, &mut state);
@@ -2491,7 +2501,9 @@ fn default_game_creator_agent_runtime_state(agent_id: &str, run_id: &str) -> Age
status: "idle".to_string(),
phase: "idle".to_string(),
current_task: String::new(),
current_goal: String::new(),
current_action: "等待输入".to_string(),
waiting_on: "开发者输入".to_string(),
next_step: "等待输入".to_string(),
plan: vec![
"读取项目上下文".to_string(),
@@ -2556,9 +2568,15 @@ fn normalize_game_creator_agent_runtime_state(state: &mut AgentRuntimeState, age
if state.phase.trim().is_empty() {
state.phase = "idle".to_string();
}
if state.current_goal.trim().is_empty() && !state.current_task.trim().is_empty() {
state.current_goal = state.current_task.clone();
}
if state.current_action.trim().is_empty() {
state.current_action = "等待输入".to_string();
}
if state.waiting_on.trim().is_empty() {
state.waiting_on = agent_runtime_waiting_on_for_phase(&state.phase).to_string();
}
if state.next_step.trim().is_empty() {
state.next_step = agent_runtime_next_step_for_phase(&state.phase).to_string();
}
@@ -2607,6 +2625,18 @@ fn agent_runtime_next_step_for_phase(phase: &str) -> &'static str {
}
}
fn agent_runtime_waiting_on_for_phase(phase: &str) -> &'static str {
match phase.trim() {
"planning" => "Agent 输出计划或回复",
"llm" => "Agent LLM 回复",
"action" => "工具观察结果",
"response" => "Agent 整理最终回复",
"completed" | "idle" => "开发者下一轮输入",
"failed" => "开发者处理失败",
_ => "当前任务推进",
}
}
fn normalize_game_creator_agent_runtime_event(event: &mut AgentRuntimeEvent) {
if event.schema_version.trim().is_empty() {
event.schema_version = AGENT_RUNTIME_SCHEMA_VERSION.to_string();
@@ -3173,12 +3203,24 @@ fn render_agent_runtime_prompt_context(root: &Path, agent_id: &str) -> Result<St
redact_agent_runtime_project_paths(root, &state.current_task, 220)
));
}
if !state.current_goal.trim().is_empty() {
lines.push(format!(
"当前目标:{}",
redact_agent_runtime_project_paths(root, &state.current_goal, 220)
));
}
if !state.current_action.trim().is_empty() {
lines.push(format!(
"当前动作:{}",
redact_agent_runtime_project_paths(root, &state.current_action, 160)
));
}
if !state.waiting_on.trim().is_empty() {
lines.push(format!(
"等待:{}",
redact_agent_runtime_project_paths(root, &state.waiting_on, 160)
));
}
if !state.next_step.trim().is_empty() {
lines.push(format!(
"下一步:{}",
@@ -154,8 +154,12 @@ struct AgentRuntimeState {
#[serde(default)]
current_task: String,
#[serde(default)]
current_goal: String,
#[serde(default)]
current_action: String,
#[serde(default)]
waiting_on: String,
#[serde(default)]
next_step: String,
#[serde(default)]
plan: Vec<String>,
@@ -1133,9 +1133,20 @@ async fn role_agent_legacy_alias_maps_to_canonical_task_runtime_and_route() {
assert_eq!(runtime.agent_id, "art-asset-plan");
assert_eq!(runtime.task_id, "art-asset-plan");
assert_eq!(runtime.session_id, "agent-session-art-asset-plan");
assert_eq!(runtime.current_goal, "排队规划美术资产");
assert_eq!(runtime.waiting_on, "Agent 输出计划或回复");
let runtime_wire: Value = serde_json::from_str(
&fs::read_to_string(root.join(".agent/runtime/agents/art-asset-plan.json"))
.expect("runtime wire json"),
)
.expect("parse runtime wire json");
assert_eq!(runtime_wire["currentGoal"], "排队规划美术资产");
assert_eq!(runtime_wire["waitingOn"], "Agent 输出计划或回复");
let alias_read =
read_game_creator_agent_runtime_at(&root, "art-asset").expect("read alias runtime");
assert_eq!(alias_read.state.agent_id, "art-asset-plan");
assert_eq!(alias_read.state.current_goal, "排队规划美术资产");
assert_eq!(alias_read.state.waiting_on, "Agent 输出计划或回复");
assert!(alias_read
.session_path
.ends_with(".agent/runtime/agents/art-asset-plan.json"));
@@ -4089,7 +4100,7 @@ async fn background_agent_runtime_can_read_other_agent_status() {
vec!["确认角色设定".to_string(), "生成规范图".to_string()],
)
.expect("start art runtime");
advance_game_creator_agent_runtime_turn_at(
let art_runtime = advance_game_creator_agent_runtime_turn_at(
&root,
art_runtime,
"action",
@@ -4097,6 +4108,8 @@ async fn background_agent_runtime_can_read_other_agent_status() {
"已拿到角色外观设定,等待素材生成结果。",
)
.expect("advance art runtime");
assert_eq!(art_runtime.current_goal, "生成角色规范图");
assert_eq!(art_runtime.waiting_on, "工具观察结果");
write_project_permission_policy_at(
&root,
ProjectPermissionPolicy {
@@ -4161,8 +4174,10 @@ async fn background_agent_runtime_can_read_other_agent_status() {
assert!(final_request.contains("agentId: art-director"));
assert!(final_request.contains("status: running"));
assert!(final_request.contains("phase: action"));
assert!(final_request.contains("当前目标: 生成角色规范图"));
assert!(final_request.contains("当前任务: 生成角色规范图"));
assert!(final_request.contains("当前动作: 正在生成角色规范图"));
assert!(final_request.contains("等待: 工具观察结果"));
assert!(!final_request.contains(".agent/runtime/agents/art-director.json"));
let runtime = wait_for_agent_runtime_idle(&root, "design-director");
+42
View File
@@ -237,7 +237,9 @@ interface AgentRuntimeState {
status: string;
phase: string;
currentTask: string;
currentGoal?: string;
currentAction: string;
waitingOn?: string;
nextStep?: string;
plan: string[];
observations: string[];
@@ -517,6 +519,9 @@ function agentRuntimeStateFromResult(
): AgentRuntimeState {
return {
...result.state,
currentGoal: result.state.currentGoal ?? result.state.currentTask ?? '',
waitingOn:
result.state.waitingOn ?? agentRuntimeWaitingOnFromPhase(result.state.phase),
nextStep:
result.state.nextStep ?? agentRuntimeNextStepFromPhase(result.state.phase),
recentToolCalls: result.state.recentToolCalls ?? [],
@@ -531,6 +536,26 @@ function agentRuntimeStateFromResult(
};
}
function agentRuntimeWaitingOnFromPhase(phase: string) {
switch (phase) {
case 'planning':
return 'Agent 输出计划或回复';
case 'llm':
return 'Agent LLM 回复';
case 'action':
return '工具观察结果';
case 'response':
return 'Agent 整理最终回复';
case 'completed':
case 'idle':
return '开发者下一轮输入';
case 'failed':
return '开发者处理失败';
default:
return '当前任务推进';
}
}
function agentRuntimeNextStepFromPhase(phase: string) {
switch (phase) {
case 'planning':
@@ -584,6 +609,8 @@ function AgentRuntimeStatusPanel({
const recentTasks = (runtime.recentTasks ?? []).slice(-3).reverse();
const toolPolicy = runtime.toolPolicy;
const nextStep = runtime.nextStep ?? agentRuntimeNextStepFromPhase(runtime.phase);
const currentGoal = runtime.currentGoal ?? runtime.currentTask;
const waitingOn = runtime.waitingOn ?? agentRuntimeWaitingOnFromPhase(runtime.phase);
return (
<section className="agent-runtime-status" aria-label="Agent Runtime 状态">
<header>
@@ -592,8 +619,10 @@ function AgentRuntimeStatusPanel({
</header>
<small>{`task: ${runtime.taskId} · ${runtime.source}`}</small>
{runtime.runId ? <small>{`run: ${runtime.runId}`}</small> : null}
{currentGoal ? <p>{`当前目标:${currentGoal}`}</p> : null}
{runtime.currentTask ? <p>{runtime.currentTask}</p> : null}
<small>{runtime.currentAction}</small>
{waitingOn ? <small>{`等待:${waitingOn}`}</small> : null}
{nextStep ? <small>{`下一步:${nextStep}`}</small> : null}
{toolPolicy ? (
<small>
@@ -763,7 +792,9 @@ interface AgentStatusCard {
toolCalls: GameCreationAgentToolCallTrace[];
runtimeStatus: string | null;
runtimePhase: string | null;
runtimeGoal: string | null;
runtimeAction: string | null;
runtimeWaitingOn: string | null;
runtimeNextStep: string | null;
runtimeTask: string | null;
runtimeRunId: string | null;
@@ -10075,7 +10106,11 @@ export function deriveAgentStatusCards(
toolCalls: latestStep?.toolCalls ?? [],
runtimeStatus: runtime?.status ?? null,
runtimePhase: runtime?.phase ?? null,
runtimeGoal: runtime?.currentGoal ?? runtime?.currentTask ?? null,
runtimeAction: runtime?.currentAction ?? null,
runtimeWaitingOn: runtime
? runtime.waitingOn ?? agentRuntimeWaitingOnFromPhase(runtime.phase)
: null,
runtimeNextStep: runtime
? runtime.nextStep ?? agentRuntimeNextStepFromPhase(runtime.phase)
: null,
@@ -10107,7 +10142,9 @@ function sameAgentStatusCard(left: AgentStatusCard, right: AgentStatusCard) {
left.lifecycleStatus === right.lifecycleStatus &&
left.runtimeStatus === right.runtimeStatus &&
left.runtimePhase === right.runtimePhase &&
left.runtimeGoal === right.runtimeGoal &&
left.runtimeAction === right.runtimeAction &&
left.runtimeWaitingOn === right.runtimeWaitingOn &&
left.runtimeNextStep === right.runtimeNextStep &&
left.runtimeTask === right.runtimeTask &&
left.runtimeRunId === right.runtimeRunId &&
@@ -10893,6 +10930,7 @@ function formatAgentCardRuntimeStatus(agent: AgentStatusCard) {
return [
`Runtime${agent.runtimeStatus} / ${agent.runtimePhase ?? '-'}`,
agent.runtimeAction,
agent.runtimeWaitingOn ? `等待 ${agent.runtimeWaitingOn}` : null,
agent.runtimeNextStep ? `下一步 ${agent.runtimeNextStep}` : null,
agent.runtimeRunId ? `run ${agent.runtimeRunId}` : null,
]
@@ -10956,6 +10994,7 @@ function summarizeAgentStatusCardsForChat(
return [
`- ${parts.join(' · ')}`,
` ${agent.summary}`,
agent.runtimeGoal ? ` 当前目标:${agent.runtimeGoal}` : null,
latestRuntimeTask
? ` 最近任务:${formatAgentRecentRuntimeTask(latestRuntimeTask)}`
: null,
@@ -20199,6 +20238,9 @@ export function App() {
{agentRuntimeStatus ? (
<small>{agentRuntimeStatus}</small>
) : null}
{agent.runtimeGoal ? (
<small>{`当前目标:${agent.runtimeGoal}`}</small>
) : null}
{agent.runtimeTask ? (
<small>{`当前任务:${agent.runtimeTask}`}</small>
) : null}
@@ -1120,7 +1120,9 @@ describe('AI 游戏创作 App 界面边界', () => {
status: 'running',
phase: 'llm',
currentTask: '请流式回答',
currentGoal: '请流式回答',
currentAction: '请求 Agent LLM',
waitingOn: 'Agent LLM 回复',
plan: ['读取项目上下文', '按角色职责推理', '流式回复'],
observations: ['已创建本轮 Agent Runtime run。'],
allowedTools: ['conversation.read', 'conversation.write'],
@@ -1133,6 +1135,7 @@ describe('AI 游戏创作 App 界面边界', () => {
status: 'idle',
phase: 'completed',
currentAction: '等待下一轮输入',
waitingOn: '开发者下一轮输入',
lastResponse: '专业 Agent 已流式完成。',
observations: [
'已创建本轮 Agent Runtime run。',
@@ -1289,7 +1292,9 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(await screen.findByLabelText('Agent Runtime 状态')).not.toBeNull();
expect(screen.getByText('running / llm')).not.toBeNull();
expect(screen.getByText('agent-session-design-director')).not.toBeNull();
expect(screen.getByText('当前目标:请流式回答')).not.toBeNull();
expect(screen.getAllByText('请求 Agent LLM').length).toBeGreaterThan(0);
expect(screen.getByText('等待:Agent LLM 回复')).not.toBeNull();
expect(screen.getByText('正在接收 Agent 回复')).not.toBeNull();
expect(invoke).toHaveBeenCalledWith(
'chat_with_game_creator_role_agent_stream',
@@ -1310,6 +1315,7 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(await screen.findByText('专业 Agent 已流式完成。')).not.toBeNull();
expect(await screen.findByText('idle / completed')).not.toBeNull();
expect(screen.getAllByText('等待下一轮输入').length).toBeGreaterThan(0);
expect(screen.getByText('等待:开发者下一轮输入')).not.toBeNull();
expect(await screen.findByText(/已保存 2 条/)).not.toBeNull();
expect(persistedMessages).toEqual([
{
@@ -1341,7 +1347,9 @@ describe('AI 游戏创作 App 界面边界', () => {
status: 'running',
phase: 'action',
currentTask: '后台整理角色规范',
currentGoal: '完成角色规范阶段',
currentAction: '调用工具 file.read',
waitingOn: '工具观察结果',
plan: ['读取项目笔记', '结合观察修正建议', '回复开发者'],
observations: ['思考摘要:需要先看项目笔记', 'file.readok · 已读取 game/notes.txt'],
allowedTools: ['conversation.read', 'conversation.write'],
@@ -1455,7 +1463,9 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(await screen.findByText('后台整理角色规范')).not.toBeNull();
expect(await screen.findByText('running / action')).not.toBeNull();
expect(screen.getByText(/agent-background-task/)).not.toBeNull();
expect(screen.getByText('当前目标:完成角色规范阶段')).not.toBeNull();
expect(screen.getByText('调用工具 file.read')).not.toBeNull();
expect(screen.getByText('等待:工具观察结果')).not.toBeNull();
expect(screen.getByText('读取项目笔记')).not.toBeNull();
expect(screen.getByText('结合观察修正建议')).not.toBeNull();
expect(screen.getByText('回复开发者')).not.toBeNull();
@@ -13190,7 +13200,9 @@ describe('AI 游戏创作 App 界面边界', () => {
status: 'running',
phase: 'planning',
currentTask: '拆解关卡节奏',
currentGoal: '补齐第一关节奏目标',
currentAction: '整理目标和约束',
waitingOn: 'Agent 输出计划或回复',
plan: ['读取项目上下文'],
observations: ['已创建本轮 Agent Runtime run。'],
allowedTools: ['conversation.read'],
@@ -13248,7 +13260,10 @@ describe('AI 游戏创作 App 界面边界', () => {
name: /拆解创作方向/,
});
expect(designCard.textContent).toContain(
'Runtimerunning / planning · 整理目标和约束 · 下一步 等待 Agent 输出计划或回复 · run runtime-design-director-1',
'Runtimerunning / planning · 整理目标和约束 · 等待 Agent 输出计划或回复 · 下一步 等待 Agent 输出计划或回复 · run runtime-design-director-1',
);
expect(designCard.textContent).toContain(
'当前目标:补齐第一关节奏目标',
);
expect(designCard.textContent).toContain('当前任务:拆解关卡节奏');
expect(designCard.textContent).toContain(
@@ -4060,6 +4060,7 @@
- 2026-07-09 调整:AI 游戏创作 App 新增 Agent Runtime V1 最小可观测状态。单 Agent 对话和生成 loop 中的角色 brief 必须写 `.agent/runtime/agents/<agentId>.json``.agent/runtime/events/<agentId>.jsonl`,记录 `agentId``taskId``sessionId``runId``source``status``phase`、当前任务 / 动作、计划、观测、允许工具、最近回复和错误;单 Agent 流式聊天事件要回传最新 `runtimeState`,开发单 Agent 聊天页和项目内单 Agent 对话弹窗只读展示该状态,读取失败必须可见提示,不得静默伪装为空状态。`source=agent-chat` 表示开发者单 Agent 对话,`source=generate-draft` 表示生成 loop 角色 briefcarry-over brief 只记录继承和完成,不伪装成重新调用 LLM。Runtime state 写入使用临时文件替换,event JSONL 读取跳过坏行,用户 prompt / 回复摘要进入 runtime 与 `agent.db` 前复用敏感上下文过滤;`.agent/runtime/` 是运行观测状态,不进入项目索引、checkpoint diff 或 restore 删除范围。该层仍是本地 JSONL 状态与事件,不引入 SQLite、常驻独立进程、远程 runner 或可中断上游 LLM 的承诺。
- 2026-07-10 调整:Agent Runtime state 新增 `toolPolicy`,从项目权限策略派生工具级 `allowedTools``autoTools``confirmTools``deniedTools`。后台 planning prompt 必须带入该快照,让 Agent 在规划阶段知道工具策略;执行阶段仍由 Runtime 白名单和项目权限 gate 决定。`blackboard.write` 继承 `memory.write` 策略,`agent.message` 继承 `conversation.write` 策略,`agent.delegate` 使用独立 `agent.delegate` 策略。
- 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 V1 后台工具箱新增只读 `task.list`。Agent 可自行读取 `.agent/manifest.json` 的 seed task 状态、依赖、产物交接和按依赖计算的 `readyTaskIds`,用于判断下一步任务;该工具必须受 `task.list` 项目权限策略保护,策略要求确认或拒绝时不得把任务图细节放进 observation。
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增受策略保护的 `task.update`。Agent 只能把 `.agent/manifest.json` 中已有 seed task 的状态更新为 `pending``running``waiting-for-confirmation``completed``failed`Runtime 必须复用项目写锁、`task.update` 权限策略和 `.agent/agent.db` 审计记录;策略要求确认或拒绝时不得修改 manifest,不得创建新任务。
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `file.list`。Agent 可自行列出项目文件摘要或相对路径范围内的条目,先观察项目结构再决定是否读取具体文件;该工具必须受 `file.list` 项目权限策略保护,observation 只返回项目相对路径、类型和大小,不读取文件内容、不返回本机绝对路径。
@@ -32,9 +32,10 @@ Agent Runtime 负责:
- 开发窗口能力:debug 构建额外打开 `developer` 窗口,走 `index.html?agent-chat`;开发者可选择 Agent、授权本地项目路径,并通过 `read_local_conversation` / `append_local_conversation_message` 读写 `.agent/conversations/agents/<agentId>.jsonl`,通过 `agentLlm.<agentId>` 调用该 Agent 的独立 LLM 路由做真实对话,用于单独调试某个 Agent 的长期对话上下文。这里的 `<agentId>` 以 manifest taskId 为规范值,旧 `group-role` 别名只作为兼容输入映射到 taskId。
- 命令能力:内置命令调用、权限 gate、执行日志;v1 只允许白名单受限命令,不执行任意 shell。
- 编排能力:任务拆分、任务图依赖、专业组调度、多智能体协作;Runtime V1 会为单 Agent 对话和生成 loop 中的角色 brief 写入独立 runtime state / event,先解决“每个 Agent 正在做什么、跑到哪一步、最近一次 task/run 是什么”的可观测性。
- 后台任务能力:开发窗口单 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``project.diff``file.list``file.read``agent.run_status`,以及受策略保护的写/运行工具 `memory.write``file.write``command.run_limited``blackboard.write``agent.message``agent.delegate``memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径并记录审计,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加 `memory/blackboard.md``agent.message` 给目标 `.agent/conversations/agents/<agentId>.jsonl` 写入 tool 留言,`agent.delegate` 把任务投递到目标 Agent 的独立后台队列,策略要求确认或拒绝时不执行写入、运行或委派,只把策略结果作为 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。
- 后台任务能力:开发窗口单 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``project.diff``file.list``file.read``agent.run_status`,以及受策略保护的写/运行工具 `memory.write``file.write``command.run_limited``blackboard.write``agent.message``agent.delegate``memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径并记录审计,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加 `memory/blackboard.md``agent.message` 给目标 `.agent/conversations/agents/<agentId>.jsonl` 写入 tool 留言,`agent.delegate` 把任务投递到目标 Agent 的独立后台队列,策略要求确认或拒绝时不执行写入、运行或委派,只把策略结果作为 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` 策略,`agent.delegate` 使用独立 `agent.delegate` 策略;实际执行仍以 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 Runtime state 新增 `currentGoal``waitingOn`,把本轮目标与当前等待对象从 `currentTask / currentAction / nextStep` 中显式拆出来;后台任务启动、工具 observation、完成和失败都会刷新该状态,开发窗口、项目内 Agent 对话弹窗、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示同一份目标 / 等待信息,避免开发者只能从动作文本里猜 Agent 卡在 LLM、工具、同伴还是人工输入。
- 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/` 并登记 manifestRuntime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
@@ -294,8 +295,8 @@ game-project/
- loop 每次运行会写 `.agent/run.latest.json``.agent/runs/<runId>.json`,记录 `Planner` / `Orchestrator` agenda / 16 个组内角色 brief 或 carry-over / 6 个 `GroupCoordinator` 汇总 / `Generator` / 6 个专业组交接 / `Evaluator` 质量评审 / `ArtifactWriter` / `Playtest` step、每步 `toolCalls`、输入文件、输出文件、状态、轮次、maxPasses、toolCallCount、maxToolCalls、stopReason、nextStep 和错误摘要;Planner、角色 agent 和 Generator 的 `inputPaths` 必须包含对应记忆文件、`.agent/conversations/project.jsonl``.agent/conversations/agents/``.agent/manifest.json` 和 agenda 等上下文来源,其中角色 agent 必须包含自己的 `memory/agents/<group>/<role>.md``memory/blackboard.md`conversation 输入只取最近少量 project / agent 对话摘要,不读取全量历史;每个 step 必须带 phase、taskId、group 和 role`.agent/run.latest.json.taskGraph` 必须记录 goal、readyTaskIds、activeTaskIds、carriedTaskIds、repairFocus、repairRoutes 和当前任务状态;`.agent/run.latest.json.passPlans` 必须按轮记录 mode、summary、activeTaskIds、carriedTaskIds、dependencyWaves、repairFocus 和 repairRoutes,作为 `/trace` 与开发窗口判断编排 loop 是否真实发生的直接证据;`run.latest.json` 是当前指针,`.agent/runs/` 保留历史 run trace;开发窗口读取 latest 展示编排过程,并复用 `file.list/read` 按文件修改时间先载入最近 20 个历史 run,滚动时再按批次读取剩余历史,普通用户窗口不展示。
- `.agent/run.latest.json``artifacts` 使用结构化记录,包含相对路径、字节数和 `fnv1a64:` checksum;除最终本地产物外,也会收集 `.agent/passes/pass-N/` 快照,便于确认返工前后的产物差异。
- 通过 Evaluator 和 `game.static_smoke` 后,Agent loop 会把本次 runId、状态、轮次、下一步、active / carry-over 任务和最终本地产物摘要追加到 `memory/session.md``memory/project.md`,把重要跨 agent 决策 / 依赖 / 风险摘要追加到 `memory/blackboard.md`,并把各角色本轮成功产出的角色摘要追加到 `memory/agents/<group>/<role>.md`;下一次 Planner、组内角色和 Generator 会通过记忆输入自然读取上一轮稳定原型状态,而不只依赖开发窗口 trace。
- 单 agent 对话入口读取对应 agent conversation;用户提交后先追加用户消息,再调用 `chat_with_game_creator_role_agent` / `chat_with_game_creator_role_agent_stream` 让对应 `agentLlm.<agentId>` 结合项目上下文、Agent 私有记忆和本 Agent 历史对话生成回复,随后把回复写入对应 `.agent/conversations/agents/<agentId>.jsonl`。这里的 `<agentId>` 以任务 `taskId` 为规范值,Tauri 只兼容旧 `group-role` 别名并映射到 taskId。每轮对话会同步写 `.agent/runtime/agents/<agentId>.json``.agent/runtime/events/<agentId>.jsonl`,字段包含 `agentId``taskId``sessionId``runId``source``status``phase``currentTask``currentAction``plan``observations``allowedTools``lastResponse``error`;流式事件会把最新 `runtimeState` 回传给界面。Runtime state 写入使用临时文件替换,event JSONL 读取会跳过坏行;`currentTask`、event detail、`lastResponse``agent.db` 摘要复用敏感上下文过滤,不保存明显 API Key / Bearer / Cookie 片段。单 agent 面板可把当前输入手动追加到对应 `memory/agents/<group>/<role>.md`,写入前复用 `memory.write` 项目策略和本地项目锁;最近对话可作为本次生成 prompt 上下文读取,但只有经过显式总结、用户显式手动沉淀或生成 loop 成功沉淀的稳定结论,才追加到 `memory/blackboard.md``memory/agents/<group>/<role>.md`
- 生成 loop 中的角色 brief 也写同一套 Agent Runtime state / eventactive 角色用 `source=generate-draft` 和当前 `runId` 标记正在读取上下文、调用角色专属 LLM 或本地编排、生成 brief、完成或失败;carry-over 角色同样写入开始 / 完成事件,但不会伪装成重新调用 LLM。主窗口 Agent 状态列表、开发单 Agent 聊天页和项目内单 Agent 对话弹窗只读展示当前 Agent 的 runtime 状态、最近 task/run、阶段、动作、下一步、计划观测;这只是 V1 可观测性,不代表已经有独立后台常驻进程或可中断任意上游 LLM 请求。
- 单 agent 对话入口读取对应 agent conversation;用户提交后先追加用户消息,再调用 `chat_with_game_creator_role_agent` / `chat_with_game_creator_role_agent_stream` 让对应 `agentLlm.<agentId>` 结合项目上下文、Agent 私有记忆和本 Agent 历史对话生成回复,随后把回复写入对应 `.agent/conversations/agents/<agentId>.jsonl`。这里的 `<agentId>` 以任务 `taskId` 为规范值,Tauri 只兼容旧 `group-role` 别名并映射到 taskId。每轮对话会同步写 `.agent/runtime/agents/<agentId>.json``.agent/runtime/events/<agentId>.jsonl`,字段包含 `agentId``taskId``sessionId``runId``source``status``phase``currentTask``currentGoal``currentAction``waitingOn``nextStep``plan``observations``recentToolCalls``toolPolicy``allowedTools``lastResponse``error`;流式事件会把最新 `runtimeState` 回传给界面。Runtime state 写入使用临时文件替换,event JSONL 读取会跳过坏行;`currentTask``currentGoal`event detail、`lastResponse``agent.db` 摘要复用敏感上下文过滤,不保存明显 API Key / Bearer / Cookie 片段。单 agent 面板可把当前输入手动追加到对应 `memory/agents/<group>/<role>.md`,写入前复用 `memory.write` 项目策略和本地项目锁;最近对话可作为本次生成 prompt 上下文读取,但只有经过显式总结、用户显式手动沉淀或生成 loop 成功沉淀的稳定结论,才追加到 `memory/blackboard.md``memory/agents/<group>/<role>.md`
- 生成 loop 中的角色 brief 也写同一套 Agent Runtime state / eventactive 角色用 `source=generate-draft` 和当前 `runId` 标记正在读取上下文、调用角色专属 LLM 或本地编排、生成 brief、完成或失败;carry-over 角色同样写入开始 / 完成事件,但不会伪装成重新调用 LLM。主窗口 Agent 状态列表、开发单 Agent 聊天页和项目内单 Agent 对话弹窗只读展示当前 Agent 的 runtime 状态、最近 task/run、阶段、当前目标、动作、等待对象、下一步、计划观测和最近工具动作;这只是 V1 可观测性,不代表已经有独立后台常驻进程或可中断任意上游 LLM 请求。
- `.agent/agent.db` 当前作为最小本地项目索引文件使用 JSONL:初始化写入 `project.init`,每次 `game.generate_draft` 追加目标、标题、本地产物路径、checkpoint 和 diff 摘要,上传 / 登记 / 画板导入资产时追加 `asset.register``asset.update`v1 不引入 SQLite 依赖。
- `game.generate_draft`、资产登记 / 导入、记忆写入、预览状态写入、checkpoint / restore、agent 生命周期控制、画板资源回流 / 生成和 policy 写入会先按 `.agent/policy.json` 判断本次命令是否被项目策略拒绝,再拿项目级 `.agent/project.lock` 串行化;锁只保护同一本地项目,v1 不做后台锁管理。`confirmCommands` 可把索引、状态读取、资产登记、checkpoint、预览、agent 生命周期、画板资源回流 / 生成、memory 读写删除和 conversation 读写等命令转成项目策略确认,命中时用户确认后才执行;用户可用 `/policy-confirm 命令` 加入确认列表,用 `/policy-auto 命令` 移除确认项。
- `ArtifactWriter` 写入最终产物前把当前项目文件保存到 `.agent/checkpoints/<checkpointId>/`,写入后把新增、修改、删除计数记录到 `.agent/agent.db`;聊天命令 `/checkpoint``/checkpoints``/diff checkpoint-id``/restore checkpoint-id` 允许用户手动保存、列出最近 checkpoint、对比和确认回滚到 checkpoint,回滚时会删除 checkpoint 后新增的受跟踪项目文件。`.agent/runtime/` 属于运行观测状态,不进入项目索引、checkpoint diff 或 restore 删除范围。