Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ce2c10107 | |||
| ea141d9f17 | |||
| 769740a46d | |||
| b472b9eec9 |
@@ -27,7 +27,6 @@ const DIRECT_TOOL_BRIDGE_SEARCH_URL: &str = "https://www.bing.com/search?format=
|
||||
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_NAME_CHARS: usize = 120;
|
||||
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS: usize = 80;
|
||||
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_PAGE_SIZE: usize = 100;
|
||||
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN: usize = 4;
|
||||
const DIRECT_TOOL_BRIDGE_MAX_ACCOUNT_ASSET_ID_CHARS: usize = 512;
|
||||
const DIRECT_TOOL_BRIDGE_MAX_LOCAL_ASSET_PATH_CHARS: usize = 512;
|
||||
|
||||
@@ -68,7 +67,6 @@ struct DirectToolBridgeActiveTurnAuthorization {
|
||||
turn_id: String,
|
||||
brief_sha256: Option<String>,
|
||||
completed_result: Option<Value>,
|
||||
resource_request_ids: BTreeMap<String, (String, String)>,
|
||||
}
|
||||
|
||||
enum DirectToolBridgeRegenerationCall {
|
||||
@@ -207,7 +205,6 @@ impl DirectToolBridgeState {
|
||||
turn_id: turn_id.clone(),
|
||||
brief_sha256: None,
|
||||
completed_result: None,
|
||||
resource_request_ids: BTreeMap::new(),
|
||||
});
|
||||
Ok(DirectToolBridgeTurnGuard {
|
||||
state: Arc::clone(self),
|
||||
@@ -637,30 +634,17 @@ impl DirectToolBridgeState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resource_request_ids(&self, request_fingerprint: &str) -> Result<(String, String), String> {
|
||||
let mut authorization = self
|
||||
/// 取当前回合身份,媒体资源请求按回合身份与请求指纹确定性派生 operation/idempotency id。
|
||||
fn active_resource_turn_id(&self) -> Result<String, String> {
|
||||
let authorization = self
|
||||
.turn_authorization
|
||||
.lock()
|
||||
.map_err(|_| "AGC 工具桥回合授权状态不可用".to_string())?;
|
||||
let active = authorization
|
||||
authorization
|
||||
.active
|
||||
.as_mut()
|
||||
.ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())?;
|
||||
if let Some(ids) = active.resource_request_ids.get(request_fingerprint) {
|
||||
return Ok(ids.clone());
|
||||
}
|
||||
if active.resource_request_ids.len() >= DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN {
|
||||
return Err("单个用户回合最多只能创建四项媒体资源请求".to_string());
|
||||
}
|
||||
let operation_id =
|
||||
direct_resource_request_uuid(&active.turn_id, "operation", request_fingerprint);
|
||||
let idempotency_key =
|
||||
direct_resource_request_uuid(&active.turn_id, "idempotency", request_fingerprint);
|
||||
active.resource_request_ids.insert(
|
||||
request_fingerprint.to_string(),
|
||||
(operation_id.clone(), idempotency_key.clone()),
|
||||
);
|
||||
Ok((operation_id, idempotency_key))
|
||||
.as_ref()
|
||||
.map(|active| active.turn_id.clone())
|
||||
.ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1890,8 +1874,11 @@ async fn bridge_create_or_derive_resource(
|
||||
))
|
||||
.await?
|
||||
} else {
|
||||
let (operation_id, idempotency_key) =
|
||||
state.resource_request_ids(&request_fingerprint)?;
|
||||
let turn_id = state.active_resource_turn_id()?;
|
||||
let operation_id =
|
||||
direct_resource_request_uuid(&turn_id, "operation", &request_fingerprint);
|
||||
let idempotency_key =
|
||||
direct_resource_request_uuid(&turn_id, "idempotency", &request_fingerprint);
|
||||
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
|
||||
let source_resource_id = source_asset
|
||||
.as_ref()
|
||||
@@ -2001,7 +1988,11 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
|
||||
})
|
||||
.await?
|
||||
} else {
|
||||
let (operation_id, idempotency_key) = state.resource_request_ids(&fingerprint)?;
|
||||
// id 按回合身份与请求指纹确定性派生,同指纹重试与 pending 对账语义不变。
|
||||
let turn_id = state.active_resource_turn_id()?;
|
||||
let operation_id = direct_resource_request_uuid(&turn_id, "operation", &fingerprint);
|
||||
let idempotency_key =
|
||||
direct_resource_request_uuid(&turn_id, "idempotency", &fingerprint);
|
||||
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
|
||||
let request = DeriveLocalProjectResourceInput {
|
||||
project_path: state.root.to_string_lossy().into_owned(),
|
||||
|
||||
@@ -2519,6 +2519,11 @@ export function registerDeveloperAgentWindowTests() {
|
||||
|
||||
const emptyGoalPanel = await screen.findByLabelText('Agent Goal 状态');
|
||||
expect(within(emptyGoalPanel).getByText('尚未开始')).not.toBeNull();
|
||||
// 读取历史的异步链路未结束时按钮处于 disabled,等它可用再切换模式,避免点击落空。
|
||||
const goalModeButton = screen.getByRole('button', { name: '目标' });
|
||||
await waitFor(() => {
|
||||
expect(goalModeButton.hasAttribute('disabled')).toBe(false);
|
||||
});
|
||||
selectDeveloperAgentChatMode('goal');
|
||||
expect(
|
||||
screen.getByRole('button', { name: '目标' }).getAttribute('aria-pressed'),
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
|
||||
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
|
||||
|
||||
## 2026-09-19 AGC Direct 删除每回合四项媒体资源请求上限
|
||||
|
||||
- 背景:2026-08-24 引入的单回合四项上限以「整个 agent run(一条用户消息到回合结束)」为窗口,计数只增不减、请求完成不释放额度;autonomous 游戏构建要求 agent 不停下跑完整局,额度耗尽后的报错实际是终态,与技能的三次重试纪律冲突,现场表现为长时间无效重试。2026-09-18 先将不计费的抠图豁免,但付费 create/derive 仍受同一窗口问题影响。
|
||||
- 决策:整体删除该上限机制。移除 `DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN` 常量、回合授权状态中的 `resource_request_ids` 计数 map 与 `resource_request_ids()` 方法;`agc_create_or_derive_resource`(含 `agc_edit_image` 委托)与 `agc_remove_background` 统一按回合身份 + 请求指纹确定性派生 operation/idempotency id,同指纹重试复用与 pending 对账语义不变。付费提交串行仍由 `resource_generation_gate` 互斥保证,成本控制由服务端计费与泥点余额兜底,客户端不再按回合计数设限。
|
||||
- 验证方式:`cargo check`(ai-game-creator-shell src-tauri)通过,203 项警告与基线一致;无测试断言该上限,未新增测试。
|
||||
|
||||
## 2026-09-18 AGC Direct 抠图不占每回合四项付费媒体额度
|
||||
|
||||
- 状态:2026-09-19 起该上限机制整体删除(见上条),本条目仅作追溯。
|
||||
- 背景:2026-08-24 起 `agc_create_or_derive_resource` 与 `agc_remove_background` 共用每回合四项媒体资源请求上限。抠图服务端持久化 `generation_cost_mud_points: 0`(不计费),bgfilter 实测单张约 1 秒,上限导致一回合抠超过四张时后续请求被直接拒绝、agent 反复无效重试。
|
||||
- 决策:`agc_remove_background` 不再经过 `resource_request_ids` 计数,直接按回合身份与请求指纹确定性派生 operation/idempotency id(与 map 复用结果一致),同指纹重试与 pending 对账语义不变。付费的 create/derive(含 `agc_edit_image` 委托)维持四项上限与原有报错文案,且抠图请求不再挤占其额度。
|
||||
- 验证方式:`cargo check`(ai-game-creator-shell src-tauri)与 `git diff --check` 通过;未新增测试。
|
||||
|
||||
## 2026-09-18 Provider 瞬态重试次数严格按设置执行(游戏开发 Agent 与策划 Agent 不再被档位收进区间)
|
||||
|
||||
- 背景:AGC 客户端此前把 `agentLlm.<agent>.maxRetries` 按运行档位重新收进固定区间——`autonomous-game-build` 档位(自主构建的游戏开发 Agent 及其继承档位的专业子 Agent)被抬到 12~16,`standard` 档位(含立项策划入口的策划 Agent 与普通 Agent 对话)被压到最多 3;瞬态分类里的上游 400 还在同一预算上再收窄到 2 次。现场把 `maxRetries` 设成 5 时,游戏开发与策划两条链路都不按设置执行。
|
||||
|
||||
@@ -203,7 +203,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
|
||||
|
||||
- `agc_tools` 新增 `agc_list_registered_assets` 与 `agc_create_or_derive_resource`。前者按 `kind / assetId / offset / limit` 有界查询客户端权威 manifest,并可显式返回角色动画正式序列帧的稳定 objectKey、assetObjectId 和尺寸;结果不包含完整 manifest、prompt、model、provider route、签名 URL、宿主路径或凭据。后者只接受 `kind / mode / sourceLocalAssetId / prompt / assetName`,`create` 仅允许无源视频、音效和背景音乐,`derive` 必须引用当前项目已登记的 localAssetId,角色动画固定为 derive。
|
||||
- `prompt` 上限按 `kind` 分别生效,且工具 schema、MCP 校验、客户端工具桥与提交校验共用同一权威口径(`resource_edit_prompt_max_chars`):背景音乐 140、音效 1900、视频与角色动画 4000、图片编辑 32000。schema 逐 kind 声明 `maxLength` 并在 `prompt` 描述里写明数字,超限必须在发起任何桥请求与付费提交之前失败并回报真实上限;`sourceLocalAssetId` 不是当前项目已登记资源时,错误文案必须直接给出 `agc_list_registered_assets` 与 `agc_list_project_files` → `agc_import_account_assets.localPaths` 两步后续动作。
|
||||
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行且最多四项。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
|
||||
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行提交,不设每回合计数上限(2026-09-19 起,见决策记录)。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
|
||||
- 资源查询同时投影未完成 operation 的安全状态。媒体工具成功只返回 operation、本地相对路径、资源类型、Canvas/resource/asset/task 身份、正式序列帧以及脱敏后的 `warnings / sliceWarnings`;错误继续使用统一脱敏边界。客户端资源账本持久化 completed 结果的两类告警,committed replay 不能把历史告警伪装成空集合。
|
||||
- 角色动画、视频、音效和背景音乐在构造新的远端请求前统一准备当前项目同名画布与素材目录上下文,并在端点支持时携带 `projectId / assetFolderId / canvasCompletion`。角色动画 placeholder 使用源图片真实宽高,避免非方形角色进入画布时失真;正式 resource/asset 与序列帧继续直接复用 External 返回身份,不从首帧伪造重复资源。已有冻结 request body 或已受理 operation 保持不变,不因本次升级重建请求或重复扣费。
|
||||
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份。普通登录态使用账号鉴权的 `/api/editor/images/background-removals`,ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`;客户端接收异步受理后轮询任务状态,下载完成媒体并登记到本地 manifest,未知结果保留同一 operation 供恢复。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
|
||||
Reference in New Issue
Block a user