Enforce Genarrative play-type SOP and update docs

Rewrite Genarrative play-type integration guidance across .codex and .hermes to define a platform-level SOP: default to form/image workbench, unify single-image asset slots (CreativeImageInputPanel), standardize series-material sheet->cut->transparent->OSS pipeline, and forbid copying legacy chat/agent workflows as the default. Add decision-log entry freezing the SOP and a pitfalls note warning against direct reuse of old play tools. Update CONTEXT.md and docs/README.md, add a new PRD file, and apply related small server-side changes (module-auth, spacetime-client mappers and runtime) to align back-end code with the new contracts and flows.
This commit is contained in:
2026-05-20 12:12:00 +08:00
parent f370539a6f
commit 3931442249
123 changed files with 15514 additions and 3419 deletions

View File

@@ -0,0 +1,274 @@
import type {
JumpHopActionRequest,
JumpHopActionResponse,
JumpHopDraftResponse,
JumpHopGalleryCardResponse,
JumpHopGalleryDetailResponse,
JumpHopGalleryResponse,
JumpHopRunResponse,
JumpHopRuntimeRunSnapshotResponse,
JumpHopSessionResponse,
JumpHopSessionSnapshotResponse,
JumpHopWorkDetailResponse,
JumpHopWorkMutationResponse,
JumpHopWorkProfileResponse,
JumpHopWorkspaceCreateRequest,
JumpHopWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/jumpHop';
import { type ApiRetryOptions, requestJson } from '../apiClient';
import { createCreationAgentClient } from '../creation-agent';
const JUMP_HOP_API_BASE = '/api/creation/jump-hop/sessions';
const JUMP_HOP_WORKS_API_BASE = '/api/creation/jump-hop/works';
const JUMP_HOP_RUNTIME_API_BASE = '/api/runtime/jump-hop';
const JUMP_HOP_RUNTIME_READ_RETRY: ApiRetryOptions = {
maxRetries: 1,
baseDelayMs: 120,
maxDelayMs: 360,
};
export type {
JumpHopActionRequest,
JumpHopActionResponse,
JumpHopDraftResponse,
JumpHopGalleryCardResponse,
JumpHopGalleryDetailResponse,
JumpHopGalleryResponse,
JumpHopRunResponse,
JumpHopRuntimeRunSnapshotResponse,
JumpHopSessionResponse,
JumpHopSessionSnapshotResponse,
JumpHopWorkDetailResponse,
JumpHopWorkMutationResponse,
JumpHopWorkProfileResponse,
JumpHopWorkspaceCreateRequest,
};
export type CreateJumpHopSessionRequest = {
themeText: string;
characterDescription: string;
tileStyle: string;
difficulty: string;
rhythmPreference: string;
};
export type ExecuteJumpHopActionRequest = JumpHopActionRequest;
export type JumpHopSessionSnapshot = JumpHopSessionSnapshotResponse;
const jumpHopCreationClient = createCreationAgentClient<
JumpHopWorkspaceCreateRequest,
JumpHopSessionResponse,
JumpHopSessionResponse,
JumpHopSessionSnapshotResponse,
never,
never,
JumpHopActionRequest,
JumpHopActionResponse
>({
apiBase: JUMP_HOP_API_BASE,
messages: {
createSession: '创建跳一跳共创会话失败',
getSession: '读取跳一跳共创会话失败',
sendMessage: '发送跳一跳共创消息失败',
streamIncomplete: '跳一跳共创消息流式结果不完整',
executeAction: '执行跳一跳共创操作失败',
},
});
type FlattenedJumpHopWorkProfileResponse = Omit<
JumpHopWorkProfileResponse,
'summary'
> &
JumpHopWorkSummaryResponse;
function normalizeJumpHopWorkProfile(
work: JumpHopWorkProfileResponse | FlattenedJumpHopWorkProfileResponse,
): JumpHopWorkProfileResponse {
if ('summary' in work && work.summary) {
return work;
}
const flattened = work as FlattenedJumpHopWorkProfileResponse;
const summary: JumpHopWorkProfileResponse['summary'] = {
runtimeKind: flattened.runtimeKind,
workId: flattened.workId,
profileId: flattened.profileId,
ownerUserId: flattened.ownerUserId,
sourceSessionId: flattened.sourceSessionId ?? null,
workTitle: flattened.workTitle,
workDescription: flattened.workDescription,
themeTags: flattened.themeTags,
difficulty: flattened.difficulty,
stylePreset: flattened.stylePreset,
coverImageSrc: flattened.coverImageSrc ?? null,
publicationStatus: flattened.publicationStatus,
playCount: flattened.playCount,
updatedAt: flattened.updatedAt,
publishedAt: flattened.publishedAt ?? null,
publishReady: flattened.publishReady,
generationStatus: flattened.generationStatus,
};
return {
summary,
draft: flattened.draft,
path: flattened.path,
characterAsset: flattened.characterAsset,
tileAtlasAsset: flattened.tileAtlasAsset,
tileAssets: flattened.tileAssets,
};
}
function normalizeJumpHopActionResponse(
response: JumpHopActionResponse,
): JumpHopActionResponse {
return {
...response,
work: response.work ? normalizeJumpHopWorkProfile(response.work) : null,
};
}
function normalizeJumpHopWorkDetailResponse(
response: JumpHopWorkDetailResponse,
): JumpHopWorkDetailResponse {
return {
...response,
item: normalizeJumpHopWorkProfile(response.item),
};
}
function normalizeJumpHopWorkMutationResponse(
response: JumpHopWorkMutationResponse,
): JumpHopWorkMutationResponse {
return {
...response,
item: normalizeJumpHopWorkProfile(response.item),
};
}
export function createJumpHopCreationSession(
payload: JumpHopWorkspaceCreateRequest,
) {
return jumpHopCreationClient.createSession(payload);
}
export function getJumpHopCreationSession(sessionId: string) {
return jumpHopCreationClient.getSession(sessionId);
}
export function executeJumpHopCreationAction(
sessionId: string,
payload: JumpHopActionRequest,
) {
return jumpHopCreationClient
.executeAction(sessionId, payload)
.then(normalizeJumpHopActionResponse);
}
export async function getJumpHopWorkDetail(profileId: string) {
const response = await requestJson<JumpHopWorkDetailResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}`,
{ method: 'GET' },
'读取跳一跳作品详情失败',
);
return normalizeJumpHopWorkDetailResponse(response);
}
export async function listJumpHopGallery() {
return requestJson<JumpHopGalleryResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/gallery`,
{ method: 'GET' },
'读取跳一跳广场失败',
{
retry: JUMP_HOP_RUNTIME_READ_RETRY,
skipAuth: true,
skipRefresh: true,
},
);
}
export async function getJumpHopGalleryDetail(publicWorkCode: string) {
const response = await requestJson<JumpHopGalleryDetailResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/gallery/${encodeURIComponent(publicWorkCode)}`,
{ method: 'GET' },
'读取跳一跳广场详情失败',
{
retry: JUMP_HOP_RUNTIME_READ_RETRY,
skipAuth: true,
skipRefresh: true,
},
);
return normalizeJumpHopWorkDetailResponse(response);
}
export async function publishJumpHopWork(profileId: string) {
const response = await requestJson<JumpHopWorkMutationResponse>(
`${JUMP_HOP_WORKS_API_BASE}/${encodeURIComponent(profileId)}/publish`,
{ method: 'POST' },
'发布跳一跳作品失败',
);
return normalizeJumpHopWorkMutationResponse(response);
}
export async function startJumpHopRuntimeRun(profileId: string) {
return requestJson<JumpHopRunResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/runs`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({ profileId }),
},
'启动跳一跳运行态失败',
);
}
export async function submitJumpHopJump(
runId: string,
payload: { chargeMs: number },
) {
const requestPayload = {
chargeMs: payload.chargeMs,
clientEventId: `jump-${runId}-${Date.now()}`,
};
return requestJson<JumpHopRunResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/jump`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(requestPayload),
},
'提交跳一跳起跳失败',
);
}
export async function restartJumpHopRuntimeRun(runId: string) {
return requestJson<JumpHopRunResponse>(
`${JUMP_HOP_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/restart`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
clientActionId: `restart-${runId}-${Date.now()}`,
}),
},
'重新开始跳一跳失败',
);
}
export const jumpHopClient = {
createSession: createJumpHopCreationSession,
getSession: getJumpHopCreationSession,
executeAction: executeJumpHopCreationAction,
getGalleryDetail: getJumpHopGalleryDetail,
getWorkDetail: getJumpHopWorkDetail,
listGallery: listJumpHopGallery,
publishWork: publishJumpHopWork,
restartRun: restartJumpHopRuntimeRun,
startRun: startJumpHopRuntimeRun,
submitJump: submitJumpHopJump,
};