Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/input.rs
T
lhk229 ded3253b08 删除已退役策划 V1 的 Runtime 模块与残留分支
按四不写原则移除 PR #159 引入、已被策划 V2 取代的整条 V1 链路
删除 runtime_protocol 下六个 V1 模块(planning_storage/submit/approval/coordinator/hydrate/provider_usage)
V1 与 V2 共用的 GDD 数据模型抽到新模块 planning_gdd_model.rs 供 V2 继续复用
删除 prompt manifest 中 planning Agent 目录、role overlay、plan sections 与 supervisorPlan 组合及对应生成常量
删除四个 V1 提示词文件(roles/project-planning.md、plan/common.md、plan/supervisor-identity.md、plan/supervisor-playbook.md)
删除 game-creator.config.json 与配置代码中的 planning 能力开关
删除 CLI --swarm-chat 的 --plan 入口与 swarm_cli 中的 plan source 分支
删除 provider_retry 的 planning session binding 与 plan 专用请求指纹链路
删除 provider_action_batch 的 plan.submit_gdd v4 批次形状校验与 planning 绑定字段
删除 tool_policy_snapshot / agent_native_tools / tool_plan_protocol 中的 plan 根阶段收窄与 plan.submit_gdd 身份门
删除 main_loop 的 plan_gdd blocker 投影、plan 信封修复回路与 plan submit 业务拒绝限流
删除 pending_recovery 与 recovery_scan 的 plan submit 锚点恢复、planning session 投影恢复与审批投影恢复
删除 acceptance_graph 的 Fast GDD 取证覆盖校验与 plan 根验收前置门
删除 agent_db 的 plan.provider_usage、plan.gdd_decided、plan_submit_gdd.committed 三条专用持久车道及其预留配额
删除 AgentRuntimeState 的 plan_submit_gdd_rejection_count 字段
runtime_tools 的委派、run_status、goal_contract 恢复为通用路径(移除 plan 根对称性守卫与 acceptance gate 钩子)
同步删除只覆盖 V1 行为的测试用例(planning 澄清、锚点恢复、plan 根委派门、plan 提示词组合等)
2026-09-08 09:44:21 +00:00

391 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::*;
#[derive(Debug, Eq, PartialEq)]
pub(super) enum SwarmChatInput {
Help,
Agents,
Status,
History,
Compact,
Goal(SwarmGoalCommand),
InvalidGoal(String),
Resume,
Quit,
Message(String),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum SwarmChatFlow {
Continue,
Exit,
}
pub(super) enum SwarmInputEvent {
Line(String),
Eof,
Error(String),
}
pub(super) enum SwarmPromptDecision {
Approve,
Reject,
Deferred,
InputClosed,
Quit,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum SwarmNewRunLaunch<'a> {
ProjectSupervisor {
source: &'static str,
run_profile: &'a str,
},
ExplicitParentDebug,
}
impl SwarmNewRunLaunch<'_> {
pub(super) fn expected_parent_source(self) -> Option<&'static str> {
match self {
Self::ProjectSupervisor { .. } | Self::ExplicitParentDebug => None,
}
}
}
pub(super) fn resolve_swarm_new_run_launch<'a>(
parent_agent_id: &str,
run_profile: &'a str,
supervisor_source: &'static str,
) -> Result<SwarmNewRunLaunch<'a>, String> {
if !matches!(
run_profile,
AGENT_RUNTIME_RUN_PROFILE_STANDARD | AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
) {
return Err(format!("不支持的 Agent Runtime Run Profile:{run_profile}"));
}
if parent_agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
if supervisor_source != AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE {
return Err(format!(
"不支持的 Project Supervisor source:{supervisor_source}"
));
}
return Ok(SwarmNewRunLaunch::ProjectSupervisor {
source: supervisor_source,
run_profile,
});
}
if supervisor_source != AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE {
return Err(format!(
"受限 Supervisor source 仅支持 project-supervisor 总控入口:{supervisor_source}"
));
}
if run_profile != AGENT_RUNTIME_RUN_PROFILE_STANDARD {
return Err("--autonomous-game-build 仅支持 project-supervisor 总控入口".to_string());
}
Ok(SwarmNewRunLaunch::ExplicitParentDebug)
}
pub(super) fn run_game_creator_swarm_chat_with_input<W: Write>(
root: &Path,
parent_agent_id: &str,
run_profile: &str,
supervisor_source: &'static str,
input: &Receiver<SwarmInputEvent>,
output: &mut W,
) -> Result<(), String> {
let parent_agent_id = parent_agent_id.trim();
if parent_agent_id.is_empty() {
return Err("parentAgentId 不能为空".to_string());
}
let new_run_launch =
resolve_swarm_new_run_launch(parent_agent_id, run_profile, supervisor_source)?;
let expected_parent_source = new_run_launch.expected_parent_source();
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
enforce_project_permission_policy(root, "agent.run_status")?;
enforce_project_permission_policy(root, "agent.compact")?;
enforce_project_permission_policy(root, "agent.resume")?;
let _ = read_local_conversation_for_session_at(root, Some(parent_agent_id), None)?;
let existing_runtimes = read_game_creator_agent_runtimes_at(root)?;
writeln!(output, "Agent Swarm Chat")
.and_then(|_| writeln!(output, "项目:{}", root.display()))
.and_then(|_| writeln!(output, "父 Agent:{parent_agent_id}"))
.and_then(|_| writeln!(output, "输入 /help 查看命令。"))
.map_err(|error| format!("写入终端失败:{error}"))?;
print_conversation_history(root, parent_agent_id, output)?;
let active_conversation =
read_local_conversation_for_session_at(root, Some(parent_agent_id), None)?;
let active_session_id = active_conversation
.session_id
.as_deref()
.ok_or_else(|| "父 Agent 当前 Session 缺失".to_string())?;
let matching_parent_is_busy = swarm_parent_runtime(
parent_agent_id,
active_session_id,
run_profile,
expected_parent_source,
&existing_runtimes,
)
.is_some_and(runtime_is_busy);
if matching_parent_is_busy {
writeln!(
output,
"[恢复扫描] 检测到未收束 Runtime;输入 /resume 继续观察,新消息会进入该 run 的 steer 队列。"
)
.map_err(|error| format!("写入终端失败:{error}"))?;
}
loop {
write!(output, "\n你> ").map_err(|error| format!("写入终端失败:{error}"))?;
output
.flush()
.map_err(|error| format!("刷新终端失败:{error}"))?;
let Some(line) = receive_swarm_chat_line(input)? else {
writeln!(output, "\n已退出 Agent Swarm Chat。")
.map_err(|error| format!("写入终端失败:{error}"))?;
return Ok(());
};
let Some(command) = parse_swarm_chat_input(&line) else {
continue;
};
match command {
SwarmChatInput::Help => print_swarm_chat_help(output)?,
SwarmChatInput::Agents => print_swarm_agents(root, output)?,
SwarmChatInput::Status => print_swarm_status(root, output)?,
SwarmChatInput::History => print_conversation_history(root, parent_agent_id, output)?,
SwarmChatInput::Compact => {
handle_swarm_context_compaction(root, parent_agent_id, output)?
}
SwarmChatInput::Goal(command) => {
let mut observer = SwarmRuntimeObserver::seed(root)?;
let Some(observation) =
handle_swarm_goal_command(root, parent_agent_id, command, output)?
else {
continue;
};
let conversation_baseline = new_swarm_turn_conversation_baseline(
observation.previous_message_count,
&observation.run_id,
);
let outcome = wait_for_swarm_turn(
root,
parent_agent_id,
&observation.session_id,
run_profile,
expected_parent_source,
conversation_baseline,
input,
output,
&mut observer,
SWARM_CHAT_POLL_INTERVAL,
SWARM_CHAT_SETTLE_WINDOW,
)?;
if outcome == SwarmTurnOutcome::Quit {
return print_swarm_chat_exit(output);
}
print_turn_outcome(outcome, output)?;
}
SwarmChatInput::InvalidGoal(error) => print_swarm_goal_error(output, &error)?,
SwarmChatInput::Resume => {
let before =
read_local_conversation_for_session_at(root, Some(parent_agent_id), None)?;
let session_id = before
.session_id
.as_deref()
.ok_or_else(|| "父 Agent 当前 Session 缺失".to_string())?;
if handle_swarm_resume_turn(
root,
parent_agent_id,
session_id,
run_profile,
expected_parent_source,
before.messages.len(),
input,
output,
)? == SwarmChatFlow::Exit
{
return Ok(());
}
}
SwarmChatInput::Quit => {
writeln!(
output,
"已退出 Agent Swarm Chat;后台 Runner 和已投递任务保持运行。"
)
.map_err(|error| format!("写入终端失败:{error}"))?;
return Ok(());
}
SwarmChatInput::Message(message) => {
let before =
read_local_conversation_for_session_at(root, Some(parent_agent_id), None)?;
let session_id = before
.session_id
.as_deref()
.ok_or_else(|| "父 Agent 当前 Session 缺失".to_string())?;
if handle_swarm_user_turn(
root,
parent_agent_id,
session_id,
run_profile,
new_run_launch,
&message,
input,
output,
)? == SwarmChatFlow::Exit
{
return Ok(());
}
}
}
}
}
pub(super) fn receive_swarm_chat_line(
input: &Receiver<SwarmInputEvent>,
) -> Result<Option<String>, String> {
match input.recv() {
Ok(SwarmInputEvent::Line(line)) => Ok(Some(line)),
Ok(SwarmInputEvent::Eof) | Err(_) => Ok(None),
Ok(SwarmInputEvent::Error(error)) => Err(format!("读取终端输入失败:{error}")),
}
}
pub(super) fn prompt_swarm_decision<W: Write>(
root: &Path,
parent_agent_id: &str,
input: &Receiver<SwarmInputEvent>,
output: &mut W,
prompt: &str,
) -> Result<SwarmPromptDecision, String> {
write!(output, "{prompt}").map_err(|error| format!("写入终端失败:{error}"))?;
output
.flush()
.map_err(|error| format!("刷新终端失败:{error}"))?;
loop {
let Some(line) = receive_swarm_chat_line(input)? else {
return Ok(SwarmPromptDecision::InputClosed);
};
match line.to_ascii_lowercase().as_str() {
"approve" | "yes" | "y" | "批准" => return Ok(SwarmPromptDecision::Approve),
"reject" | "no" | "n" | "拒绝" => return Ok(SwarmPromptDecision::Reject),
"/quit" | "/exit" => return Ok(SwarmPromptDecision::Quit),
_ => {
if let Some(command) = parse_swarm_chat_input(&line) {
match command {
SwarmChatInput::Goal(command) => {
let _ =
handle_swarm_goal_command(root, parent_agent_id, command, output)?;
return Ok(SwarmPromptDecision::Deferred);
}
SwarmChatInput::InvalidGoal(error) => {
print_swarm_goal_error(output, &error)?;
}
SwarmChatInput::Compact => {
handle_swarm_context_compaction(root, parent_agent_id, output)?;
return Ok(SwarmPromptDecision::Deferred);
}
_ => {}
}
}
write!(output, "请输入 approve 或 reject:")
.map_err(|error| format!("写入终端失败:{error}"))?;
output
.flush()
.map_err(|error| format!("刷新终端失败:{error}"))?;
}
}
}
}
pub(super) fn print_swarm_chat_exit<W: Write>(output: &mut W) -> Result<(), String> {
writeln!(
output,
"已退出 Agent Swarm Chat;后台 Runner 和已投递任务保持运行。"
)
.map_err(|error| format!("写入终端失败:{error}"))
}
pub(super) fn parse_swarm_chat_input(input: &str) -> Option<SwarmChatInput> {
let input = input.trim();
if input.is_empty() {
return None;
}
if let Some(rest) = input.strip_prefix("/goal") {
if rest.is_empty() {
return Some(SwarmChatInput::Goal(SwarmGoalCommand::Status));
}
if !rest.chars().next().is_some_and(char::is_whitespace) {
return Some(SwarmChatInput::InvalidGoal(
"未知 /goal 命令;输入 /help 查看支持的 Goal 命令。".to_string(),
));
}
return Some(parse_swarm_goal_command(rest.trim()));
}
Some(match input {
"/help" => SwarmChatInput::Help,
"/agents" => SwarmChatInput::Agents,
"/status" => SwarmChatInput::Status,
"/history" => SwarmChatInput::History,
"/compact" => SwarmChatInput::Compact,
"/resume" => SwarmChatInput::Resume,
"/quit" | "/exit" => SwarmChatInput::Quit,
value => SwarmChatInput::Message(value.to_string()),
})
}
pub(super) fn parse_swarm_goal_command(input: &str) -> SwarmChatInput {
if input.is_empty() || input == "status" {
return SwarmChatInput::Goal(SwarmGoalCommand::Status);
}
if input == "pause" {
return SwarmChatInput::Goal(SwarmGoalCommand::Pause);
}
if input == "resume" {
return SwarmChatInput::Goal(SwarmGoalCommand::Resume);
}
if input == "clear" {
return SwarmChatInput::Goal(SwarmGoalCommand::Clear);
}
if let Some(outcome) = input.strip_prefix("edit") {
if outcome.is_empty() {
return SwarmChatInput::InvalidGoal("用法:/goal edit <目标>".to_string());
}
if outcome.chars().next().is_some_and(char::is_whitespace) {
let outcome = outcome.trim();
return if outcome.is_empty() {
SwarmChatInput::InvalidGoal("用法:/goal edit <目标>".to_string())
} else {
SwarmChatInput::Goal(SwarmGoalCommand::Edit(outcome.to_string()))
};
}
}
for command in ["status", "pause", "resume", "clear"] {
if input
.strip_prefix(command)
.is_some_and(|rest| rest.chars().next().is_some_and(char::is_whitespace))
{
return SwarmChatInput::InvalidGoal(format!("/goal {command} 不接受额外参数"));
}
}
SwarmChatInput::Goal(SwarmGoalCommand::Start(input.to_string()))
}
pub(super) fn print_swarm_chat_help<W: Write>(output: &mut W) -> Result<(), String> {
writeln!(output, "/agents 查看静态 Agent 与动态 child")
.and_then(|_| writeln!(output, "/status 查看全部 Runtime 状态"))
.and_then(|_| writeln!(output, "/history 查看父 Agent 当前 Session 历史"))
.and_then(|_| writeln!(output, "/compact 压缩父 Agent 当前空闲 Session 历史"))
.and_then(|_| writeln!(output, "/resume 继续观察当前 Session 的未收束 Runtime"))
.and_then(|_| writeln!(output, "/goal <目标> 启动当前 Session 的持久 Goal"))
.and_then(|_| writeln!(output, "/goal 查看当前 Goal"))
.and_then(|_| writeln!(output, "/goal status 查看当前 Goal"))
.and_then(|_| writeln!(output, "/goal edit <目标> 编辑当前 Goal"))
.and_then(|_| writeln!(output, "/goal pause 暂停当前 Goal"))
.and_then(|_| writeln!(output, "/goal resume 恢复当前 Goal"))
.and_then(|_| writeln!(output, "/goal clear 清理当前 Goal"))
.and_then(|_| writeln!(output, "/help 查看命令"))
.and_then(|_| writeln!(output, "/quit 退出终端观察客户端"))
.map_err(|error| format!("写入终端失败:{error}"))
}