From cb4408104009df448b0b1db7b7e0af051892f7bc Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:27:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=E8=B5=84=E6=96=99=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE=E7=9A=84=E9=A1=B9=E7=9B=AE=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E8=84=B1=E6=95=8F=EF=BC=9A=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=E5=8F=AA=E5=B8=A6=E6=96=87=E4=BB=B6=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GameDistributionPublishPanel:buildPublishProjectContext 不再原样拼接 asset.localPath,新增 contextSafeAssetLabel(统一分隔符、取最后一段、限长 80),绝对路径(含用户目录)不再进入提示词;上下文整体仍截到 6,000 字符 - 新增用例「发布资料建议只带脱敏且有界的项目上下文」:40 个绝对路径素材 + 超长版本说明,断言长度在 5,000~6,000、含文件名、不含 C:\Users 与项目目录;13 tests passed - 变异验证:改回 `asset.localPath` 原样拼接后该用例立即以 expected ... to not include 'C:\Users' 变红 - 游戏分发里程碑第 C2 条补记该客户端侧口径与剩余边界(GUI 与真实 Provider 仍待验收) --- .../GameDistributionPublishPanel.tsx | 15 +++++++- .../gameDistributionPublishPanel.test.tsx | 36 +++++++++++++++++++ ...‹碑】游戏分发目录详情与在线游玩-2026-09-18.md | 2 +- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx b/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx index 54eaa2db1..23524532f 100644 --- a/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx +++ b/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx @@ -96,7 +96,7 @@ function buildPublishProjectContext(manifest: GameCreationAppManifest) { .join('、'); const assetSummary = (manifest.assets ?? []) .slice(0, 24) - .map((asset) => `${asset.kind}:${asset.localPath}`) + .map((asset) => `${asset.kind}:${contextSafeAssetLabel(asset.localPath)}`) .join('、'); const versionSummary = (manifest.versions ?? []) .slice(-3) @@ -116,6 +116,19 @@ function buildPublishProjectContext(manifest: GameCreationAppManifest) { .slice(0, 6_000); } +/** + * 送进模型的项目上下文必须脱敏:素材只带文件名,不带目录片段。 + * + * 素材清单里的 `localPath` 正常是项目内相对路径,但登记来源不受本地约束时也可能是 + * 绝对路径(含盘符或用户目录)——`C:\Users\<用户名>\...\assets\x.png` 这种一旦进提示词 + * 就等于把本地目录结构发给了平台模型。这里统一取最后一段并限长。 + */ +function contextSafeAssetLabel(value: string) { + const normalized = value.trim().replaceAll('\\', '/'); + const base = normalized.split('/').filter(Boolean).pop() ?? ''; + return base.slice(0, 80); +} + function buildCoverGenerationPrompt( manifest: GameCreationAppManifest, summary: string, diff --git a/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx b/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx index c4e34ede5..ddc3458b8 100644 --- a/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx +++ b/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx @@ -176,6 +176,42 @@ describe('GameDistributionPublishPanel', () => { ).not.toBeNull(); }); + test('发布资料建议只带脱敏且有界的项目上下文', async () => { + const assets = Array.from({ length: 40 }, (_, index) => ({ + kind: 'image', + localPath: `C:\\Users\\author\\secret-project\\assets\\shot-${index}.png`, + name: `shot-${index}.png`, + })); + installTauriInvoke(async () => undefined); + renderPanel({ + manifest: { + ...MANIFEST, + assets, + tasks: Array.from({ length: 24 }, (_, index) => ({ + title: `任务 ${index}`, + status: 'completed', + })), + // 超长版本说明用于验证上下文真的被截到上限,而不是只在短样例里碰巧合规。 + versions: Array.from({ length: 6 }, () => ({ + editPrompt: '长'.repeat(3_000), + })), + } as unknown as GameCreationAppManifest, + }); + + await waitFor(() => { + expect(suggestGameDistributionPublishMetadata).toHaveBeenCalled(); + }); + const [request] = vi.mocked(suggestGameDistributionPublishMetadata).mock + .calls[0]; + const context = String(request.context ?? ''); + expect(context.length).toBeGreaterThanOrEqual(5_000); + expect(context.length).toBeLessThanOrEqual(6_000); + expect(context).toContain('shot-0.png'); + // 脱敏:绝对路径里的用户目录与项目目录不得进入提示词。 + expect(context).not.toContain('C:\\Users'); + expect(context).not.toContain('secret-project'); + }); + test('确认后基于项目上下文生成封面并作为发布素材', async () => { vi.mocked(readGameCoverGenerationPrice).mockResolvedValueOnce(5); installTauriInvoke(async () => undefined); diff --git a/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md b/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md index b4785808f..4ed1582c1 100644 --- a/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md +++ b/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md @@ -96,7 +96,7 @@ ### 行为与验收 - [ ] AGC 从已构建 dist 生成根入口为 `index.html` 的真实包,一次提交动作完成检查、资料确认、上传和送审;状态及失败原因与服务端回读一致。 -- [ ] AGC 发布面板隐藏发行包技术摘要;打开时基于有界、脱敏的项目上下文免费生成一句话简介与白名单分类,失败保留本地兜底且不阻断发布;作者始终可以直接编辑生成结果。 +- [ ] AGC 发布面板隐藏发行包技术摘要;打开时基于有界、脱敏的项目上下文免费生成一句话简介与白名单分类,失败保留本地兜底且不阻断发布;作者始终可以直接编辑生成结果。(2026-09-29 补客户端侧口径:「脱敏」此前只在服务端有界——`buildPublishProjectContext()` 会把素材的 `localPath` 原样写进提示词,登记来源不受本地约束时绝对路径(含用户目录)就会外发。现在素材只带**文件名**(`contextSafeAssetLabel` 取最后一段并限长 80),上下文整体仍截到 6,000 字符;`gameDistributionPublishPanel.test.tsx` 新增「发布资料建议只带脱敏且有界的项目上下文」并用 40 个绝对路径素材 + 超长版本说明验证。变异验证:把该处改回 `asset.localPath` 原样拼接后,用例立即以 `expected ... to not include 'C:\Users'` 变红。GUI 打开面板、真实 Provider 生成与失败兜底仍待客户端验收。) - [ ] AGC 发布封面支持基于项目上下文生成,复用现役图片生成与泥点扣费链路;生成结果登记为当前账号平台素材后自动作为 `coverAssetId`,不二次上传。 - [x] 网页可选 ZIP、提交封面和必需资料,进入相同上传/校验/审核流程;任一客户端可以查看同账号游戏状态,更新沿用相同 `gameId`。 - [x] 上传中断、双击、登录失效、窗口关闭后恢复原操作;换账号不能恢复前账号私有状态;待审不能显示为已发布。