明确泥点不足的游戏生成中断原因
统一余额不足分类与稳定原因码 阻止泥点不足进入重试和对账状态 在游戏聊天与阶段记录展示固定安全说明 补齐 API、Runtime 与前端回归测试 同步技术方案与项目决策记录
This commit is contained in:
@@ -55,9 +55,12 @@ pub(crate) use draft_validation::{
|
||||
pub(crate) use draft_writer::write_local_game_draft_at;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use loop_orchestration::{
|
||||
emit_agent_progress, game_creator_agent_llm_error_public_summary,
|
||||
request_game_creator_llm_text, request_generator_game_draft_with_client,
|
||||
request_planner_spec_with_client, run_game_creator_agent_loop_at, AgentProgressEmitter,
|
||||
emit_agent_progress, game_creator_agent_llm_error_is_mud_points_insufficient,
|
||||
game_creator_agent_llm_error_public_summary, game_creator_mud_points_insufficient_message,
|
||||
game_creator_runtime_error_is_mud_points_insufficient, request_game_creator_llm_text,
|
||||
request_generator_game_draft_with_client, request_planner_spec_with_client,
|
||||
run_game_creator_agent_loop_at, AgentProgressEmitter,
|
||||
GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use pass_artifacts::{
|
||||
|
||||
@@ -511,9 +511,34 @@ pub(in crate::agent) fn build_game_creator_agent_runtime_llm_client(
|
||||
build_game_creator_llm_client_without_redirects_from_llm_config(&single_attempt, config_path)
|
||||
}
|
||||
|
||||
pub(crate) const GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND: &str =
|
||||
"kind=mud-points-insufficient";
|
||||
|
||||
pub(crate) fn game_creator_mud_points_insufficient_message(message: &str) -> bool {
|
||||
let message = message.trim();
|
||||
message == "泥点余额不足" || message.starts_with("可消费泥点不足:")
|
||||
}
|
||||
|
||||
pub(crate) fn game_creator_runtime_error_is_mud_points_insufficient(error: &str) -> bool {
|
||||
error.contains("泥点余额不足") || error.contains("可消费泥点不足:")
|
||||
}
|
||||
|
||||
pub(crate) fn game_creator_agent_llm_error_is_mud_points_insufficient(
|
||||
error: &platform_llm::LlmError,
|
||||
) -> bool {
|
||||
matches!(
|
||||
error,
|
||||
platform_llm::LlmError::Upstream { message, .. }
|
||||
if game_creator_mud_points_insufficient_message(message)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn game_creator_agent_llm_error_public_summary(
|
||||
error: &platform_llm::LlmError,
|
||||
) -> String {
|
||||
if game_creator_agent_llm_error_is_mud_points_insufficient(error) {
|
||||
return GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND.to_string();
|
||||
}
|
||||
let (kind, http_status) = match error {
|
||||
platform_llm::LlmError::Timeout { .. } => ("timeout".to_string(), None),
|
||||
platform_llm::LlmError::Connectivity { .. } => ("connectivity".to_string(), None),
|
||||
|
||||
+61
-13
@@ -181,16 +181,32 @@ fn game_chat_fast_path_has_visual_asset(root: &Path, task_id: &str) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn game_chat_fast_path_canvas_generation_failed(runtime: &AgentRuntimeState) -> bool {
|
||||
runtime.observations.iter().rev().any(|observation| {
|
||||
[
|
||||
fn game_chat_fast_path_canvas_generation_error(
|
||||
runtime: &AgentRuntimeState,
|
||||
default_error: &str,
|
||||
) -> Option<String> {
|
||||
runtime.observations.iter().rev().find_map(|observation| {
|
||||
let failed = [
|
||||
"canvas.asset_generate:failed",
|
||||
"canvas.asset_generate:blocked",
|
||||
"canvas.asset_generate:rejected",
|
||||
"canvas.asset_generate:needs-reconciliation",
|
||||
]
|
||||
.iter()
|
||||
.any(|prefix| observation.starts_with(prefix))
|
||||
.any(|prefix| observation.starts_with(prefix));
|
||||
if !failed {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
if observation.contains(GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND)
|
||||
|| game_creator_runtime_error_is_mud_points_insufficient(observation)
|
||||
{
|
||||
GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND
|
||||
} else {
|
||||
default_error
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -479,11 +495,11 @@ pub(crate) fn game_chat_fast_path_plan_at(
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if game_chat_fast_path_canvas_generation_failed(runtime) {
|
||||
return Err(
|
||||
"game-chat 统一视觉规范图生成失败,拒绝跳过美术阶段或退回纯几何首版"
|
||||
.to_string(),
|
||||
);
|
||||
if let Some(error) = game_chat_fast_path_canvas_generation_error(
|
||||
runtime,
|
||||
"game-chat 统一视觉规范图生成失败,拒绝跳过美术阶段或退回纯几何首版",
|
||||
) {
|
||||
return Err(error);
|
||||
}
|
||||
let root_task = game_chat_fast_path_root_task(root, &budget)?;
|
||||
Ok(Some(game_chat_fast_path_canvas_asset_plan(
|
||||
@@ -516,10 +532,11 @@ pub(crate) fn game_chat_fast_path_plan_at(
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if game_chat_fast_path_canvas_generation_failed(runtime) {
|
||||
return Err(
|
||||
"game-chat 透明核心美术图集生成失败,拒绝退回纯代码核心画面".to_string()
|
||||
);
|
||||
if let Some(error) = game_chat_fast_path_canvas_generation_error(
|
||||
runtime,
|
||||
"game-chat 透明核心美术图集生成失败,拒绝退回纯代码核心画面",
|
||||
) {
|
||||
return Err(error);
|
||||
}
|
||||
let root_task = game_chat_fast_path_root_task(root, &budget)?;
|
||||
Ok(Some(game_chat_fast_path_canvas_asset_plan(
|
||||
@@ -1373,6 +1390,37 @@ mod tests {
|
||||
assert!(!prompt.contains("可直接作为首版主要背景"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canvas_generation_mud_point_failure_keeps_the_stable_reason() {
|
||||
let temporary = tempfile::tempdir().expect("temporary project");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "game-chat-mud-points", "mud points")
|
||||
.expect("initialize project");
|
||||
let mut runtime = start_game_creator_agent_runtime_task_at(
|
||||
&root,
|
||||
"art-asset-plan",
|
||||
"生成透明核心美术图集",
|
||||
"mud-points-art-run",
|
||||
"agent-ready-task-scheduler",
|
||||
"生成透明核心美术图集",
|
||||
Vec::new(),
|
||||
)
|
||||
.expect("start art runtime");
|
||||
runtime.observations.push(
|
||||
"canvas.asset_generate:failed · 平台图片生成任务失败:可消费泥点不足:需要 10,扣除退款占用后可用 2;operationId=private-operation-id"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let error = game_chat_fast_path_canvas_generation_error(
|
||||
&runtime,
|
||||
"game-chat 透明核心美术图集生成失败,拒绝退回纯代码核心画面",
|
||||
)
|
||||
.expect("mud point failure must stop the fast path");
|
||||
assert_eq!(error, GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND);
|
||||
assert!(!error.contains("operationId"));
|
||||
assert!(!error.contains("private-operation-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_html_uses_spritesheet_but_never_art_spec() {
|
||||
let temporary = tempfile::tempdir().expect("temporary project");
|
||||
|
||||
@@ -27,6 +27,9 @@ pub(in crate::agent) fn game_creator_agent_runtime_transient_provider_error_kind
|
||||
error: &platform_llm::LlmError,
|
||||
retry_autonomous_upstream_400: bool,
|
||||
) -> Option<&'static str> {
|
||||
if game_creator_agent_llm_error_is_mud_points_insufficient(error) {
|
||||
return None;
|
||||
}
|
||||
match error {
|
||||
platform_llm::LlmError::Timeout { .. } => Some("timeout"),
|
||||
platform_llm::LlmError::Connectivity { .. } => Some("connectivity"),
|
||||
@@ -181,6 +184,12 @@ pub(in crate::agent) fn game_creator_agent_runtime_failure_conversation_message(
|
||||
agent_id: &str,
|
||||
error: &str,
|
||||
) -> String {
|
||||
if error.contains(GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND)
|
||||
|| game_creator_runtime_error_is_mud_points_insufficient(error)
|
||||
{
|
||||
return "泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。"
|
||||
.to_string();
|
||||
}
|
||||
let subject = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||
"项目总控 Agent"
|
||||
} else {
|
||||
@@ -1559,6 +1568,58 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mud_point_insufficiency_interrupts_without_retry_and_uses_a_fixed_public_message() {
|
||||
for status_code in [409, 502] {
|
||||
let error = platform_llm::LlmError::Upstream {
|
||||
status_code,
|
||||
message: "泥点余额不足".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
game_creator_agent_runtime_transient_provider_error_kind(&error, false),
|
||||
None,
|
||||
"泥点余额不足不是可通过自动重试恢复的上游瞬态错误"
|
||||
);
|
||||
assert_eq!(
|
||||
game_creator_agent_llm_error_public_summary(&error),
|
||||
GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND
|
||||
);
|
||||
let encoded = game_creator_agent_runtime_provider_error_with_transient_kind(
|
||||
&error,
|
||||
"agentLlm.project-supervisor",
|
||||
"规划",
|
||||
false,
|
||||
);
|
||||
assert!(!encoded.starts_with(AGENT_RUNTIME_PROVIDER_TRANSIENT_ERROR_PREFIX));
|
||||
assert!(encoded.ends_with(GAME_CREATOR_MUD_POINTS_INSUFFICIENT_ERROR_KIND));
|
||||
assert_eq!(
|
||||
game_creator_agent_runtime_failure_conversation_message(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&encoded,
|
||||
),
|
||||
"泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。"
|
||||
);
|
||||
}
|
||||
|
||||
let provider_quota = platform_llm::LlmError::Upstream {
|
||||
status_code: 429,
|
||||
message: "insufficient_quota".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
game_creator_agent_runtime_transient_provider_error_kind(&provider_quota, false),
|
||||
Some("upstream-429"),
|
||||
"第三方 Provider quota 不能误分类为平台泥点不足"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
game_creator_agent_runtime_failure_conversation_message(
|
||||
"art-asset-plan",
|
||||
"canvas.asset_generate:failed · 平台图片生成任务失败:可消费泥点不足:需要 10,扣除退款占用后可用 2;operationId=private-operation-id",
|
||||
),
|
||||
"泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retryable_upstream_503_keeps_generic_classification_and_specific_safe_code() {
|
||||
let secret = ["sk", "provider-body-secret"].join("-");
|
||||
|
||||
@@ -926,7 +926,9 @@ fn platform_art_generation_observation_status(
|
||||
run_id: &str,
|
||||
error: &str,
|
||||
) -> &'static str {
|
||||
if platform_art_generation_error_needs_reconciliation(error)
|
||||
if game_creator_runtime_error_is_mud_points_insufficient(error) {
|
||||
"failed"
|
||||
} else if platform_art_generation_error_needs_reconciliation(error)
|
||||
|| game_creator_agent_runtime_external_generation_exists(root, agent_id, run_id)
|
||||
{
|
||||
AGENT_RUNTIME_TOOL_OBSERVATION_STATUS_NEEDS_RECONCILIATION
|
||||
@@ -990,4 +992,31 @@ mod platform_art_generation_observation_tests {
|
||||
"failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mud_point_insufficiency_is_a_definitive_failure_with_a_durable_generation_ledger() {
|
||||
let root = tempfile::tempdir().expect("create mud point observation status root");
|
||||
let ledger_directory = root
|
||||
.path()
|
||||
.join(".agent/runtime/canvas-generation-requests/art-director");
|
||||
fs::create_dir_all(&ledger_directory).expect("create durable generation ledger directory");
|
||||
fs::write(ledger_directory.join("run-accepted.json"), b"accepted")
|
||||
.expect("write durable generation ledger");
|
||||
|
||||
for error in [
|
||||
"平台图片生成任务失败:泥点余额不足;operationId=private-operation-id",
|
||||
"平台图片生成任务失败:可消费泥点不足:需要 10,扣除退款占用后可用 2",
|
||||
] {
|
||||
assert_eq!(
|
||||
platform_art_generation_observation_status(
|
||||
root.path(),
|
||||
"art-director",
|
||||
"run-accepted",
|
||||
error,
|
||||
),
|
||||
"failed",
|
||||
"明确的泥点不足不能因 durable ledger 存在而误入 reconciliation"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1496,6 +1496,18 @@ export function projectRuntimeVisibleCurrentWork(runtime: AgentRuntimeState) {
|
||||
return '正在执行当前任务';
|
||||
}
|
||||
|
||||
export const MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE =
|
||||
'泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。';
|
||||
|
||||
export function isMudPointInsufficientRuntimeError(message: string) {
|
||||
const normalized = message.trim().toLowerCase();
|
||||
return (
|
||||
message.includes('泥点余额不足') ||
|
||||
message.includes('可消费泥点不足:') ||
|
||||
normalized.includes('kind=mud-points-insufficient')
|
||||
);
|
||||
}
|
||||
|
||||
export function projectRuntimeVisibleError(
|
||||
message: string,
|
||||
subject: string,
|
||||
@@ -1506,6 +1518,9 @@ export function projectRuntimeVisibleError(
|
||||
if (isRuntimeConfigMissingError(message)) {
|
||||
return '运行时配置未完成,请先打开配置';
|
||||
}
|
||||
if (isMudPointInsufficientRuntimeError(message)) {
|
||||
return MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE;
|
||||
}
|
||||
const exhaustedUpstreamRetry = visibleMessage.match(
|
||||
/(?:^|[\s::])kind=upstream-(\d{3}) httpStatus=(\d{3}) fingerprint=[0-9a-f]{64} chars=\d+ retryAttempt=(\d+) maxRetries=(\d+) retryState=exhausted\s*$/,
|
||||
);
|
||||
|
||||
+49
-8
@@ -23,7 +23,9 @@ import type {
|
||||
} from '../../app/types';
|
||||
import {
|
||||
formatAgentRuntimeEvent,
|
||||
isMudPointInsufficientRuntimeError,
|
||||
isAgentRuntimeTerminalState,
|
||||
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
|
||||
projectNameFromPath,
|
||||
projectProfessionalAgentLabel,
|
||||
projectRuntimePlanProgress,
|
||||
@@ -89,6 +91,7 @@ export type GameChatSupervisorProgress = {
|
||||
currentWork: string;
|
||||
activeAgents: string[];
|
||||
evidence: GameChatProgressEvidence[];
|
||||
interruptionText: string | null;
|
||||
};
|
||||
|
||||
export type GameChatResultImage = {
|
||||
@@ -185,7 +188,7 @@ export function formatGameChatStageRecord(
|
||||
) {
|
||||
const terminalStatus =
|
||||
runtime.status === 'failed' || runtime.phase === 'failed'
|
||||
? '本轮失败'
|
||||
? progress.interruptionText || '本轮失败'
|
||||
: runtime.status === 'cancelled' || runtime.phase === 'cancelled'
|
||||
? '本轮已取消'
|
||||
: runtime.status === 'completed' || runtime.phase === 'completed'
|
||||
@@ -213,6 +216,34 @@ export function formatGameChatStageRecord(
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function gameChatMudPointInterruptionText(
|
||||
runtime: AgentRuntimeState,
|
||||
runtimeByAgentId: Record<string, AgentRuntimeState | undefined>,
|
||||
) {
|
||||
const childRuntimes = Object.values(runtimeByAgentId).filter(
|
||||
(childRuntime): childRuntime is AgentRuntimeState =>
|
||||
childRuntime !== undefined &&
|
||||
childRuntime.parentAgentId === runtime.agentId &&
|
||||
childRuntime.parentRunId === runtime.runId &&
|
||||
[
|
||||
'agent-delegate',
|
||||
'agent-delegate-retry',
|
||||
'agent-ready-task-scheduler',
|
||||
].includes(childRuntime.source),
|
||||
);
|
||||
const relatedRuntimes = [
|
||||
runtime,
|
||||
...childRuntimes,
|
||||
];
|
||||
return relatedRuntimes.some(
|
||||
(relatedRuntime) =>
|
||||
relatedRuntime.error &&
|
||||
isMudPointInsufficientRuntimeError(relatedRuntime.error),
|
||||
)
|
||||
? MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE
|
||||
: null;
|
||||
}
|
||||
|
||||
export function isGameChatStageRecordMessage(text: string) {
|
||||
return text.startsWith(GAME_CHAT_STAGE_RECORD_PREFIX);
|
||||
}
|
||||
@@ -504,6 +535,10 @@ export function buildGameChatProgressEvidence(
|
||||
),
|
||||
activeAgents,
|
||||
evidence,
|
||||
interruptionText: gameChatMudPointInterruptionText(
|
||||
runtime,
|
||||
runtimeByAgentId,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -817,13 +852,19 @@ export function SupervisorChatOnlyView({
|
||||
synchronizingAcceptedRun ||
|
||||
(runtime && !isAgentRuntimeTerminalState(runtime)),
|
||||
);
|
||||
const status = runtimeError
|
||||
? projectRuntimeVisibleError(runtimeError, '项目总控 Agent', true)
|
||||
: synchronizingAcceptedRun
|
||||
? '已投递,正在同步 Agent Runner'
|
||||
: runtime
|
||||
? projectSupervisorChatRuntimeStatus(runtime)
|
||||
: workspaceStatus;
|
||||
const gameChatInterruptionText =
|
||||
gameChatMode && runtime
|
||||
? gameChatMudPointInterruptionText(runtime, runtimeByAgentId)
|
||||
: null;
|
||||
const status =
|
||||
gameChatInterruptionText ||
|
||||
(runtimeError
|
||||
? projectRuntimeVisibleError(runtimeError, '项目总控 Agent', true)
|
||||
: synchronizingAcceptedRun
|
||||
? '已投递,正在同步 Agent Runner'
|
||||
: runtime
|
||||
? projectSupervisorChatRuntimeStatus(runtime)
|
||||
: workspaceStatus);
|
||||
const headerStatus =
|
||||
gameChatMode && previewStatus === '未启动'
|
||||
? '预览未启动'
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
} from '../src/app/types';
|
||||
import {
|
||||
formatAgentRuntimeEvent,
|
||||
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
|
||||
mergeGameChatRuntimeResponseMessagesIntoHistory,
|
||||
projectRuntimeVisibleCurrentWork,
|
||||
projectRuntimeVisibleError,
|
||||
@@ -292,6 +293,36 @@ describe('Agent Runtime Provider 状态投影', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('明确说明泥点余额不足导致的中断并隐藏附加诊断', () => {
|
||||
for (const message of [
|
||||
'agentLlm.project-supervisor 规划调用 LLM 失败:kind=mud-points-insufficient',
|
||||
'平台图片生成任务失败:泥点余额不足;operationId=private-operation-id',
|
||||
]) {
|
||||
expect(projectRuntimeVisibleError(message, '项目总控 Agent', true)).toBe(
|
||||
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
|
||||
);
|
||||
expect(
|
||||
projectSupervisorVisibleConversationText(`后台任务失败:${message}`),
|
||||
).toBe(MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE);
|
||||
}
|
||||
|
||||
const failedRuntime = {
|
||||
...providerRetryRuntime(),
|
||||
status: 'failed',
|
||||
phase: 'failed',
|
||||
error:
|
||||
'平台图片生成任务失败:泥点余额不足;operationId=private-operation-id',
|
||||
};
|
||||
const status = projectSupervisorChatRuntimeStatus(failedRuntime);
|
||||
expect(status).toBe(MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE);
|
||||
expect(status).not.toContain('operationId');
|
||||
expect(status).not.toContain('private-operation-id');
|
||||
|
||||
expect(
|
||||
projectRuntimeVisibleError('上游余额不足', '项目总控 Agent', true),
|
||||
).toBe('项目总控 Agent 执行失败,请稍后重试');
|
||||
});
|
||||
|
||||
test('即使前缀含恶意上游正文也只展示严格字段派生的摘要', () => {
|
||||
const fingerprint = 'b'.repeat(64);
|
||||
const malicious =
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
AgentRuntimeEventRecord,
|
||||
AgentRuntimeState,
|
||||
} from '../../src/app/types';
|
||||
import { MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE } from '../../src/features/agent-runtime/model';
|
||||
import {
|
||||
buildGameChatProgressEvidence,
|
||||
collectGameChatResultImages,
|
||||
@@ -2684,7 +2685,7 @@ export function registerProjectSupervisorSurfaceTests() {
|
||||
taskId: 'design-foundation',
|
||||
sessionId: 'design-session',
|
||||
runId: 'design-run',
|
||||
source: 'agent-delegate',
|
||||
source: 'agent-ready-task-scheduler',
|
||||
parentAgentId: 'project-supervisor',
|
||||
parentRunId: 'active-parent-run',
|
||||
updatedAt: 30,
|
||||
@@ -3574,6 +3575,52 @@ export function registerProjectSupervisorSurfaceTests() {
|
||||
expect(statusCard.textContent).toContain('生成 Agent 工具计划(本轮)');
|
||||
});
|
||||
|
||||
it('shows and archives the explicit mud point interruption from a failed art child runtime', () => {
|
||||
const rootRunId = 'game-chat-mud-point-root-run';
|
||||
const runtime = gameChatRuntimeState({
|
||||
runId: rootRunId,
|
||||
status: 'failed',
|
||||
phase: 'failed',
|
||||
error: '专业 Agent 执行失败',
|
||||
updatedAt: 9100,
|
||||
});
|
||||
const artRuntime = gameChatRuntimeState({
|
||||
agentId: 'art-asset-plan',
|
||||
taskId: 'art-asset-plan',
|
||||
sessionId: 'game-chat-mud-point-art-session',
|
||||
runId: 'game-chat-mud-point-art-run',
|
||||
source: 'agent-delegate',
|
||||
parentAgentId: 'project-supervisor',
|
||||
parentRunId: rootRunId,
|
||||
status: 'failed',
|
||||
phase: 'failed',
|
||||
error:
|
||||
'平台图片生成任务失败:可消费泥点不足:需要 10,扣除退款占用后可用 2;operationId=private-operation-id',
|
||||
updatedAt: 9000,
|
||||
});
|
||||
const runtimeByAgentId = { 'art-asset-plan': artRuntime };
|
||||
|
||||
renderGameChatStatus({ runtime, runtimeByAgentId });
|
||||
|
||||
const status = screen.getByLabelText('最新状态').textContent ?? '';
|
||||
expect(status).toContain(MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE);
|
||||
expect(status).not.toContain('operationId');
|
||||
expect(status).not.toContain('private-operation-id');
|
||||
|
||||
const progress = buildGameChatProgressEvidence(
|
||||
runtime,
|
||||
runtimeByAgentId,
|
||||
null,
|
||||
);
|
||||
if (!progress) {
|
||||
throw new Error('missing failed game-chat progress fixture');
|
||||
}
|
||||
const stageRecord = formatGameChatStageRecord(runtime, progress, []);
|
||||
expect(stageRecord).toContain(MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE);
|
||||
expect(stageRecord).not.toContain('operationId');
|
||||
expect(stageRecord).not.toContain('private-operation-id');
|
||||
});
|
||||
|
||||
it('counts only the seven first-playable tasks in game-chat progress', () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'game-chat-progress-total',
|
||||
@@ -3622,6 +3669,7 @@ export function registerProjectSupervisorSurfaceTests() {
|
||||
});
|
||||
const initialSupervisorOverrides = {
|
||||
runId: supervisorRunId,
|
||||
source: 'project-supervisor-game-chat',
|
||||
runProfile: 'autonomous-game-build' as const,
|
||||
status: 'running',
|
||||
phase: 'execution',
|
||||
@@ -3652,7 +3700,7 @@ export function registerProjectSupervisorSurfaceTests() {
|
||||
taskId: 'code-prototype',
|
||||
sessionId: 'code-prototype-progress-session',
|
||||
runId: 'code-prototype-progress-run',
|
||||
source: 'agent-delegate',
|
||||
source: 'agent-ready-task-scheduler',
|
||||
parentAgentId: 'project-supervisor',
|
||||
parentRunId: supervisorRunId,
|
||||
delegationId: 'code-prototype-progress-delegation',
|
||||
|
||||
Reference in New Issue
Block a user