Merge branch 'master' into codex/admin-database-table-query
Project CI / Backend tests (pull_request) Successful in 8m18s
Project CI / Repository checks (pull_request) Successful in 3m53s
Project CI / Frontend tests (pull_request) Successful in 5m24s
Project CI / Native shell tests (pull_request) Successful in 17m15s

This commit is contained in:
2026-09-01 11:47:03 +08:00
7 changed files with 149 additions and 224 deletions
@@ -715,36 +715,6 @@ fn build_game_creator_agent_background_tool_plan_request_at(
))
}
pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
root: &Path,
agent_id: &str,
session_id: &str,
run_id: &str,
task: &str,
observations: &[AgentRuntimeToolObservation],
loop_index: usize,
) -> Result<
(
GameCreatorLlmConfig,
String,
LlmRunRequest,
String,
AgentRuntimeToolPlanRequestSnapshot,
),
String,
> {
build_game_creator_agent_background_tool_plan_request_at(
root,
None,
agent_id,
session_id,
run_id,
task,
observations,
loop_index,
)
}
pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request_locked(
root: &Path,
project_lock: &ProjectWriteLock,
@@ -972,17 +942,20 @@ mod tests {
use crate::{update_manifest_task_status_at, GameCreationAppTaskStatus};
use super::{
acquire_game_creator_agent_provider_plan_project_write_lock_with_wait,
agent_runtime_root_source_at, bind_game_creator_agent_runtime_run_profile_at,
build_game_creator_agent_background_final_reply_request,
build_game_creator_agent_background_tool_plan_request,
build_game_creator_agent_background_tool_plan_request_locked,
game_creator_agent_context_preload_notice,
game_creator_agent_runtime_run_profile_binding_path,
game_creator_project_supervisor_chat_system_prompt, init_local_game_project_at,
new_game_creation_app_seed_tasks, provider_command_exec_contract,
provider_command_start_contract, render_relaxed_autonomous_manifest_ready_task_background_prompt,
provider_command_start_contract,
render_relaxed_autonomous_manifest_ready_task_background_prompt,
required_runtime_prompt_section, start_game_creator_agent_runtime_task_at,
AgentRuntimeGoalContractAcceptanceNodeDraft, AgentRuntimeGoalContractDraft,
AgentRuntimeTaskLink, AgentRuntimeToolObservation, AgentRuntimeToolPlan,
AgentRuntimeToolPlanRequestSnapshot, GameCreatorLlmConfig,
AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT,
AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL,
AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND,
@@ -996,6 +969,40 @@ mod tests {
RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION,
};
fn build_game_creator_agent_background_tool_plan_request_for_test(
root: &std::path::Path,
agent_id: &str,
session_id: &str,
run_id: &str,
task: &str,
observations: &[AgentRuntimeToolObservation],
loop_index: usize,
) -> Result<
(
GameCreatorLlmConfig,
String,
platform_llm::LlmRunRequest,
String,
AgentRuntimeToolPlanRequestSnapshot,
),
String,
> {
let lock = acquire_game_creator_agent_provider_plan_project_write_lock_with_wait(
root,
"test.provider_request.build.tool_plan",
)?;
build_game_creator_agent_background_tool_plan_request_locked(
root,
&lock,
agent_id,
session_id,
run_id,
task,
observations,
loop_index,
)
}
fn native_input_required_fields(
request: &platform_llm::LlmRunRequest,
tool: &str,
@@ -1066,7 +1073,7 @@ mod tests {
summary: "结构化计划更新被 Runtime 拒绝".to_string(),
detail: Some("计划状态回退".to_string()),
};
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request(
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test(
&root,
&state.agent_id,
&state.session_id,
@@ -1155,16 +1162,17 @@ mod tests {
// Relaxed orchestration does not convert an idle planning counter into
// a tool-removal gate; the Provider remains free to choose its next
// action.
let (_, _, baseline, _, _) = build_game_creator_agent_background_tool_plan_request(
&root,
&state.agent_id,
&state.session_id,
&state.run_id,
&state.current_task,
&[],
1,
)
.expect("build baseline request");
let (_, _, baseline, _, _) =
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
&state.agent_id,
&state.session_id,
&state.run_id,
&state.current_task,
&[],
1,
)
.expect("build baseline request");
assert!(baseline
.function_tools
.iter()
@@ -1175,7 +1183,7 @@ mod tests {
crate::agent::write_game_creator_agent_runtime_state(&root, &idle_state)
.expect("persist idle rounds");
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request(
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test(
&root,
&state.agent_id,
&state.session_id,
@@ -1256,7 +1264,7 @@ mod tests {
vec!["交付当前 manifest task".to_string()],
)
.expect("start autonomous ready child task");
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request(
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test(
&root,
agent_id,
&state.session_id,
@@ -1506,7 +1514,7 @@ mod tests {
vec!["冻结 Goal Contract".to_string()],
)
.expect("start trusted root");
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request(
let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test(
&root,
&state.agent_id,
&state.session_id,
@@ -1566,7 +1574,7 @@ mod tests {
)
.expect("start plan root");
let (_, _, first, _, _) = build_game_creator_agent_background_tool_plan_request(
let (_, _, first, _, _) = build_game_creator_agent_background_tool_plan_request_for_test(
&root,
&state.agent_id,
&state.session_id,
@@ -1633,7 +1641,7 @@ mod tests {
},
)
.expect("freeze plan contract");
let (_, _, later, _, _) = build_game_creator_agent_background_tool_plan_request(
let (_, _, later, _, _) = build_game_creator_agent_background_tool_plan_request_for_test(
&root,
&state.agent_id,
&state.session_id,
@@ -1719,7 +1727,7 @@ mod tests {
)
.expect("start supervisor runtime state");
let (_, _, supervisor_request, _, _) =
build_game_creator_agent_background_tool_plan_request(
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&supervisor_state.session_id,
@@ -1865,16 +1873,17 @@ mod tests {
vec!["核对普通说明".to_string()],
)
.expect("start ordinary runtime state");
let (_, _, ordinary_request, _, _) = build_game_creator_agent_background_tool_plan_request(
&root,
"code-prototype",
&ordinary_state.session_id,
&ordinary_state.run_id,
&ordinary_state.current_task,
&[],
0,
)
.expect("build ordinary planning request");
let (_, _, ordinary_request, _, _) =
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
"code-prototype",
&ordinary_state.session_id,
&ordinary_state.run_id,
&ordinary_state.current_task,
&[],
0,
)
.expect("build ordinary planning request");
assert!(ordinary_request.messages[0]
.content
.contains("你正在使用 Genarrative AI 游戏创作多智能体 Runtime"));
@@ -1989,16 +1998,17 @@ mod tests {
vec!["读取需求并准备澄清".to_string()],
)
.expect("start planning child");
let (_, _, planning_request, _, _) = build_game_creator_agent_background_tool_plan_request(
&root,
GAME_CREATOR_PROJECT_PLANNING_AGENT_ID,
&planning_state.session_id,
&planning_state.run_id,
&planning_state.current_task,
&[],
0,
)
.expect("build planning request");
let (_, _, planning_request, _, _) =
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
GAME_CREATOR_PROJECT_PLANNING_AGENT_ID,
&planning_state.session_id,
&planning_state.run_id,
&planning_state.current_task,
&[],
0,
)
.expect("build planning request");
let planning_system_prompt = &planning_request.messages[0].content;
let planning_brief_marker = "你是“立项策划 Agent”(`agentId=project-planning`";
assert!(planning_system_prompt.contains(planning_brief_marker));
@@ -2074,7 +2084,7 @@ mod tests {
)
.expect("start supervisor");
let (_, _, supervisor_request, _, _) =
build_game_creator_agent_background_tool_plan_request(
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&supervisor_state.session_id,
@@ -2183,7 +2193,7 @@ mod tests {
},
];
let (_, _, request, _, request_snapshot) =
build_game_creator_agent_background_tool_plan_request(
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&state.session_id,
@@ -2209,7 +2219,7 @@ mod tests {
),
});
let (_, _, _, _, settled_request_snapshot) =
build_game_creator_agent_background_tool_plan_request(
build_game_creator_agent_background_tool_plan_request_for_test(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&state.session_id,
@@ -216,23 +216,6 @@ pub(crate) fn pending_matches_receipt(
/// Construct the independent planning pending projection after an external
/// acceptance gate has succeeded. M1C-1 does not decide whether the gate
/// passed; the caller must supply that fact and the exact GDD identity.
pub(crate) fn create_plan_gdd_approval_pending_at(
root: &Path,
gdd: &PlanGddV1,
) -> Result<(), PlanningStorageError> {
if !crate::config::game_creator_planning_capability_enabled()
.map_err(|error| approval_error("PLAN_CAPABILITY_DISABLED", error))?
{
return Err(approval_error(
"PLAN_CAPABILITY_DISABLED",
"立项策划能力当前已停用",
));
}
let _lock = acquire_project_write_lock(root, "planning.approval-pending.create")
.map_err(|error| approval_error("PLAN_DURABILITY_FAILED", error))?;
create_plan_gdd_approval_pending_locked(root, gdd)
}
pub(crate) fn create_plan_gdd_approval_pending_locked(
root: &Path,
gdd: &PlanGddV1,
@@ -2357,13 +2340,6 @@ pub(crate) fn plan_gdd_typed_completion_blocker_at_locked(
),
));
}
if session.phase == "recovery_required" {
return Some(plan_gdd_completion_blocker(
"needs-reconciliation",
"planning session 仍处于 recovery_required,不能收束任务",
format!("gddVersion={}", latest.version),
));
}
None
}
@@ -2408,8 +2408,7 @@ pub(crate) fn validate_plan_session_successor(
&& next.active_run_id.is_some()
&& next.last_run_id == next.active_run_id.clone().unwrap_or_default();
if next.run_profile_binding_fingerprint != previous.run_profile_binding_fingerprint
&& (!active_run_changed
|| !matches!(next.phase.as_str(), "collecting" | "revision_requested"))
&& !active_run_changed
{
return Err(conflict(
"session 只有在绑定新的 active planning child 时才能更换 Run Profile binding fingerprint",
@@ -2712,18 +2711,6 @@ pub(crate) fn canonical_plan_submit_gdd_input_bytes(
Ok(bytes)
}
pub(crate) fn parse_plan_submit_gdd_input_bytes(
bytes: &[u8],
) -> Result<PlanSubmitGddInputV1, PlanningStorageError> {
let value = parse_strict_canonical::<PlanSubmitGddInputV1>(
bytes,
"plan.submit_gdd input",
PLAN_GDD_MAX_BYTES,
)?;
validate_plan_submit_gdd_input(&value)?;
Ok(value)
}
pub(crate) fn validate_plan_gdd_chain(values: &[PlanGddV1]) -> Result<(), PlanningStorageError> {
if values.len() > PLAN_MAX_VERSIONS as usize {
return Err(PlanningStorageError::new(
@@ -5067,22 +5054,23 @@ mod tests {
fn submit_input_has_strict_canonical_parser_and_runtime_field_boundary() {
let value = golden_submit_input();
let bytes = canonical_plan_submit_gdd_input_bytes(&value).expect("submit input bytes");
assert_eq!(
parse_plan_submit_gdd_input_bytes(&bytes).expect("parse input"),
value
);
let parse = |bytes: &[u8]| -> Result<PlanSubmitGddInputV1, PlanningStorageError> {
let value = parse_strict_canonical::<PlanSubmitGddInputV1>(
bytes,
"plan.submit_gdd input",
PLAN_GDD_MAX_BYTES,
)?;
validate_plan_submit_gdd_input(&value)?;
Ok(value)
};
assert_eq!(parse(&bytes).expect("parse input"), value);
let mut newline = bytes.clone();
newline.push(b'\n');
assert_eq!(
parse_plan_submit_gdd_input_bytes(&newline)
.unwrap_err()
.code(),
"PLAN_NON_CANONICAL_BYTES"
);
assert_eq!(parse(&newline).unwrap_err().code(), "PLAN_NON_CANONICAL_BYTES");
let mut object = serde_json::from_slice::<serde_json::Value>(&bytes).expect("input json");
object["projectId"] = serde_json::Value::String("forged-project".to_string());
let forged = serde_json::to_vec(&object).expect("forged input");
assert!(parse_plan_submit_gdd_input_bytes(&forged).is_err());
assert!(parse(&forged).is_err());
}
#[test]
@@ -1166,15 +1166,7 @@ fn validate_current_session_cas(
"plan.submit_gdd 必须绑定当前活跃策划子 Run",
));
}
// 这条 phase 判据实际只可能看到 `collecting`:上面的 activeRunId 判据要求
// session 绑着当前策划子 run,而 schema 不变量禁止 `awaiting_user_input`、
// `awaiting_gdd_approval`、`revision_requested`、`approved`、`rejected`、
// `recovery_required` 保留 activeRunIdplanning_storage.rs 的
// 「session 进入审批/终态/recovery_required 后不得保留 activeRunId」)。
// 因此 revise/reject 之后能不能重做,不由这条门决定,而由 M1C-2b 的 continuation
// 起点 writer 决定——它必须以新 activeRunId 写 revision+1 successorphase 只能落回
// `collecting`。这里保留 `revision_requested` 作为既有冗余,不再新增更多不可达分支。
if !matches!(session.phase.as_str(), "collecting" | "revision_requested") {
if session.phase != "collecting" {
return Err(submit_error(
"PLAN_PENDING_GDD_EXISTS",
"当前 planning session 仍有未决 GDD",
@@ -1641,11 +1633,7 @@ fn project_submit_successors_locked(
let source_session_matches_gdd = session_identity_matches_gdd
&& previous_session.session_revision == gdd.source_session_revision
&& previous_session.session_fingerprint == gdd.source_session_fingerprint
&& previous_session.active_run_id.as_deref() == Some(gdd.created_by_run_id.as_str())
&& matches!(
previous_session.phase.as_str(),
"collecting" | "revision_requested"
);
&& previous_session.active_run_id.as_deref() == Some(gdd.created_by_run_id.as_str());
if !session_identity_matches_gdd {
recovery_pending = true;
} else if same_ref {
@@ -1896,6 +1884,15 @@ mod tests {
use std::fs;
use std::path::PathBuf;
fn create_plan_gdd_approval_pending_for_test(
root: &std::path::Path,
gdd: &PlanGddV1,
) -> Result<(), PlanningStorageError> {
let _lock = acquire_project_write_lock(root, "test.planning.approval-pending.create")
.map_err(|error| PlanningStorageError::new("PLAN_DURABILITY_FAILED", error))?;
create_plan_gdd_approval_pending_locked(root, gdd)
}
fn valid_input() -> PlanSubmitGddInputV1 {
PlanSubmitGddInputV1 {
schema_version: PLAN_SUBMIT_INPUT_SCHEMA.to_string(),
@@ -3628,7 +3625,7 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
let decision_input = approval_input(
&gdd,
"approve",
@@ -3654,13 +3651,13 @@ mod tests {
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
let pending = read_plan_gdd_approval_pending_locked(&root)
.expect("read approval pending")
.expect("pending exists");
assert_eq!(pending.status, "awaiting_decision");
// Recreating the exact card is an idempotent replay.
create_plan_gdd_approval_pending_at(&root, &gdd).expect("replay approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("replay approval pending");
let mut forged_next = gdd.clone();
forged_next.version = 2;
@@ -3669,7 +3666,7 @@ mod tests {
"gdd-approval-00000000-0000-4000-8000-000000000003".to_string();
forged_next.action_fingerprint = "4".repeat(64);
forged_next.fingerprint = plan_gdd_fingerprint(&forged_next).expect("next fingerprint");
let stale = create_plan_gdd_approval_pending_at(&root, &forged_next)
let stale = create_plan_gdd_approval_pending_for_test(&root, &forged_next)
.expect_err("a non-latest GDD cannot receive an approval card");
assert_eq!(stale.code(), "PLAN_STALE_APPROVAL");
@@ -3684,7 +3681,7 @@ mod tests {
)
.expect("commit approval receipt");
assert_eq!(decision.outcome, "committed");
let after_receipt = create_plan_gdd_approval_pending_at(&root, &gdd)
let after_receipt = create_plan_gdd_approval_pending_for_test(&root, &gdd)
.expect_err("a receipt must close awaiting_decision recreation");
assert_eq!(after_receipt.code(), "PLAN_STALE_APPROVAL");
cleanup_fixture(root);
@@ -3698,7 +3695,7 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
let task_path =
game_creator_agent_runtime_task_path(&root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID);
@@ -3738,7 +3735,7 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
let _config_guard = crate::tests::write_test_local_config(
r#"{"planning":{"capabilityEnabled":false}}"#.to_string(),
);
@@ -3802,7 +3799,7 @@ mod tests {
assert_eq!(blocker.status, "needs-reconciliation");
assert!(blocker.summary.contains("审批 pending"));
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
let blocker = plan_gdd_completion_blocker_at_locked(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
@@ -3955,7 +3952,7 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
decide_plan_gdd_at(
&root,
&approval_input(
@@ -4089,7 +4086,7 @@ mod tests {
#[test]
fn a_revision_comment_reaches_the_supervisor_conversation_once() {
let (root, gdd, _root_runtime) = acceptance_gate_fixture(true);
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
decide_plan_gdd_at(
&root,
&approval_input(
@@ -4143,7 +4140,7 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
decide_plan_gdd_at(
&root,
&approval_input(
@@ -4230,7 +4227,8 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd)
.expect("create approval pending");
let first_input = approval_input(
&gdd,
action,
@@ -4461,7 +4459,7 @@ mod tests {
.expect("read submitted GDD")
.pop()
.expect("GDD exists");
create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending");
create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending");
let decision_input = approval_input(
&gdd,
"approve",
-14
View File
@@ -11037,20 +11037,6 @@ export function App({
projectSupervisorRuntime={projectSupervisorRuntime}
projectSupervisorRuntimeError={projectSupervisorRuntimeError}
projectSupervisorTransientReply={projectSupervisorTransientReply}
planGddState={planGddState}
planGddHydrateBusy={planGddHydrateBusy}
planGddDecisionBusy={planGddDecisionBusy}
planGddError={planGddError}
onPlanGddRefresh={() => void hydratePlanGddState()}
onPlanGddDecision={decidePlanGdd}
onMakeGameFromApprovedGdd={
onMakeGameFromApprovedGdd
? () =>
onMakeGameFromApprovedGdd(
localProject?.projectPath ?? projectPath,
)
: undefined
}
queueAgentRunControlFromPanel={queueAgentRunControlFromPanel}
queueOrExecuteProjectIndex={queueOrExecuteProjectIndex}
queuePendingCommand={queuePendingCommand}
@@ -25,8 +25,6 @@ import type {
MemoryScope,
PendingCommand,
PendingUiConfirmation,
PlanGddDecisionAction,
PlanGddStateViewV1,
} from '../../app/types';
import type { ProjectAgentResultSummary } from '../../view/project-development';
import {
@@ -47,13 +45,10 @@ import {
taskGroupLabels,
taskStatusLabels,
} from '../project-summary/projectSummary';
import { PlanGddSurface } from './GddApprovalCard';
import {
pendingCommandDetail,
pendingCommandTitle,
} from './pendingCommandPresentation';
import { isPlanningLaneRuntime } from './planningLane';
import { PlanningLaneRuntimeStrip } from './PlanningLaneRuntimeStrip';
import { resolvePendingCommandProjectPath } from './projectCommandPolicy';
type ProjectWorkspaceChatPaneProps = {
@@ -180,16 +175,6 @@ type ProjectWorkspaceChatPaneProps = {
projectSupervisorRuntime: AgentRuntimeState | null;
projectSupervisorRuntimeError: string;
projectSupervisorTransientReply: string;
planGddState: PlanGddStateViewV1 | null;
planGddHydrateBusy: boolean;
planGddDecisionBusy: boolean;
planGddError: string | null;
onPlanGddRefresh: () => void;
onPlanGddDecision: (
action: PlanGddDecisionAction,
comment: string | null,
) => Promise<void>;
onMakeGameFromApprovedGdd?: () => Promise<void>;
queueAgentRunControlFromPanel: (action: 'kill' | 'retry' | 'resume') => void;
queueOrExecuteProjectIndex: () => Promise<void>;
queuePendingCommand: (command: PendingCommand) => void;
@@ -273,13 +258,6 @@ export function ProjectWorkspaceChatPane({
projectSupervisorRuntime,
projectSupervisorRuntimeError,
projectSupervisorTransientReply,
planGddState,
planGddHydrateBusy,
planGddDecisionBusy,
planGddError,
onPlanGddRefresh,
onPlanGddDecision,
onMakeGameFromApprovedGdd,
queueAgentRunControlFromPanel,
queueOrExecuteProjectIndex,
queuePendingCommand,
@@ -369,17 +347,6 @@ export function ProjectWorkspaceChatPane({
</button>
</div>
</header>
<PlanGddSurface
state={planGddState}
active={isPlanningLaneRuntime(projectSupervisorRuntime)}
projectPath={projectPath}
hydrateBusy={planGddHydrateBusy}
decisionBusy={planGddDecisionBusy}
error={planGddError}
onRefresh={onPlanGddRefresh}
onDecision={onPlanGddDecision}
onMakeGame={onMakeGameFromApprovedGdd}
/>
<div className="chat-quick-actions" aria-label="预览快捷操作">
<button
type="button"
@@ -865,29 +832,18 @@ export function ProjectWorkspaceChatPane({
</p>
) : null}
</div>
{isPlanningLaneRuntime(projectSupervisorRuntime) ? (
<PlanningLaneRuntimeStrip
runtime={projectSupervisorRuntime}
error={projectSupervisorRuntimeError}
controlBusy={chatAgentBusy}
onSupervisorRetry={handleProjectSupervisorRetry}
onUserInput={handleProjectSupervisorUserInput}
/>
) : (
<ProjectSupervisorRuntimePanel
runtime={projectSupervisorRuntime}
error={projectSupervisorRuntimeError}
runtimeByAgentId={agentRuntimeById}
controlBusy={chatAgentBusy}
planGddAwaitingDecision={Boolean(planGddState?.pendingApproval)}
professionalResultsByAgentId={professionalAgentResultsById}
onToolAction={handleProjectSupervisorToolAction}
onSupervisorRetry={handleProjectSupervisorRetry}
onProfessionalToolAction={handleProjectProfessionalAgentToolAction}
onProfessionalRetry={handleProjectProfessionalAgentRetry}
onUserInput={handleProjectSupervisorUserInput}
/>
)}
<ProjectSupervisorRuntimePanel
runtime={projectSupervisorRuntime}
error={projectSupervisorRuntimeError}
runtimeByAgentId={agentRuntimeById}
controlBusy={chatAgentBusy}
professionalResultsByAgentId={professionalAgentResultsById}
onToolAction={handleProjectSupervisorToolAction}
onSupervisorRetry={handleProjectSupervisorRetry}
onProfessionalToolAction={handleProjectProfessionalAgentToolAction}
onProfessionalRetry={handleProjectProfessionalAgentRetry}
onUserInput={handleProjectSupervisorUserInput}
/>
{pendingCommand ? (
<div className="pending-command">
<span>
@@ -4,21 +4,34 @@ import { resolve } from 'node:path';
import { PlanGddStageProgress } from '../../src/features/project-workspace/GddApprovalCard';
import {
agentRuntimeUserInputRequest,
App,
createPlanGddStateView,
createProjectSupervisorRuntimeHarness,
expect,
fireEvent,
it,
openMainProject,
React,
render,
renderAppAt,
screen,
vi,
waitFor,
within,
} from './harness';
function mountFormalSupervisor(
harness: ReturnType<typeof createProjectSupervisorRuntimeHarness>,
) {
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
initialProjectPath: harness.projectPath,
orchestrationMode: 'single-supervisor',
planningStartMode: true,
projectSupervisorOnly: true,
}),
);
}
async function mountApprovalCard(
harness: ReturnType<typeof createProjectSupervisorRuntimeHarness>,
) {
@@ -26,8 +39,7 @@ async function mountApprovalCard(
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
renderAppAt('/');
await openMainProject(harness.projectPath);
mountFormalSupervisor(harness);
return await screen.findByLabelText('GDD 审批卡');
}
@@ -38,8 +50,7 @@ async function mountPlanningSurface(
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
renderAppAt('/');
await openMainProject(harness.projectPath);
mountFormalSupervisor(harness);
return await screen.findByLabelText('立项策划阶段进度');
}