diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs
index dc71492c0..53eff6757 100644
--- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs
+++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs
@@ -4179,10 +4179,18 @@ fn sync_direct_codex_project_file_projection_at(
return Err("Codex 返回后没有可登记的游戏文件".to_string());
}
update_manifest_task_status_at(root, "code-prototype", GameCreationAppTaskStatus::Completed)?;
+ let mut version_registered = false;
if outputs_changed || manifest_requires_sync {
let _lock = acquire_project_write_lock_with_wait(root, "direct-codex.version")?;
let version_revision = advance_agent_runtime_project_revision_locked(root)?;
- append_agent_game_iteration_version_at(root, version_revision)?;
+ version_registered = append_agent_game_iteration_version_at(root, version_revision)?;
+ }
+ if version_registered {
+ // 新增的内部版本只落在清单里:这条写入不经过任何工具桥,也就没有任何事件。
+ // 必须显式宣告清单失效,否则渲染层的资源投影(「项目版本」卡、发布面板的版本标签)
+ // 在本回合结束后不会重读清单 —— 版本已经在盘上,用户却看不到新的版本卡。
+ // 与工具桥的素材写入同一口径(见 direct_tool_bridge.rs 的 asset-import)。
+ emit_game_creator_manifest_invalidated(root, "direct-codex.version");
}
Ok(())
}
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 b030fa5e3..8f32bb5b0 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
@@ -282,6 +282,22 @@ export function GameDistributionPublishPanel({
() => resolveGameCreationAppProjectVersion(manifest),
[manifest],
);
+ /**
+ * 「项目版本」这一格的说明。标签只有一个来源(`versions[]` 条数),平台线上版本号是
+ * 另一套事实:两句都要说清,且不在这里做第二份口径派生。
+ */
+ const projectVersionHint = useMemo(() => {
+ const internalCount = manifest.versions?.length ?? 0;
+ const label =
+ internalCount > 0
+ ? `本次发布标签 = 最新内部版本 v${projectVersion};agent 再改进一次后,下一次发布将是 v${projectVersion + 1}。`
+ : `这个项目还没有内部版本,本次发布按首版 v${projectVersion}。`;
+ const platform =
+ latestVersionNumber > 0
+ ? `线上最近提交 v${latestVersionNumber} 是平台侧事实,与本标签互不推导。`
+ : '第一次发布即第一版。';
+ return `${label}${platform}`;
+ }, [latestVersionNumber, manifest.versions, projectVersion]);
const metadataSuggestionRequestRef = useRef(0);
const publicationRequestRef = useRef(0);
const publicationDraftAppliedRef = useRef(false);
@@ -922,7 +938,7 @@ export function GameDistributionPublishPanel({
/>
- {`来自 AGC 内部版本(版本 ${projectVersion}):每次智能体修订追加一个内部版本,发布标签随之推进;${latestVersionNumber > 0 ? `线上最近提交 v${latestVersionNumber} 是平台侧事实,与本标签互不推导。` : '第一次发布即第一版。'}`}
+ {projectVersionHint}
+ {publicationTag ? (
+
+ {`最新 · 发布标签 v${publicationTag}`}
+
+ ) : null}
{resource.version?.childVersionIds.length
? `${resource.version.childVersionIds.length} 个直接子版本`
diff --git a/apps/ai-game-creator-shell/src/view/project-development/resourceProjectionModel.ts b/apps/ai-game-creator-shell/src/view/project-development/resourceProjectionModel.ts
index 4b110f41e..f209b5920 100644
--- a/apps/ai-game-creator-shell/src/view/project-development/resourceProjectionModel.ts
+++ b/apps/ai-game-creator-shell/src/view/project-development/resourceProjectionModel.ts
@@ -32,6 +32,12 @@ export type ProjectAgentResultSummary = {
export type ProjectVersionResourceSummary = GameIterationVersion & {
label: string;
childVersionIds: string[];
+ /**
+ * 只有**最新内部版本**(`manifest.versions` 最后一条)带它,值就是这次发布的标签
+ * (= 内部版本序数 = `versions.length`,与发布面板的 `resolveGameCreationAppProjectVersion`
+ * 取的是同一个数)。卡面据此渲染「最新 · 发布标签 v{N}」角标;其余版本不渲染。
+ */
+ publicationTag?: number;
};
export type ProjectResource = {
@@ -506,8 +512,9 @@ export function projectResourcesFromReadModels(
});
}
+ const manifestVersions = manifest.versions ?? [];
const childVersionIdsByParent = new Map();
- for (const version of manifest.versions ?? []) {
+ for (const version of manifestVersions) {
if (!version.parentVersionId) {
continue;
}
@@ -515,12 +522,17 @@ export function projectResourcesFromReadModels(
children.push(version.versionId);
childVersionIdsByParent.set(version.parentVersionId, children);
}
- for (const [index, manifestVersion] of (manifest.versions ?? []).entries()) {
+ for (const [index, manifestVersion] of manifestVersions.entries()) {
const version: ProjectVersionResourceSummary = {
...manifestVersion,
label: `版本 ${index + 1}`,
childVersionIds:
childVersionIdsByParent.get(manifestVersion.versionId) ?? [],
+ // 最新内部版本 = 数组最后一条 = 发布面板取的标签(`versions.length`)。
+ // 只有它带 `publicationTag`:卡面据此渲染「最新 · 发布标签 v{N}」角标。
+ ...(index === manifestVersions.length - 1
+ ? { publicationTag: index + 1 }
+ : {}),
};
resources.push({
id: `version:${version.versionId}`,
diff --git a/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx b/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx
index 9dfa1ab29..63de6614a 100644
--- a/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx
+++ b/apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx
@@ -752,7 +752,7 @@ describe('GameDistributionPublishPanel', () => {
expect(versionInput).toHaveProperty('readOnly', true);
expect(screen.getByText('v1')).not.toBeNull();
expect(screen.getByText(/线上最近提交 v2 · 审核中/u)).not.toBeNull();
- expect(screen.getByText(/来自 AGC 内部版本(版本 1)/u)).not.toBeNull();
+ expect(screen.getByText(/本次发布按首版 v1。/u)).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '更新游戏' }));
await waitFor(() =>
@@ -839,7 +839,11 @@ describe('GameDistributionPublishPanel', () => {
expect(input).toHaveProperty('value', '4');
expect(input).toHaveProperty('readOnly', true);
expect(screen.getByText('v4')).not.toBeNull();
- expect(screen.getByText(/来自 AGC 内部版本(版本 4)/u)).not.toBeNull();
+ expect(
+ screen.getByText(
+ /本次发布标签 = 最新内部版本 v4;agent 再改进一次后,下一次发布将是 v5。/u,
+ ),
+ ).not.toBeNull();
expect(screen.getByText(/线上最近提交 v5 · 已公开/u)).not.toBeNull();
await selectCover();
fireEvent.click(screen.getByRole('button', { name: '更新游戏' }));
diff --git a/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts b/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
index 4a38324a9..35d7f8b48 100644
--- a/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
+++ b/apps/ai-game-creator-shell/tests/projectResourceProjectionModel.test.ts
@@ -293,6 +293,10 @@ describe('项目资源投影', () => {
]);
expect(versions[0]?.version?.childVersionIds).toEqual(['version-child']);
expect(versions[1]?.version?.parentVersionId).toBe('version-root');
+ // 只有最新内部版本(数组最后一条)带发布标签:它等于 `versions.length`,
+ // 也就是发布面板「项目版本」取的同一个数;角标只长在这一张卡上。
+ expect(versions[0]?.version?.publicationTag).toBeUndefined();
+ expect(versions[1]?.version?.publicationTag).toBe(2);
});
it('按稳定优先级推导资源卡类型标识并为未知类型兜底', () => {