fix(core): 对抗式交叉验证发现的 4 个 critical + 关键 major

- 额度泄漏(critical):流式 agent 客户端断连抛 GeneratorExit(BaseException)逃过 except,
  预扣额度冻结。改 try/finally + settled 标志兜底释放。
- 默认图像模型(critical):0006 只停用 yunqi,没停火山 Seedream(bootstrap 种入 active 且更早),
  get_default_model 仍选 Seedream → 参考图分镜/模特库被架空。改为停用所有非 tokenssr 图像模型 +
  provider down→up 自愈置 active。
- 故事板参考提示词(critical):image_edit 分支复用 request_payload['prompt'](恒真)→ refs 版
  锁脸锁商品提示词成死代码,参考图白传。改为强制用 refs 版提示词。
- 凭证解析(major):build_provider 官方直连分支丢了 DB api_key,与中转站路径优先级不一致。
  统一走 resolve_provider_credentials。
- JSON 抽取(major):_extract_json 取首个围栏块(易抓示例块)+ rfind('}') 越界。改为取末个
  合法草稿块 + 字符串感知的括号配平扫描。
- 前端兜底(major):流式已成功(saved)但收尾抖动时 catch 仍回退旧端点 → 重复生成+扣费。
  改为按 ok/收到事件 判定,只在零事件时回退。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-17 03:21:06 +08:00
co-authored by Claude Opus 4.8
parent 6464001f84
commit a625f86b38
4 changed files with 155 additions and 83 deletions
+12 -3
View File
@@ -655,6 +655,7 @@ export function PipelinePage(props: {
const agentMode = mode ?? mapSourceToMode(source ?? chatMode);
const baseVersionId = agentMode === "revise" ? currentScript?.id : undefined;
let ok = false;
let receivedEvent = false;
try {
await api.agentScriptStream(
project.id,
@@ -667,6 +668,7 @@ export function PipelinePage(props: {
total_duration: 60
},
(evt) => {
receivedEvent = true;
if (evt.type === "tool") {
// 工具卡:running 时把 label 滚进进度流(done/error 暂只用于结束态)
if (evt.status === "running" && typeof evt.label === "string") {
@@ -685,9 +687,16 @@ export function PipelinePage(props: {
}
);
} catch {
// 流式不可用(网关不支持 SSE 等)→ 退回旧同步端点,保证可用性
const res = await onGenerateScript(prompt, source ?? chatMode).catch(() => null);
ok = !!res;
if (ok) {
// 已收到 saved:流其实成功了(只是收尾抖动),绝不回退重发,避免重复生成+重复扣费
} else if (!receivedEvent) {
// 一个事件都没收到 = 流式根本没跑起来(网关不支持 SSE)→ 退回旧同步端点
const res = await onGenerateScript(prompt, source ?? chatMode).catch(() => null);
ok = !!res;
} else {
// 流中途断了(后端已通过 finally 释放预扣额度):不重试,提示用户
pushMsg("ai", "生成中断了,请重试。");
}
}
setChatMsgs((list) => list.map((m) => (m.id === progressId ? { ...m, done: true } : m)));
if (ok) {