保留Agent工具确认等待态
后台Runtime命中工具确认策略时停在waiting-for-confirmation。 任务队列和前端状态面板展示等待确认计数。 补充Rust和前端测试覆盖工具确认等待态。 同步AI游戏创作App技术方案和决策记录。
This commit is contained in:
@@ -468,8 +468,17 @@ async fn drain_game_creator_agent_background_tasks(
|
||||
first_task: String,
|
||||
first_state: AgentRuntimeState,
|
||||
) {
|
||||
run_game_creator_agent_background_task(root.clone(), agent_id.clone(), first_task, first_state)
|
||||
.await;
|
||||
if run_game_creator_agent_background_task(
|
||||
root.clone(),
|
||||
agent_id.clone(),
|
||||
first_task,
|
||||
first_state,
|
||||
)
|
||||
.await
|
||||
== AgentBackgroundTaskOutcome::WaitingForConfirmation
|
||||
{
|
||||
return;
|
||||
}
|
||||
loop {
|
||||
let Some(next_task) = read_next_pending_game_creator_agent_runtime_task(&root, &agent_id)
|
||||
.ok()
|
||||
@@ -495,13 +504,17 @@ async fn drain_game_creator_agent_background_tasks(
|
||||
}
|
||||
};
|
||||
let _ = append_game_creator_agent_background_task_started_record(&root, &state);
|
||||
run_game_creator_agent_background_task(
|
||||
if run_game_creator_agent_background_task(
|
||||
root.clone(),
|
||||
agent_id.clone(),
|
||||
next_task.task,
|
||||
state,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
== AgentBackgroundTaskOutcome::WaitingForConfirmation
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +523,7 @@ async fn run_game_creator_agent_background_task(
|
||||
agent_id: String,
|
||||
task: String,
|
||||
state: AgentRuntimeState,
|
||||
) {
|
||||
) -> AgentBackgroundTaskOutcome {
|
||||
let mut runtime = state;
|
||||
let mut plan = AgentRuntimeToolPlan::default();
|
||||
let mut observations = Vec::new();
|
||||
@@ -535,7 +548,7 @@ async fn run_game_creator_agent_background_task(
|
||||
Err(error) => {
|
||||
let fallback = default_game_creator_agent_runtime_state(&agent_id, "");
|
||||
let _ = fail_game_creator_agent_runtime_turn_at(&root, fallback, &error);
|
||||
return;
|
||||
return AgentBackgroundTaskOutcome::Finished;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -575,7 +588,7 @@ async fn run_game_creator_agent_background_task(
|
||||
}),
|
||||
);
|
||||
}
|
||||
return;
|
||||
return AgentBackgroundTaskOutcome::Finished;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -647,7 +660,7 @@ async fn run_game_creator_agent_background_task(
|
||||
Err(error) => {
|
||||
let fallback = default_game_creator_agent_runtime_state(&agent_id, "");
|
||||
let _ = fail_game_creator_agent_runtime_turn_at(&root, fallback, &error);
|
||||
return;
|
||||
return AgentBackgroundTaskOutcome::Finished;
|
||||
}
|
||||
};
|
||||
let _ = append_game_creator_agent_runtime_event(
|
||||
@@ -665,25 +678,45 @@ 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);
|
||||
complete_agent_runtime_active_plan_step(
|
||||
&mut runtime,
|
||||
if observation.status == "ok" {
|
||||
"completed"
|
||||
} else {
|
||||
"failed"
|
||||
},
|
||||
&observation_summary,
|
||||
);
|
||||
runtime.waiting_on = "Agent 根据工具观察修正计划".to_string();
|
||||
runtime.next_step = "把工具观察交给 Agent 修正计划".to_string();
|
||||
if observation.is_waiting_for_confirmation() {
|
||||
runtime.status = "waiting-for-confirmation".to_string();
|
||||
runtime.phase = "waiting-for-confirmation".to_string();
|
||||
runtime.current_action = format!("等待确认工具 {}", observation.tool);
|
||||
runtime.waiting_on = "开发者确认 Agent 工具动作".to_string();
|
||||
runtime.next_step =
|
||||
format!("确认或调整策略后继续 Agent 工具动作:{}", observation.tool);
|
||||
complete_agent_runtime_active_plan_step(
|
||||
&mut runtime,
|
||||
"waiting-for-confirmation",
|
||||
&observation_summary,
|
||||
);
|
||||
} else {
|
||||
complete_agent_runtime_active_plan_step(
|
||||
&mut runtime,
|
||||
if observation.status == "ok" {
|
||||
"completed"
|
||||
} else {
|
||||
"failed"
|
||||
},
|
||||
&observation_summary,
|
||||
);
|
||||
runtime.waiting_on = "Agent 根据工具观察修正计划".to_string();
|
||||
runtime.next_step = "把工具观察交给 Agent 修正计划".to_string();
|
||||
}
|
||||
runtime.updated_at = unix_timestamp();
|
||||
let _ = append_game_creator_agent_runtime_task(&root, &runtime);
|
||||
let _ = refresh_game_creator_agent_runtime_task_queue(&root, &mut runtime);
|
||||
let _ = write_game_creator_agent_runtime_state(&root, &runtime);
|
||||
let _ = append_game_creator_agent_runtime_event(
|
||||
&root,
|
||||
&runtime,
|
||||
"observation",
|
||||
runtime.status.as_str(),
|
||||
"observation",
|
||||
if observation.is_waiting_for_confirmation() {
|
||||
"waiting-for-confirmation"
|
||||
} else {
|
||||
"observation"
|
||||
},
|
||||
observation_summary.as_str(),
|
||||
observation.detail.as_deref(),
|
||||
);
|
||||
@@ -699,6 +732,20 @@ async fn run_game_creator_agent_background_task(
|
||||
"summary": observation.summary,
|
||||
}),
|
||||
);
|
||||
if observation.is_waiting_for_confirmation() {
|
||||
let _ = append_agent_db_record(
|
||||
&root,
|
||||
serde_json::json!({
|
||||
"recordType": "agent.runtime.tool_confirmation_required",
|
||||
"agentId": runtime.agent_id,
|
||||
"taskId": runtime.task_id,
|
||||
"runId": runtime.run_id,
|
||||
"tool": observation.tool,
|
||||
"summary": observation.summary,
|
||||
}),
|
||||
);
|
||||
return AgentBackgroundTaskOutcome::WaitingForConfirmation;
|
||||
}
|
||||
observations.push(observation);
|
||||
}
|
||||
}
|
||||
@@ -721,7 +768,7 @@ async fn run_game_creator_agent_background_task(
|
||||
Err(error) => {
|
||||
let fallback = default_game_creator_agent_runtime_state(&agent_id, "");
|
||||
let _ = fail_game_creator_agent_runtime_turn_at(&root, fallback, &error);
|
||||
return;
|
||||
return AgentBackgroundTaskOutcome::Finished;
|
||||
}
|
||||
};
|
||||
match request_game_creator_agent_background_final_reply_at(
|
||||
@@ -761,7 +808,7 @@ async fn run_game_creator_agent_background_task(
|
||||
}),
|
||||
);
|
||||
}
|
||||
return;
|
||||
return AgentBackgroundTaskOutcome::Finished;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -804,7 +851,7 @@ async fn run_game_creator_agent_background_task(
|
||||
}),
|
||||
);
|
||||
}
|
||||
return;
|
||||
return AgentBackgroundTaskOutcome::Finished;
|
||||
}
|
||||
}
|
||||
let _ = append_local_conversation_message_at(
|
||||
@@ -828,6 +875,7 @@ async fn run_game_creator_agent_background_task(
|
||||
"responsePreview": runtime.last_response,
|
||||
}),
|
||||
);
|
||||
AgentBackgroundTaskOutcome::Finished
|
||||
}
|
||||
|
||||
const AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT: usize = 3;
|
||||
@@ -838,6 +886,12 @@ const AGENT_RUNTIME_TOOL_OBSERVATION_MAX_CHARS: usize = 900;
|
||||
const AGENT_RUNTIME_TOOL_WRITE_MAX_CHARS: usize = 12_000;
|
||||
pub(crate) const AGENT_RUNTIME_LOCK_STALE_AFTER_SECONDS: u64 = 300;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum AgentBackgroundTaskOutcome {
|
||||
Finished,
|
||||
WaitingForConfirmation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AgentRuntimeToolPlan {
|
||||
@@ -875,6 +929,16 @@ impl AgentRuntimeToolObservation {
|
||||
fn summary(&self) -> String {
|
||||
format!("{}:{} · {}", self.tool, self.status, self.summary)
|
||||
}
|
||||
|
||||
fn is_waiting_for_confirmation(&self) -> bool {
|
||||
self.status == "waiting-for-confirmation"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum AgentRuntimeToolPolicyBlock {
|
||||
Denied(String),
|
||||
RequiresConfirmation(String),
|
||||
}
|
||||
|
||||
fn append_agent_runtime_tool_call_record(
|
||||
@@ -964,10 +1028,10 @@ fn complete_agent_runtime_active_plan_step(
|
||||
let Some(active_index) = runtime.active_plan_step_index else {
|
||||
return;
|
||||
};
|
||||
let status = if status == "failed" {
|
||||
"failed"
|
||||
} else {
|
||||
"completed"
|
||||
let status = match status {
|
||||
"failed" => "failed",
|
||||
"waiting-for-confirmation" => "waiting-for-confirmation",
|
||||
_ => "completed",
|
||||
};
|
||||
let now = unix_timestamp();
|
||||
for step in runtime.plan_steps.iter_mut() {
|
||||
@@ -1200,10 +1264,16 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action(
|
||||
if let Some(blocked) =
|
||||
game_creator_agent_runtime_tool_policy_block(root, agent_id, command_id)
|
||||
{
|
||||
let (status, summary) = match blocked {
|
||||
AgentRuntimeToolPolicyBlock::Denied(summary) => ("blocked", summary),
|
||||
AgentRuntimeToolPolicyBlock::RequiresConfirmation(summary) => {
|
||||
("waiting-for-confirmation", summary)
|
||||
}
|
||||
};
|
||||
return AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: blocked,
|
||||
status: status.to_string(),
|
||||
summary,
|
||||
detail: None,
|
||||
};
|
||||
}
|
||||
@@ -1378,14 +1448,14 @@ fn game_creator_agent_runtime_tool_policy_block(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
command_id: &str,
|
||||
) -> Option<String> {
|
||||
) -> Option<AgentRuntimeToolPolicyBlock> {
|
||||
let view = match read_project_permission_policy_at(root) {
|
||||
Ok(view) => view,
|
||||
Err(error) => return Some(error),
|
||||
Err(error) => return Some(AgentRuntimeToolPolicyBlock::Denied(error)),
|
||||
};
|
||||
let agent_id = match normalize_game_creator_runtime_agent_id(agent_id) {
|
||||
Ok(agent_id) => agent_id,
|
||||
Err(error) => return Some(error),
|
||||
Err(error) => return Some(AgentRuntimeToolPolicyBlock::Denied(error)),
|
||||
};
|
||||
if view
|
||||
.policy
|
||||
@@ -1393,7 +1463,9 @@ fn game_creator_agent_runtime_tool_policy_block(
|
||||
.iter()
|
||||
.any(|command| command == command_id)
|
||||
{
|
||||
return Some(format!("项目权限策略拒绝执行:{command_id}"));
|
||||
return Some(AgentRuntimeToolPolicyBlock::Denied(format!(
|
||||
"项目权限策略拒绝执行:{command_id}"
|
||||
)));
|
||||
}
|
||||
if view
|
||||
.policy
|
||||
@@ -1407,7 +1479,9 @@ fn game_creator_agent_runtime_tool_policy_block(
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Some(format!("Agent 权限策略拒绝执行:{agent_id} / {command_id}"));
|
||||
return Some(AgentRuntimeToolPolicyBlock::Denied(format!(
|
||||
"Agent 权限策略拒绝执行:{agent_id} / {command_id}"
|
||||
)));
|
||||
}
|
||||
if view
|
||||
.policy
|
||||
@@ -1415,7 +1489,9 @@ fn game_creator_agent_runtime_tool_policy_block(
|
||||
.iter()
|
||||
.any(|command| command == command_id)
|
||||
{
|
||||
return Some(format!("项目权限策略要求用户确认:{command_id}"));
|
||||
return Some(AgentRuntimeToolPolicyBlock::RequiresConfirmation(format!(
|
||||
"项目权限策略要求用户确认:{command_id}"
|
||||
)));
|
||||
}
|
||||
if view
|
||||
.policy
|
||||
@@ -1429,9 +1505,9 @@ fn game_creator_agent_runtime_tool_policy_block(
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Some(format!(
|
||||
return Some(AgentRuntimeToolPolicyBlock::RequiresConfirmation(format!(
|
||||
"Agent 权限策略要求用户确认:{agent_id} / {command_id}"
|
||||
));
|
||||
)));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -2568,10 +2644,11 @@ fn format_agent_runtime_active_plan_step_observation(state: &AgentRuntimeState)
|
||||
|
||||
fn format_agent_runtime_task_queue_observation(queue: &AgentRuntimeTaskQueueSummary) -> String {
|
||||
format!(
|
||||
"total={} pending={} running={} completed={} failed={} latest={}",
|
||||
"total={} pending={} running={} waiting={} completed={} failed={} latest={}",
|
||||
queue.total,
|
||||
queue.pending,
|
||||
queue.running,
|
||||
queue.waiting_for_confirmation,
|
||||
queue.completed,
|
||||
queue.failed,
|
||||
queue.latest_run_id.as_deref().unwrap_or("-")
|
||||
@@ -3069,6 +3146,7 @@ fn agent_runtime_next_step_for_phase(phase: &str) -> &'static str {
|
||||
match phase.trim() {
|
||||
"planning" => "等待 Agent 输出计划或回复",
|
||||
"action" => "等待工具观察结果",
|
||||
"waiting-for-confirmation" => "等待开发者确认工具动作",
|
||||
"response" => "等待 Agent 整理最终回复",
|
||||
"completed" | "idle" => "等待下一轮输入",
|
||||
"failed" => "等待开发者处理失败",
|
||||
@@ -3081,6 +3159,7 @@ fn agent_runtime_waiting_on_for_phase(phase: &str) -> &'static str {
|
||||
"planning" => "Agent 输出计划或回复",
|
||||
"llm" => "Agent LLM 回复",
|
||||
"action" => "工具观察结果",
|
||||
"waiting-for-confirmation" => "开发者确认 Agent 工具动作",
|
||||
"response" => "Agent 整理最终回复",
|
||||
"completed" | "idle" => "开发者下一轮输入",
|
||||
"failed" => "开发者处理失败",
|
||||
@@ -3616,6 +3695,7 @@ fn summarize_game_creator_agent_runtime_task_queue(
|
||||
match record.status.as_str() {
|
||||
"pending" => summary.pending += 1,
|
||||
"running" => summary.running += 1,
|
||||
"waiting-for-confirmation" => summary.waiting_for_confirmation += 1,
|
||||
"completed" => summary.completed += 1,
|
||||
"failed" => summary.failed += 1,
|
||||
_ => {}
|
||||
|
||||
@@ -260,6 +260,8 @@ struct AgentRuntimeTaskQueueSummary {
|
||||
#[serde(default)]
|
||||
running: u32,
|
||||
#[serde(default)]
|
||||
waiting_for_confirmation: u32,
|
||||
#[serde(default)]
|
||||
completed: u32,
|
||||
#[serde(default)]
|
||||
failed: u32,
|
||||
@@ -275,6 +277,7 @@ impl Default for AgentRuntimeTaskQueueSummary {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
running: 0,
|
||||
waiting_for_confirmation: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latest_run_id: None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -288,6 +288,7 @@ interface AgentRuntimeTaskQueueSummary {
|
||||
total: number;
|
||||
pending: number;
|
||||
running: number;
|
||||
waitingForConfirmation?: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
latestRunId: string | null;
|
||||
@@ -621,6 +622,7 @@ function normalizeAgentRuntimeState(
|
||||
total: 0,
|
||||
pending: 0,
|
||||
running: 0,
|
||||
waitingForConfirmation: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: null,
|
||||
@@ -654,6 +656,8 @@ function agentRuntimeWaitingOnFromPhase(phase: string) {
|
||||
return 'Agent LLM 回复';
|
||||
case 'action':
|
||||
return '工具观察结果';
|
||||
case 'waiting-for-confirmation':
|
||||
return '开发者确认 Agent 工具动作';
|
||||
case 'response':
|
||||
return 'Agent 整理最终回复';
|
||||
case 'completed':
|
||||
@@ -672,6 +676,8 @@ function agentRuntimeNextStepFromPhase(phase: string) {
|
||||
return '等待 Agent 输出计划或回复';
|
||||
case 'action':
|
||||
return '等待工具观察结果';
|
||||
case 'waiting-for-confirmation':
|
||||
return '等待开发者确认工具动作';
|
||||
case 'response':
|
||||
return '等待 Agent 整理最终回复';
|
||||
case 'completed':
|
||||
@@ -708,6 +714,7 @@ function formatAgentRuntimeTaskQueue(
|
||||
const parts = [
|
||||
`pending ${queue.pending}`,
|
||||
`running ${queue.running}`,
|
||||
`waiting ${queue.waitingForConfirmation ?? 0}`,
|
||||
`completed ${queue.completed}`,
|
||||
`failed ${queue.failed}`,
|
||||
`total ${queue.total}`,
|
||||
@@ -10467,6 +10474,8 @@ function sameAgentRuntimeTaskQueue(
|
||||
left.total === right.total &&
|
||||
left.pending === right.pending &&
|
||||
left.running === right.running &&
|
||||
(left.waitingForConfirmation ?? 0) ===
|
||||
(right.waitingForConfirmation ?? 0) &&
|
||||
left.completed === right.completed &&
|
||||
left.failed === right.failed &&
|
||||
left.latestRunId === right.latestRunId &&
|
||||
|
||||
@@ -1662,7 +1662,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
expect(screen.getByText('Loop:1/3 · 工具预算 3')).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/任务队列:pending 0 · running 1 · completed 0 · failed 0 · total 1 · latest launcher-agent-task-/,
|
||||
/任务队列:pending 0 · running 1 · waiting 0 · completed 0 · failed 0 · total 1 · latest launcher-agent-task-/,
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText('计划进度')).not.toBeNull();
|
||||
@@ -1921,7 +1921,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/任务队列:pending 1 · running 1 · completed 0 · failed 0 · total 2 · latest launcher-agent-task-/,
|
||||
/任务队列:pending 1 · running 1 · waiting 0 · completed 0 · failed 0 · total 2 · latest launcher-agent-task-/,
|
||||
),
|
||||
).not.toBeNull();
|
||||
});
|
||||
@@ -13648,7 +13648,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'当前计划步骤:#1 active · 读取项目上下文 · 正在整理目标和约束',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'任务队列:pending 1 · running 1 · completed 0 · failed 0 · total 2 · latest runtime-design-director-1',
|
||||
'任务队列:pending 1 · running 1 · waiting 0 · completed 0 · failed 0 · total 2 · latest runtime-design-director-1',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'最近任务:pending / queued · 排队补齐世界观拆解',
|
||||
@@ -13694,7 +13694,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'Runtime:idle / completed · Loop 2/3 · 等待下一轮输入 · 等待 开发者下一轮输入 · 下一步 等待下一轮输入 · run runtime-design-director-1',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'任务队列:pending 1 · running 0 · completed 1 · failed 0 · total 2 · latest runtime-design-director-1',
|
||||
'任务队列:pending 1 · running 0 · waiting 0 · completed 1 · failed 0 · total 2 · latest runtime-design-director-1',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'最近任务:completed / completed · 排队补齐世界观拆解',
|
||||
@@ -18338,6 +18338,13 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
expect(
|
||||
await screen.findByText('已设置本地项目:/tmp/authorized-game'),
|
||||
).not.toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command]) => command === 'read_project_permission_policy',
|
||||
).length,
|
||||
).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
invoke.mockClear();
|
||||
|
||||
submitChat('/policy-confirm file.write');
|
||||
|
||||
@@ -4061,6 +4061,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 角色 brief;carry-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/policy.json` 支持 `agentPolicies`,用规范 Agent id 保存单个 Agent 的 `deniedCommands / confirmCommands`。Runtime 计算有效工具策略时把项目级策略和 Agent 级策略叠加,项目级策略继续对所有 Agent 生效,Agent 级策略只能进一步拒绝或要求确认,不能放宽项目级策略;拒绝优先于确认。主聊天新增 `/agent-policy-deny Agent 命令`、`/agent-policy-allow Agent 命令`、`/agent-policy-confirm Agent 命令` 和 `/agent-policy-auto Agent 命令`,继续通过 `project.policy_write` 确认卡写入策略。
|
||||
- 2026-07-10 调整:后台 Agent 工具命中确认策略时不再当作 `blocked` observation 继续收尾,而是把当前 Runtime 写成 `status/phase = waiting-for-confirmation`,`waitingOn` 固定为等待开发者确认工具动作,`recentToolCalls`、事件流、任务记录和 `taskQueue.waitingForConfirmation` 都保留该事实;同一 Agent 的后台 drain 暂停,不继续消费后续 pending 任务。命中拒绝策略仍使用 `blocked` observation 交回 Agent 修正计划。
|
||||
- 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。
|
||||
|
||||
@@ -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 按白名单和项目权限策略执行工具动作,写入 `thinking_summary / plan / action / observation / response / error` 事件,再把 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 对话弹窗的 Runtime 状态面板展示最近事件、最近任务、当前目标、当前任务、当前动作、等待对象、下一步和运行阶段,主窗口 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/policy.json` 新增 `agentPolicies`,可按规范 Agent id 分别配置 `deniedCommands / confirmCommands`。有效策略为“项目级策略 + Agent 级策略”的保守叠加:项目级拒绝 / 确认仍对所有 Agent 生效,Agent 级策略只能进一步限制该 Agent,不能放宽项目级策略,拒绝优先于确认。主聊天新增 `/agent-policy-deny Agent 命令`、`/agent-policy-allow Agent 命令`、`/agent-policy-confirm Agent 命令` 和 `/agent-policy-auto Agent 命令`,写入前仍走 `project.policy_write` 确认卡。
|
||||
- 2026-07-10 补充:后台 Agent 工具命中 `confirmCommands` 时不再继续整理最终回复,而是把本轮 Runtime 停在 `status/phase = waiting-for-confirmation`,`waitingOn` 指向“开发者确认 Agent 工具动作”,`recentToolCalls`、事件流、任务记录和 `taskQueue.waitingForConfirmation` 都保留该待确认事实;同一 Agent 的 drain 会暂停,不继续消费后续 pending 任务。命中拒绝策略仍作为 `blocked` observation 交回 Agent 继续修正计划。
|
||||
- 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 的执行上限或工具权限。
|
||||
@@ -273,7 +274,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 状态查看进度和结果。后台任务会向 `.agent/runtime/tasks/<agentId>.jsonl` 追加任务视角记录,任务状态使用 `pending / running / completed / failed`,读取时按 `runId` 去重返回最近任务;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/<agentId>.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行。客户端重开项目时会对当前项目路径自动尝试一次 Runtime 恢复;恢复命令必须通过 `agent.resume` 自动权限,默认确认策略下不会静默启动;同一 Agent 同时存在上一进程遗留 `running` 和 `pending` 时,先重接 `running`,再由 drain 继续 `pending`。后台任务的核心 loop 最多 3 轮:每轮把已有 observation 带回 LLM 让 Agent 重新规划,只有 actions 为空且 response 非空时提前收束,否则继续执行白名单工具,跑满后再进入最终回复整理。后台任务完成后会把 assistant 回复追加到对应 `.agent/conversations/agents/<agentId>.jsonl`,并向 `.agent/agent.db` 写入 `agent.runtime.background_task.queued` / `agent.runtime.background_task` / `agent.runtime.background_task.recovered` / `agent.runtime.tool_observation` / `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.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`;若项目策略要求确认或拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;`.agent/agent.db` 追加写入按整行 JSONL 写入,减少多个 Agent 同时完成时的行交错风险。
|
||||
- 开发窗口和项目内 Agent 对话弹窗的“后台运行”只启动或排队单 Agent 后台任务,不阻塞等待回复;用户可刷新同一 Agent 对话或 runtime 状态查看进度和结果。后台任务会向 `.agent/runtime/tasks/<agentId>.jsonl` 追加任务视角记录,任务状态使用 `pending / running / waiting-for-confirmation / completed / failed`,读取时按 `runId` 去重返回最近任务;runtime state 自身仍可在完成后显示 `idle / completed`,二者语义分开。同一 Agent 的 pending 任务由持有 `.agent/runtime/locks/<agentId>.lock` 的后台 drain 串行执行,避免同一 Agent 并发抢上下文;不同 Agent 仍可并行;若某个工具动作命中确认策略,该 Agent 会停在 `waiting-for-confirmation` 并暂停继续消费队列,等待后续确认或策略调整。客户端重开项目时会对当前项目路径自动尝试一次 Runtime 恢复;恢复命令必须通过 `agent.resume` 自动权限,默认确认策略下不会静默启动;同一 Agent 同时存在上一进程遗留 `running` 和 `pending` 时,先重接 `running`,再由 drain 继续 `pending`。后台任务的核心 loop 最多 3 轮:每轮把已有 observation 带回 LLM 让 Agent 重新规划,只有 actions 为空且 response 非空时提前收束,否则继续执行白名单工具,跑满后再进入最终回复整理。后台任务完成后会把 assistant 回复追加到对应 `.agent/conversations/agents/<agentId>.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.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`;若项目策略拒绝,对应工具不会执行,Runtime 会把策略结果作为 observation 回给 Agent 修正计划;若项目策略要求确认,Runtime 会保留待确认状态而不执行该工具。`toolPolicy` 保存当前工具级权限快照,供 planning prompt 和状态面板展示;`recentToolCalls` 保存最近 20 条结构化工具动作,供状态面板展示最近动作;`.agent/agent.db` 追加写入按整行 JSONL 写入,减少多个 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` 项目权限策略保护。
|
||||
|
||||
Reference in New Issue
Block a user