修复余额失败后的生成重试收口
明确失败的旧 operation 仅通过只读状态查询后收口 请求快照变化时允许安全创建新的生成请求 保留排队运行完成及未知状态的幂等保护
This commit is contained in:
@@ -681,6 +681,47 @@ pub(in crate::agent) fn platform_art_generation_error_result_unknown(error: &str
|
||||
error.starts_with(EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX)
|
||||
}
|
||||
|
||||
/// Observe an accepted operation once without waiting. A single GET that
|
||||
/// reports `failed` is authoritative and allows a changed retry to release
|
||||
/// the old local slot; queued/running/unknown outcomes remain protected.
|
||||
async fn accepted_generation_is_authoritatively_failed_once(
|
||||
client: &reqwest::Client,
|
||||
access: &ExternalEditorBindingAccess<'_>,
|
||||
submission_payload: &serde_json::Value,
|
||||
) -> Result<bool, String> {
|
||||
let submission = external_editor_response_data(submission_payload);
|
||||
let operation_id = json_string_field(submission, "operationId")
|
||||
.ok_or_else(|| "External Editor accepted 账本缺少 operationId".to_string())?;
|
||||
access.validate_frozen_session()?;
|
||||
let payload = tokio::time::timeout(
|
||||
Duration::from_secs(3),
|
||||
external_editor_json_request(
|
||||
client
|
||||
.get(format!(
|
||||
"{}{}",
|
||||
access.api_base_url(),
|
||||
access.generation_status_route(&operation_id)
|
||||
))
|
||||
.bearer_auth(access.bearer_token()),
|
||||
"查询平台图片生成任务",
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "查询平台图片生成任务超时".to_string())??;
|
||||
access.validate_frozen_session()?;
|
||||
let generation = platform_generation_status_data(&payload);
|
||||
match json_string_field(generation, "status").as_deref() {
|
||||
Some("failed") => Ok(true),
|
||||
Some("queued" | "running" | "completed") => Ok(false),
|
||||
Some(status) => Err(format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 平台图片生成任务返回未知状态 {status};operationId={operation_id}"
|
||||
)),
|
||||
None => Err(format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 平台图片生成任务状态响应缺少 status;operationId={operation_id}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn external_editor_json_request(
|
||||
request: reqwest::RequestBuilder,
|
||||
action: &str,
|
||||
@@ -2427,6 +2468,34 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at
|
||||
)
|
||||
})?;
|
||||
if snapshot.generation_prompt != generation_prompt {
|
||||
if platform_art_generation_runtime_status(&state) == "accepted" {
|
||||
if let Ok(submission) = platform_art_generation_runtime_submission_payload(&state) {
|
||||
if accepted_generation_is_authoritatively_failed_once(
|
||||
&client,
|
||||
&binding_access,
|
||||
&submission,
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if let Some(context) = runtime_context {
|
||||
remove_platform_art_generation_runtime_state_at(
|
||||
root,
|
||||
&context.agent_id,
|
||||
&context.run_id,
|
||||
)?;
|
||||
}
|
||||
return Box::pin(request_platform_art_asset_with_runtime_options_at(
|
||||
root,
|
||||
prompt,
|
||||
briefs,
|
||||
options,
|
||||
runtime_context,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Err(format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 当前生成意图与已持久化请求快照不一致,已拒绝将旧操作当作本次请求恢复;原生成账本已保留,需要先完成或对账旧操作"
|
||||
));
|
||||
@@ -2448,6 +2517,36 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at
|
||||
)
|
||||
})?;
|
||||
if snapshot.reference_resource_ids != [current_reference] {
|
||||
if platform_art_generation_runtime_status(&state) == "accepted" {
|
||||
if let Ok(submission) =
|
||||
platform_art_generation_runtime_submission_payload(&state)
|
||||
{
|
||||
if accepted_generation_is_authoritatively_failed_once(
|
||||
&client,
|
||||
&binding_access,
|
||||
&submission,
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if let Some(context) = runtime_context {
|
||||
remove_platform_art_generation_runtime_state_at(
|
||||
root,
|
||||
&context.agent_id,
|
||||
&context.run_id,
|
||||
)?;
|
||||
}
|
||||
return Box::pin(request_platform_art_asset_with_runtime_options_at(
|
||||
root,
|
||||
prompt,
|
||||
briefs,
|
||||
options,
|
||||
runtime_context,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Err(format!(
|
||||
"{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} 当前规范图身份与已持久化派生请求不一致,已拒绝恢复旧操作;原生成账本已保留,需要先完成或对账旧操作"
|
||||
));
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
### 失败生成任务归档与任务侧栏
|
||||
|
||||
- 用户界面的“删除失败任务”语义是归档,不物理销毁私有 generation ledger。只有平台明确失败的 `failed` 任务可归档;`reconciliation-required`、已受理、运行中和结果未知任务不得移出恢复队列。
|
||||
- 已受理 operation 在重试时若请求快照发生变化,客户端可先对原 operation 执行一次只读状态查询;仅当平台明确返回 `failed` 时才自动收口旧账本并允许新提交,排队、运行中、完成或未知状态继续保持原幂等身份并阻断替代请求。
|
||||
- 归档命令校验 project、draft、generation 与 expected draft revision,先把私有 ledger 写入可重放的 `archiving/archivedAt`,再从 `draft.generations` 移除公开投影并推进一次 revision;草稿删除成功并回读后才发布 `archived`。`archiving` 以及历史上已写 `archived` 但仍残留公开记录的状态都必须在恢复阶段幂等收敛,且不依赖图片生成服务凭证。
|
||||
- 失败占位和右上角任务项复用同一个归档动作,成功后两处同时消失,其它任务、图层和候选不受影响。
|
||||
- 任务侧栏折叠只属于当前会话 UI 状态,不写入 draft 或 manifest。视觉和交互复用现役美术画布:右上角独立“任务列表”图标按钮、20rem 白色模糊卡、总数徽标、`排队/生成中` 与 `已完成` 双 Tab、状态圆形图标、阶段进度和时间信息;折叠后只保留图标按钮,不显示摘要卡。用户显式新建 generation 时自动展开并切回活动 Tab,普通进度更新不得推翻用户已有折叠选择。Game Agent 的失败归档作为任务行扩展保留。
|
||||
|
||||
Reference in New Issue
Block a user