Merge branch 'codex/dev' into codex/backend-rewrite-spacetimedb
# Conflicts: # docs/technical/README.md # server-node/src/modules/assets/qwenSpriteRoutes.ts # src/components/CustomWorldResultView.test.tsx # src/components/CustomWorldResultView.tsx # src/components/custom-world-agent/CustomWorldAgentDraftDetailPanel.tsx # src/components/game-shell/PreGameSelectionFlow.agent.interaction.test.tsx # src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx # src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx # src/components/rpg-entry/RpgEntryCharacterSelectView.tsx # src/components/rpg-entry/RpgEntryHomeView.tsx # src/services/apiClient.ts # src/tools/QwenSpriteSheetTool.tsx
This commit is contained in:
@@ -78,7 +78,8 @@ export type AuthPhoneChangeResponse = {
|
||||
};
|
||||
|
||||
export type AuthRefreshResponse = {
|
||||
token: string;
|
||||
ok: true;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
export type AuthSessionSummary = {
|
||||
|
||||
@@ -1,533 +1,12 @@
|
||||
export type CustomWorldWorkStatus = 'draft' | 'published';
|
||||
export type CustomWorldWorkSource = 'agent_session' | 'published_profile';
|
||||
/**
|
||||
* 兼容出口:
|
||||
* 当前仓库仍有大量旧 customWorld 命名导入,这个文件继续作为过渡层保留。
|
||||
* 工作包 H 完成后,真实类型定义已经迁移到 rpg* 契约文件中;这里仅聚合旧命名分文件。
|
||||
*/
|
||||
|
||||
export interface WorldPromiseValue {
|
||||
hook: string;
|
||||
differentiator: string;
|
||||
desiredExperience: string;
|
||||
}
|
||||
|
||||
export interface PlayerFantasyValue {
|
||||
playerRole: string;
|
||||
corePursuit: string;
|
||||
fearOfLoss: string;
|
||||
}
|
||||
|
||||
export interface ThemeBoundaryValue {
|
||||
toneKeywords: string[];
|
||||
aestheticDirectives: string[];
|
||||
forbiddenDirectives: string[];
|
||||
}
|
||||
|
||||
export interface PlayerEntryPointValue {
|
||||
openingIdentity: string;
|
||||
openingProblem: string;
|
||||
entryMotivation: string;
|
||||
}
|
||||
|
||||
export interface CoreConflictValue {
|
||||
surfaceConflicts: string[];
|
||||
hiddenCrisis: string;
|
||||
firstTouchedConflict: string;
|
||||
}
|
||||
|
||||
export interface KeyRelationshipValue {
|
||||
pairs: string;
|
||||
relationshipType: string;
|
||||
secretOrCost: string;
|
||||
}
|
||||
|
||||
export interface HiddenLineValue {
|
||||
hiddenTruths: string[];
|
||||
misdirectionHints: string[];
|
||||
revealPacing: string;
|
||||
}
|
||||
|
||||
export interface IconicElementValue {
|
||||
iconicMotifs: string[];
|
||||
institutionsOrArtifacts: string[];
|
||||
hardRules: string[];
|
||||
}
|
||||
|
||||
export interface EightAnchorContent {
|
||||
worldPromise: WorldPromiseValue | null;
|
||||
playerFantasy: PlayerFantasyValue | null;
|
||||
themeBoundary: ThemeBoundaryValue | null;
|
||||
playerEntryPoint: PlayerEntryPointValue | null;
|
||||
coreConflict: CoreConflictValue | null;
|
||||
keyRelationships: KeyRelationshipValue[];
|
||||
hiddenLines: HiddenLineValue | null;
|
||||
iconicElements: IconicElementValue | null;
|
||||
}
|
||||
|
||||
export interface CustomWorldWorkSummary {
|
||||
workId: string;
|
||||
sourceType: CustomWorldWorkSource;
|
||||
status: CustomWorldWorkStatus;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
summary: string;
|
||||
coverImageSrc?: string | null;
|
||||
coverRenderMode?: 'image' | 'scene_with_roles';
|
||||
coverCharacterImageSrcs?: string[];
|
||||
updatedAt: string;
|
||||
publishedAt?: string | null;
|
||||
stage?: string | null;
|
||||
stageLabel?: string | null;
|
||||
playableNpcCount: number;
|
||||
landmarkCount: number;
|
||||
roleVisualReadyCount?: number;
|
||||
roleAnimationReadyCount?: number;
|
||||
roleAssetSummaryLabel?: string | null;
|
||||
sessionId?: string | null;
|
||||
profileId?: string | null;
|
||||
canResume: boolean;
|
||||
canEnterWorld: boolean;
|
||||
}
|
||||
|
||||
export interface CreatorIntentReadiness {
|
||||
isReady: boolean;
|
||||
completedKeys: string[];
|
||||
missingKeys: string[];
|
||||
}
|
||||
|
||||
export interface CustomWorldPendingClarification {
|
||||
id: string;
|
||||
label: string;
|
||||
question: string;
|
||||
targetKey:
|
||||
| 'world_hook'
|
||||
| 'player_premise'
|
||||
| 'theme_and_tone'
|
||||
| 'core_conflict'
|
||||
| 'relationship_seed'
|
||||
| 'iconic_element';
|
||||
priority: number;
|
||||
answer?: string;
|
||||
}
|
||||
|
||||
export type CustomWorldAgentStage =
|
||||
| 'collecting_intent'
|
||||
| 'clarifying'
|
||||
| 'foundation_review'
|
||||
| 'object_refining'
|
||||
| 'visual_refining'
|
||||
| 'long_tail_review'
|
||||
| 'ready_to_publish'
|
||||
| 'published'
|
||||
| 'error';
|
||||
|
||||
export type CustomWorldAgentMessageRole = 'user' | 'assistant' | 'system';
|
||||
|
||||
export type CustomWorldAgentMessageKind =
|
||||
| 'chat'
|
||||
| 'clarification'
|
||||
| 'summary'
|
||||
| 'checkpoint'
|
||||
| 'warning'
|
||||
| 'action_result';
|
||||
|
||||
export interface CustomWorldAgentMessage {
|
||||
id: string;
|
||||
role: CustomWorldAgentMessageRole;
|
||||
kind: CustomWorldAgentMessageKind;
|
||||
text: string;
|
||||
createdAt: string;
|
||||
relatedOperationId?: string | null;
|
||||
}
|
||||
|
||||
export type CustomWorldDraftCardKind =
|
||||
| 'world'
|
||||
| 'camp'
|
||||
| 'faction'
|
||||
| 'character'
|
||||
| 'landmark'
|
||||
| 'thread'
|
||||
| 'chapter'
|
||||
| 'scene_chapter'
|
||||
| 'carrier'
|
||||
| 'sidequest_seed';
|
||||
|
||||
export type CustomWorldDraftCardStatus =
|
||||
| 'suggested'
|
||||
| 'confirmed'
|
||||
| 'locked'
|
||||
| 'warning';
|
||||
|
||||
export interface CustomWorldDraftCardSummary {
|
||||
id: string;
|
||||
kind: CustomWorldDraftCardKind;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
summary: string;
|
||||
status: CustomWorldDraftCardStatus;
|
||||
linkedIds: string[];
|
||||
warningCount: number;
|
||||
assetStatus?: CustomWorldRoleAssetStatus | null;
|
||||
assetStatusLabel?: string | null;
|
||||
}
|
||||
|
||||
export interface CustomWorldDraftCardDetailSection {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftFaction {
|
||||
id: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
publicGoal: string;
|
||||
relatedConflict: string;
|
||||
tension?: string;
|
||||
playerRelation: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftCharacter {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
role: string;
|
||||
publicIdentity: string;
|
||||
publicMask?: string;
|
||||
currentPressure: string;
|
||||
hiddenHook?: string;
|
||||
relationToPlayer: string;
|
||||
threadIds: string[];
|
||||
summary: string;
|
||||
skills?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
actionPreviewConfig?: Record<string, unknown> | null;
|
||||
}>;
|
||||
imageSrc?: string | null;
|
||||
generatedVisualAssetId?: string | null;
|
||||
generatedAnimationSetId?: string | null;
|
||||
animationMap?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftLandmark {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
purpose: string;
|
||||
mood: string;
|
||||
importance: string;
|
||||
secret?: string;
|
||||
dangerLevel?: string;
|
||||
imageSrc?: string | null;
|
||||
characterIds: string[];
|
||||
threadIds: string[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftThread {
|
||||
id: string;
|
||||
title: string;
|
||||
type: 'main' | 'hidden';
|
||||
conflictType?: string;
|
||||
conflict: string;
|
||||
stakes?: string;
|
||||
characterIds: string[];
|
||||
landmarkIds: string[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftChapter {
|
||||
id: string;
|
||||
title: string;
|
||||
openingEvent: string;
|
||||
playerGoal: string;
|
||||
characterIds: string[];
|
||||
landmarkIds: string[];
|
||||
understandingShift: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftCamp {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
mood: string;
|
||||
dangerLevel?: string;
|
||||
imageSrc?: string | null;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export type CustomWorldSceneActStage =
|
||||
| 'opening'
|
||||
| 'expansion'
|
||||
| 'turning_point'
|
||||
| 'climax'
|
||||
| 'aftermath';
|
||||
|
||||
export type CustomWorldSceneActAdvanceRule =
|
||||
| 'after_primary_contact'
|
||||
| 'after_active_step_complete'
|
||||
| 'after_chapter_resolution';
|
||||
|
||||
export interface CustomWorldFoundationDraftSceneAct {
|
||||
id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
stageCoverage: CustomWorldSceneActStage[];
|
||||
backgroundImageSrc?: string | null;
|
||||
backgroundAssetId?: string | null;
|
||||
encounterNpcIds: string[];
|
||||
primaryNpcId: string;
|
||||
linkedThreadIds: string[];
|
||||
actGoal: string;
|
||||
transitionHook: string;
|
||||
advanceRule: CustomWorldSceneActAdvanceRule;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftSceneChapter {
|
||||
id: string;
|
||||
sceneId: string;
|
||||
sceneName: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
linkedThreadIds: string[];
|
||||
linkedLandmarkIds: string[];
|
||||
acts: CustomWorldFoundationDraftSceneAct[];
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftProfile {
|
||||
name: string;
|
||||
subtitle: string;
|
||||
summary: string;
|
||||
tone: string;
|
||||
playerGoal: string;
|
||||
majorFactions: string[];
|
||||
coreConflicts: string[];
|
||||
playableNpcs: CustomWorldFoundationDraftCharacter[];
|
||||
storyNpcs: CustomWorldFoundationDraftCharacter[];
|
||||
landmarks: CustomWorldFoundationDraftLandmark[];
|
||||
camp?: CustomWorldFoundationDraftCamp | null;
|
||||
themePack?: Record<string, unknown> | null;
|
||||
storyGraph?: Record<string, unknown> | null;
|
||||
factions: CustomWorldFoundationDraftFaction[];
|
||||
threads: CustomWorldFoundationDraftThread[];
|
||||
chapters: CustomWorldFoundationDraftChapter[];
|
||||
sceneChapters: CustomWorldFoundationDraftSceneChapter[];
|
||||
worldHook: string;
|
||||
playerPremise: string;
|
||||
openingSituation: string;
|
||||
iconicElements: string[];
|
||||
sourceAnchorSummary: string;
|
||||
}
|
||||
|
||||
export interface CustomWorldFoundationDraftResult {
|
||||
draftProfile: CustomWorldFoundationDraftProfile;
|
||||
draftCards: CustomWorldDraftCardSummary[];
|
||||
}
|
||||
|
||||
export interface CustomWorldDraftCardDetail {
|
||||
id: string;
|
||||
kind: CustomWorldDraftCardKind;
|
||||
title: string;
|
||||
sections: CustomWorldDraftCardDetailSection[];
|
||||
linkedIds: string[];
|
||||
locked: false;
|
||||
editable: boolean;
|
||||
editableSectionIds: string[];
|
||||
warningMessages: string[];
|
||||
assetStatus?: CustomWorldRoleAssetStatus | null;
|
||||
assetStatusLabel?: string | null;
|
||||
}
|
||||
|
||||
export interface CustomWorldSuggestedAction {
|
||||
id: string;
|
||||
type:
|
||||
| 'request_summary'
|
||||
| 'draft_foundation'
|
||||
| 'refine_focus_target'
|
||||
| 'lock_current_target'
|
||||
| 'generate_role_assets'
|
||||
| 'generate_scene_assets'
|
||||
| 'expand_long_tail'
|
||||
| 'publish_world';
|
||||
label: string;
|
||||
targetId?: string | null;
|
||||
}
|
||||
|
||||
export type CustomWorldAssetPriorityTier = 'hero' | 'featured' | 'supporting';
|
||||
|
||||
export type CustomWorldRoleAssetStatus =
|
||||
| 'missing'
|
||||
| 'visual_ready'
|
||||
| 'animations_ready'
|
||||
| 'complete';
|
||||
|
||||
export interface CustomWorldRoleAssetSummary {
|
||||
roleId: string;
|
||||
roleName: string;
|
||||
roleKind: 'playable' | 'story';
|
||||
priorityTier: CustomWorldAssetPriorityTier;
|
||||
portraitPath?: string | null;
|
||||
generatedVisualAssetId?: string | null;
|
||||
generatedAnimationSetId?: string | null;
|
||||
status: CustomWorldRoleAssetStatus;
|
||||
missingAnimations: string[];
|
||||
nextPointCost: number;
|
||||
}
|
||||
|
||||
export interface CustomWorldSceneAssetSummary {
|
||||
sceneId: string;
|
||||
sceneName: string;
|
||||
actId?: string | null;
|
||||
actTitle?: string | null;
|
||||
imageSrc?: string | null;
|
||||
assetId?: string | null;
|
||||
status: 'missing' | 'ready';
|
||||
nextPointCost: number;
|
||||
}
|
||||
|
||||
export interface CustomWorldAssetCoverageSummary {
|
||||
roleAssets: CustomWorldRoleAssetSummary[];
|
||||
sceneAssets: CustomWorldSceneAssetSummary[];
|
||||
allRoleAssetsReady: boolean;
|
||||
allSceneAssetsReady: boolean;
|
||||
}
|
||||
|
||||
export interface CustomWorldAgentSessionSnapshot {
|
||||
sessionId: string;
|
||||
currentTurn: number;
|
||||
anchorContent: EightAnchorContent;
|
||||
progressPercent: number;
|
||||
lastAssistantReply: string | null;
|
||||
stage: CustomWorldAgentStage;
|
||||
focusCardId: string | null;
|
||||
creatorIntent: Record<string, unknown> | null;
|
||||
creatorIntentReadiness: CreatorIntentReadiness;
|
||||
anchorPack: Record<string, unknown> | null;
|
||||
lockState: Record<string, unknown> | null;
|
||||
draftProfile: Record<string, unknown> | null;
|
||||
messages: CustomWorldAgentMessage[];
|
||||
draftCards: CustomWorldDraftCardSummary[];
|
||||
pendingClarifications: CustomWorldPendingClarification[];
|
||||
suggestedActions: CustomWorldSuggestedAction[];
|
||||
recommendedReplies: string[];
|
||||
qualityFindings: {
|
||||
id: string;
|
||||
severity: 'info' | 'warning' | 'blocker';
|
||||
code: string;
|
||||
targetId?: string | null;
|
||||
message: string;
|
||||
}[];
|
||||
assetCoverage: CustomWorldAssetCoverageSummary;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type CustomWorldAgentOperationType =
|
||||
| 'process_message'
|
||||
| 'lock_cards'
|
||||
| 'unlock_cards'
|
||||
| 'regenerate_scope'
|
||||
| 'draft_foundation'
|
||||
| 'update_draft_card'
|
||||
| 'generate_characters'
|
||||
| 'generate_landmarks'
|
||||
| 'generate_role_assets'
|
||||
| 'sync_role_assets'
|
||||
| 'generate_scene_assets'
|
||||
| 'sync_scene_assets'
|
||||
| 'expand_long_tail'
|
||||
| 'publish_world'
|
||||
| 'revert_checkpoint';
|
||||
|
||||
export type CustomWorldAgentOperationStatus =
|
||||
| 'queued'
|
||||
| 'running'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
|
||||
export interface CustomWorldAgentOperationRecord {
|
||||
operationId: string;
|
||||
type: CustomWorldAgentOperationType;
|
||||
status: CustomWorldAgentOperationStatus;
|
||||
phaseLabel: string;
|
||||
phaseDetail: string;
|
||||
progress: number;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateCustomWorldAgentSessionRequest {
|
||||
seedText?: string;
|
||||
}
|
||||
|
||||
export interface CreateCustomWorldAgentSessionResponse {
|
||||
session: CustomWorldAgentSessionSnapshot;
|
||||
}
|
||||
|
||||
export interface SendCustomWorldAgentMessageRequest {
|
||||
clientMessageId: string;
|
||||
text: string;
|
||||
quickFillRequested?: boolean;
|
||||
focusCardId?: string | null;
|
||||
selectedCardIds?: string[];
|
||||
}
|
||||
|
||||
export interface SendCustomWorldAgentMessageResponse {
|
||||
operation: CustomWorldAgentOperationRecord;
|
||||
}
|
||||
|
||||
export type CustomWorldAgentActionRequest =
|
||||
| { action: 'lock_cards'; cardIds: string[] }
|
||||
| { action: 'unlock_cards'; cardIds: string[] }
|
||||
| {
|
||||
action: 'regenerate_scope';
|
||||
scope:
|
||||
| 'focus_card'
|
||||
| 'long_tail_npcs'
|
||||
| 'long_tail_landmarks'
|
||||
| 'sidequest_seeds'
|
||||
| 'role_assets'
|
||||
| 'scene_assets';
|
||||
targetCardId?: string | null;
|
||||
}
|
||||
| { action: 'draft_foundation' }
|
||||
| {
|
||||
action: 'update_draft_card';
|
||||
cardId: string;
|
||||
sections: Array<{
|
||||
sectionId: string;
|
||||
value: string;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
action: 'generate_characters';
|
||||
count: number;
|
||||
promptText?: string | null;
|
||||
anchorCardIds?: string[];
|
||||
}
|
||||
| {
|
||||
action: 'generate_landmarks';
|
||||
count: number;
|
||||
promptText?: string | null;
|
||||
anchorCardIds?: string[];
|
||||
}
|
||||
| { action: 'generate_role_assets'; roleIds: string[] }
|
||||
| {
|
||||
action: 'sync_role_assets';
|
||||
roleId: string;
|
||||
portraitPath: string;
|
||||
generatedVisualAssetId: string;
|
||||
generatedAnimationSetId?: string | null;
|
||||
animationMap?: Record<string, unknown> | null;
|
||||
}
|
||||
| { action: 'publish_world' };
|
||||
|
||||
export interface CustomWorldAgentActionResponse {
|
||||
operation: CustomWorldAgentOperationRecord;
|
||||
}
|
||||
|
||||
export interface GetCustomWorldAgentCardDetailResponse {
|
||||
card: CustomWorldDraftCardDetail;
|
||||
}
|
||||
|
||||
export interface ListCustomWorldWorksResponse {
|
||||
items: CustomWorldWorkSummary[];
|
||||
}
|
||||
export type * from './customWorldAgentAnchors';
|
||||
export type * from './customWorldAgentDraft';
|
||||
export type * from './customWorldAgentActions';
|
||||
export type * from './customWorldAgentSession';
|
||||
export type * from './customWorldResultPreview';
|
||||
export type * from './customWorldWorkSummary';
|
||||
|
||||
14
packages/shared/src/contracts/customWorldAgentActions.ts
Normal file
14
packages/shared/src/contracts/customWorldAgentActions.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 旧 custom world 动作契约兼容出口。
|
||||
* 后续若逐步迁移旧代码,建议直接改用 rpgAgentActions.ts。
|
||||
*/
|
||||
|
||||
export type {
|
||||
RpgAgentActionRequest as CustomWorldAgentActionRequest,
|
||||
RpgAgentActionResponse as CustomWorldAgentActionResponse,
|
||||
RpgAgentOperationRecord as CustomWorldAgentOperationRecord,
|
||||
RpgAgentOperationStatus as CustomWorldAgentOperationStatus,
|
||||
RpgAgentOperationType as CustomWorldAgentOperationType,
|
||||
RpgAgentSupportedAction as CustomWorldSupportedAction,
|
||||
RpgAgentSuggestedAction as CustomWorldSuggestedAction,
|
||||
} from './rpgAgentActions';
|
||||
16
packages/shared/src/contracts/customWorldAgentAnchors.ts
Normal file
16
packages/shared/src/contracts/customWorldAgentAnchors.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 旧 custom world 八锚点兼容出口。
|
||||
* 这里只保留旧命名到 RPG 创作域新契约的映射,便于旧导入渐进迁移。
|
||||
*/
|
||||
|
||||
export type {
|
||||
RpgCreationAnchorContent as EightAnchorContent,
|
||||
RpgCreationCoreConflictValue as CoreConflictValue,
|
||||
RpgCreationHiddenLineValue as HiddenLineValue,
|
||||
RpgCreationIconicElementValue as IconicElementValue,
|
||||
RpgCreationKeyRelationshipValue as KeyRelationshipValue,
|
||||
RpgCreationPlayerEntryPointValue as PlayerEntryPointValue,
|
||||
RpgCreationPlayerFantasyValue as PlayerFantasyValue,
|
||||
RpgCreationThemeBoundaryValue as ThemeBoundaryValue,
|
||||
RpgCreationWorldPromiseValue as WorldPromiseValue,
|
||||
} from './rpgAgentAnchors';
|
||||
29
packages/shared/src/contracts/customWorldAgentDraft.ts
Normal file
29
packages/shared/src/contracts/customWorldAgentDraft.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 旧 custom world 草稿契约兼容出口。
|
||||
* 工作包 H 完成后,真实定义已经迁到 rpgAgentDraft.ts,这里只负责旧命名映射。
|
||||
*/
|
||||
|
||||
export type {
|
||||
RpgAgentAssetCoverageSummary as CustomWorldAssetCoverageSummary,
|
||||
RpgAgentAssetPriorityTier as CustomWorldAssetPriorityTier,
|
||||
RpgAgentDraftCardDetail as CustomWorldDraftCardDetail,
|
||||
RpgAgentDraftCardDetailSection as CustomWorldDraftCardDetailSection,
|
||||
RpgAgentDraftCardKind as CustomWorldDraftCardKind,
|
||||
RpgAgentDraftCardStatus as CustomWorldDraftCardStatus,
|
||||
RpgAgentDraftCardSummary as CustomWorldDraftCardSummary,
|
||||
RpgAgentFoundationDraftCamp as CustomWorldFoundationDraftCamp,
|
||||
RpgAgentFoundationDraftChapter as CustomWorldFoundationDraftChapter,
|
||||
RpgAgentFoundationDraftCharacter as CustomWorldFoundationDraftCharacter,
|
||||
RpgAgentFoundationDraftFaction as CustomWorldFoundationDraftFaction,
|
||||
RpgAgentFoundationDraftLandmark as CustomWorldFoundationDraftLandmark,
|
||||
RpgAgentFoundationDraftProfile as CustomWorldFoundationDraftProfile,
|
||||
RpgAgentFoundationDraftResult as CustomWorldFoundationDraftResult,
|
||||
RpgAgentFoundationDraftSceneAct as CustomWorldFoundationDraftSceneAct,
|
||||
RpgAgentFoundationDraftSceneChapter as CustomWorldFoundationDraftSceneChapter,
|
||||
RpgAgentFoundationDraftThread as CustomWorldFoundationDraftThread,
|
||||
RpgAgentRoleAssetStatus as CustomWorldRoleAssetStatus,
|
||||
RpgAgentRoleAssetSummary as CustomWorldRoleAssetSummary,
|
||||
RpgAgentSceneActAdvanceRule as CustomWorldSceneActAdvanceRule,
|
||||
RpgAgentSceneActStage as CustomWorldSceneActStage,
|
||||
RpgAgentSceneAssetSummary as CustomWorldSceneAssetSummary,
|
||||
} from './rpgAgentDraft';
|
||||
20
packages/shared/src/contracts/customWorldAgentSession.ts
Normal file
20
packages/shared/src/contracts/customWorldAgentSession.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 旧 custom world 会话契约兼容出口。
|
||||
* 这一层只做命名映射,不再承担 session 真相源结构定义。
|
||||
*/
|
||||
|
||||
export type {
|
||||
CreateRpgAgentSessionRequest as CreateCustomWorldAgentSessionRequest,
|
||||
CreateRpgAgentSessionResponse as CreateCustomWorldAgentSessionResponse,
|
||||
GetRpgAgentCardDetailResponse as GetCustomWorldAgentCardDetailResponse,
|
||||
RpgAgentMessage as CustomWorldAgentMessage,
|
||||
RpgAgentMessageKind as CustomWorldAgentMessageKind,
|
||||
RpgAgentMessageRole as CustomWorldAgentMessageRole,
|
||||
RpgAgentPendingClarification as CustomWorldPendingClarification,
|
||||
RpgAgentQualityFinding as CustomWorldAgentQualityFinding,
|
||||
RpgAgentSessionSnapshot as CustomWorldAgentSessionSnapshot,
|
||||
RpgAgentStage as CustomWorldAgentStage,
|
||||
RpgCreationIntentReadiness as CreatorIntentReadiness,
|
||||
SendRpgAgentMessageRequest as SendCustomWorldAgentMessageRequest,
|
||||
SendRpgAgentMessageResponse as SendCustomWorldAgentMessageResponse,
|
||||
} from './rpgAgentSession';
|
||||
12
packages/shared/src/contracts/customWorldResultPreview.ts
Normal file
12
packages/shared/src/contracts/customWorldResultPreview.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 旧 custom world 结果页预览兼容出口。
|
||||
* 额外单独拆一个 preview 兼容文件,避免预览别名继续堆回 customWorldAgent.ts 聚合层。
|
||||
*/
|
||||
|
||||
export type {
|
||||
RpgCreationPreview as CustomWorldResultPreview,
|
||||
RpgCreationPreviewBlocker as CustomWorldResultPreviewBlocker,
|
||||
RpgCreationPreviewEnvelope as CustomWorldResultPreviewEnvelope,
|
||||
RpgCreationPreviewFinding as CustomWorldResultPreviewFinding,
|
||||
RpgCreationPreviewSource as CustomWorldResultPreviewSource,
|
||||
} from './rpgCreationPreview';
|
||||
11
packages/shared/src/contracts/customWorldWorkSummary.ts
Normal file
11
packages/shared/src/contracts/customWorldWorkSummary.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 旧 custom world works 读模型兼容出口。
|
||||
* 用于把旧作品列表命名平滑映射到新的 RPG 创作域 works 契约。
|
||||
*/
|
||||
|
||||
export type {
|
||||
ListRpgCreationWorksResponse as ListCustomWorldWorksResponse,
|
||||
RpgCreationWorkSource as CustomWorldWorkSource,
|
||||
RpgCreationWorkStatus as CustomWorldWorkStatus,
|
||||
RpgCreationWorkSummary as CustomWorldWorkSummary,
|
||||
} from './rpgCreationWorkSummary';
|
||||
120
packages/shared/src/contracts/rpgAgentActions.ts
Normal file
120
packages/shared/src/contracts/rpgAgentActions.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* RPG Agent 动作与异步操作契约。
|
||||
* 这里显式区分“建议动作”和“真实可执行动作”,为后续后端 registry 收口预留接口。
|
||||
*/
|
||||
|
||||
export type RpgAgentSuggestedActionType =
|
||||
| 'request_summary'
|
||||
| 'draft_foundation'
|
||||
| 'refine_focus_target'
|
||||
| 'lock_current_target'
|
||||
| 'generate_role_assets'
|
||||
| 'generate_scene_assets'
|
||||
| 'expand_long_tail'
|
||||
| 'publish_world';
|
||||
|
||||
export interface RpgAgentSuggestedAction {
|
||||
id: string;
|
||||
type: RpgAgentSuggestedActionType;
|
||||
label: string;
|
||||
targetId?: string | null;
|
||||
}
|
||||
|
||||
export type RpgAgentActionType =
|
||||
| 'draft_foundation'
|
||||
| 'update_draft_card'
|
||||
| 'sync_result_profile'
|
||||
| 'generate_characters'
|
||||
| 'generate_landmarks'
|
||||
| 'generate_role_assets'
|
||||
| 'sync_role_assets'
|
||||
| 'generate_scene_assets'
|
||||
| 'sync_scene_assets'
|
||||
| 'expand_long_tail'
|
||||
| 'publish_world'
|
||||
| 'revert_checkpoint';
|
||||
|
||||
export type RpgAgentActionCapabilityKey =
|
||||
| RpgAgentSuggestedActionType
|
||||
| RpgAgentActionType;
|
||||
|
||||
/**
|
||||
* 当前先把能力矩阵定义为可选契约。
|
||||
* 等工作包 E 的 registry 落地后,后端可以把真实 supportedActions 填充到 session snapshot。
|
||||
*/
|
||||
export interface RpgAgentSupportedAction {
|
||||
action: RpgAgentActionCapabilityKey;
|
||||
enabled: boolean;
|
||||
reason?: string | null;
|
||||
}
|
||||
|
||||
export type RpgAgentOperationType = RpgAgentActionType | 'process_message';
|
||||
|
||||
export type RpgAgentOperationStatus =
|
||||
| 'queued'
|
||||
| 'running'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
|
||||
export interface RpgAgentOperationRecord {
|
||||
operationId: string;
|
||||
type: RpgAgentOperationType;
|
||||
status: RpgAgentOperationStatus;
|
||||
phaseLabel: string;
|
||||
phaseDetail: string;
|
||||
progress: number;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export type RpgAgentActionRequest =
|
||||
| { action: 'draft_foundation' }
|
||||
| {
|
||||
action: 'update_draft_card';
|
||||
cardId: string;
|
||||
sections: Array<{
|
||||
sectionId: string;
|
||||
value: string;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
action: 'sync_result_profile';
|
||||
profile: Record<string, unknown>;
|
||||
}
|
||||
| {
|
||||
action: 'generate_characters';
|
||||
count: number;
|
||||
promptText?: string | null;
|
||||
anchorCardIds?: string[];
|
||||
}
|
||||
| {
|
||||
action: 'generate_landmarks';
|
||||
count: number;
|
||||
promptText?: string | null;
|
||||
anchorCardIds?: string[];
|
||||
}
|
||||
| { action: 'generate_role_assets'; roleIds: string[] }
|
||||
| {
|
||||
action: 'sync_role_assets';
|
||||
roleId: string;
|
||||
portraitPath: string;
|
||||
generatedVisualAssetId: string;
|
||||
generatedAnimationSetId?: string | null;
|
||||
animationMap?: Record<string, unknown> | null;
|
||||
}
|
||||
| { action: 'generate_scene_assets'; sceneIds: string[] }
|
||||
| {
|
||||
action: 'sync_scene_assets';
|
||||
sceneId: string;
|
||||
sceneKind: 'camp' | 'landmark';
|
||||
imageSrc: string;
|
||||
generatedSceneAssetId: string;
|
||||
generatedScenePrompt?: string | null;
|
||||
generatedSceneModel?: string | null;
|
||||
}
|
||||
| { action: 'expand_long_tail' }
|
||||
| { action: 'publish_world' }
|
||||
| { action: 'revert_checkpoint'; checkpointId: string };
|
||||
|
||||
export interface RpgAgentActionResponse {
|
||||
operation: RpgAgentOperationRecord;
|
||||
}
|
||||
63
packages/shared/src/contracts/rpgAgentAnchors.ts
Normal file
63
packages/shared/src/contracts/rpgAgentAnchors.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* RPG 创作八锚点契约。
|
||||
* 这一层只描述“创作意图采集态”的结构,不混入 session 或结果页字段。
|
||||
*/
|
||||
|
||||
export interface RpgCreationWorldPromiseValue {
|
||||
hook: string;
|
||||
differentiator: string;
|
||||
desiredExperience: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationPlayerFantasyValue {
|
||||
playerRole: string;
|
||||
corePursuit: string;
|
||||
fearOfLoss: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationThemeBoundaryValue {
|
||||
toneKeywords: string[];
|
||||
aestheticDirectives: string[];
|
||||
forbiddenDirectives: string[];
|
||||
}
|
||||
|
||||
export interface RpgCreationPlayerEntryPointValue {
|
||||
openingIdentity: string;
|
||||
openingProblem: string;
|
||||
entryMotivation: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationCoreConflictValue {
|
||||
surfaceConflicts: string[];
|
||||
hiddenCrisis: string;
|
||||
firstTouchedConflict: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationKeyRelationshipValue {
|
||||
pairs: string;
|
||||
relationshipType: string;
|
||||
secretOrCost: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationHiddenLineValue {
|
||||
hiddenTruths: string[];
|
||||
misdirectionHints: string[];
|
||||
revealPacing: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationIconicElementValue {
|
||||
iconicMotifs: string[];
|
||||
institutionsOrArtifacts: string[];
|
||||
hardRules: string[];
|
||||
}
|
||||
|
||||
export interface RpgCreationAnchorContent {
|
||||
worldPromise: RpgCreationWorldPromiseValue | null;
|
||||
playerFantasy: RpgCreationPlayerFantasyValue | null;
|
||||
themeBoundary: RpgCreationThemeBoundaryValue | null;
|
||||
playerEntryPoint: RpgCreationPlayerEntryPointValue | null;
|
||||
coreConflict: RpgCreationCoreConflictValue | null;
|
||||
keyRelationships: RpgCreationKeyRelationshipValue[];
|
||||
hiddenLines: RpgCreationHiddenLineValue | null;
|
||||
iconicElements: RpgCreationIconicElementValue | null;
|
||||
}
|
||||
251
packages/shared/src/contracts/rpgAgentDraft.ts
Normal file
251
packages/shared/src/contracts/rpgAgentDraft.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* RPG Agent 草稿与资产覆盖率契约。
|
||||
* 这一层只描述 foundation draft、草稿卡片与资产状态,不包含会话编排语义。
|
||||
*/
|
||||
|
||||
export type RpgAgentDraftCardKind =
|
||||
| 'world'
|
||||
| 'camp'
|
||||
| 'faction'
|
||||
| 'character'
|
||||
| 'landmark'
|
||||
| 'thread'
|
||||
| 'chapter'
|
||||
| 'scene_chapter'
|
||||
| 'carrier'
|
||||
| 'sidequest_seed';
|
||||
|
||||
export type RpgAgentDraftCardStatus =
|
||||
| 'suggested'
|
||||
| 'confirmed'
|
||||
| 'locked'
|
||||
| 'warning';
|
||||
|
||||
export interface RpgAgentDraftCardSummary {
|
||||
id: string;
|
||||
kind: RpgAgentDraftCardKind;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
summary: string;
|
||||
status: RpgAgentDraftCardStatus;
|
||||
linkedIds: string[];
|
||||
warningCount: number;
|
||||
assetStatus?: RpgAgentRoleAssetStatus | null;
|
||||
assetStatusLabel?: string | null;
|
||||
}
|
||||
|
||||
export interface RpgAgentDraftCardDetailSection {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftFaction {
|
||||
id: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
publicGoal: string;
|
||||
relatedConflict: string;
|
||||
tension?: string;
|
||||
playerRelation: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftCharacter {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
role: string;
|
||||
publicIdentity: string;
|
||||
publicMask?: string;
|
||||
currentPressure: string;
|
||||
hiddenHook?: string;
|
||||
relationToPlayer: string;
|
||||
threadIds: string[];
|
||||
summary: string;
|
||||
skills?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
actionPreviewConfig?: Record<string, unknown> | null;
|
||||
}>;
|
||||
imageSrc?: string | null;
|
||||
generatedVisualAssetId?: string | null;
|
||||
generatedAnimationSetId?: string | null;
|
||||
animationMap?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftLandmark {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
purpose: string;
|
||||
mood: string;
|
||||
importance: string;
|
||||
secret?: string;
|
||||
dangerLevel?: string;
|
||||
imageSrc?: string | null;
|
||||
generatedSceneAssetId?: string | null;
|
||||
generatedScenePrompt?: string | null;
|
||||
generatedSceneModel?: string | null;
|
||||
characterIds: string[];
|
||||
threadIds: string[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftThread {
|
||||
id: string;
|
||||
title: string;
|
||||
type: 'main' | 'hidden';
|
||||
conflictType?: string;
|
||||
conflict: string;
|
||||
stakes?: string;
|
||||
characterIds: string[];
|
||||
landmarkIds: string[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftChapter {
|
||||
id: string;
|
||||
title: string;
|
||||
openingEvent: string;
|
||||
playerGoal: string;
|
||||
characterIds: string[];
|
||||
landmarkIds: string[];
|
||||
understandingShift: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftCamp {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
mood: string;
|
||||
dangerLevel?: string;
|
||||
imageSrc?: string | null;
|
||||
generatedSceneAssetId?: string | null;
|
||||
generatedScenePrompt?: string | null;
|
||||
generatedSceneModel?: string | null;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export type RpgAgentSceneActStage =
|
||||
| 'opening'
|
||||
| 'expansion'
|
||||
| 'turning_point'
|
||||
| 'climax'
|
||||
| 'aftermath';
|
||||
|
||||
export type RpgAgentSceneActAdvanceRule =
|
||||
| 'after_primary_contact'
|
||||
| 'after_active_step_complete'
|
||||
| 'after_chapter_resolution';
|
||||
|
||||
export interface RpgAgentFoundationDraftSceneAct {
|
||||
id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
stageCoverage: RpgAgentSceneActStage[];
|
||||
backgroundImageSrc?: string | null;
|
||||
backgroundAssetId?: string | null;
|
||||
encounterNpcIds: string[];
|
||||
primaryNpcId: string;
|
||||
linkedThreadIds: string[];
|
||||
actGoal: string;
|
||||
transitionHook: string;
|
||||
advanceRule: RpgAgentSceneActAdvanceRule;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftSceneChapter {
|
||||
id: string;
|
||||
sceneId: string;
|
||||
sceneName: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
linkedThreadIds: string[];
|
||||
linkedLandmarkIds: string[];
|
||||
acts: RpgAgentFoundationDraftSceneAct[];
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftProfile {
|
||||
name: string;
|
||||
subtitle: string;
|
||||
summary: string;
|
||||
tone: string;
|
||||
playerGoal: string;
|
||||
majorFactions: string[];
|
||||
coreConflicts: string[];
|
||||
playableNpcs: RpgAgentFoundationDraftCharacter[];
|
||||
storyNpcs: RpgAgentFoundationDraftCharacter[];
|
||||
landmarks: RpgAgentFoundationDraftLandmark[];
|
||||
camp?: RpgAgentFoundationDraftCamp | null;
|
||||
themePack?: Record<string, unknown> | null;
|
||||
storyGraph?: Record<string, unknown> | null;
|
||||
factions: RpgAgentFoundationDraftFaction[];
|
||||
threads: RpgAgentFoundationDraftThread[];
|
||||
chapters: RpgAgentFoundationDraftChapter[];
|
||||
sceneChapters: RpgAgentFoundationDraftSceneChapter[];
|
||||
worldHook: string;
|
||||
playerPremise: string;
|
||||
openingSituation: string;
|
||||
iconicElements: string[];
|
||||
sourceAnchorSummary: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentFoundationDraftResult {
|
||||
draftProfile: RpgAgentFoundationDraftProfile;
|
||||
draftCards: RpgAgentDraftCardSummary[];
|
||||
}
|
||||
|
||||
export interface RpgAgentDraftCardDetail {
|
||||
id: string;
|
||||
kind: RpgAgentDraftCardKind;
|
||||
title: string;
|
||||
sections: RpgAgentDraftCardDetailSection[];
|
||||
linkedIds: string[];
|
||||
locked: false;
|
||||
editable: boolean;
|
||||
editableSectionIds: string[];
|
||||
warningMessages: string[];
|
||||
assetStatus?: RpgAgentRoleAssetStatus | null;
|
||||
assetStatusLabel?: string | null;
|
||||
}
|
||||
|
||||
export type RpgAgentAssetPriorityTier = 'hero' | 'featured' | 'supporting';
|
||||
|
||||
export type RpgAgentRoleAssetStatus =
|
||||
| 'missing'
|
||||
| 'visual_ready'
|
||||
| 'animations_ready'
|
||||
| 'complete';
|
||||
|
||||
export interface RpgAgentRoleAssetSummary {
|
||||
roleId: string;
|
||||
roleName: string;
|
||||
roleKind: 'playable' | 'story';
|
||||
priorityTier: RpgAgentAssetPriorityTier;
|
||||
portraitPath?: string | null;
|
||||
generatedVisualAssetId?: string | null;
|
||||
generatedAnimationSetId?: string | null;
|
||||
status: RpgAgentRoleAssetStatus;
|
||||
missingAnimations: string[];
|
||||
nextPointCost: number;
|
||||
}
|
||||
|
||||
export interface RpgAgentSceneAssetSummary {
|
||||
sceneId: string;
|
||||
sceneName: string;
|
||||
actId?: string | null;
|
||||
actTitle?: string | null;
|
||||
imageSrc?: string | null;
|
||||
assetId?: string | null;
|
||||
status: 'missing' | 'ready';
|
||||
nextPointCost: number;
|
||||
}
|
||||
|
||||
export interface RpgAgentAssetCoverageSummary {
|
||||
roleAssets: RpgAgentRoleAssetSummary[];
|
||||
sceneAssets: RpgAgentSceneAssetSummary[];
|
||||
allRoleAssetsReady: boolean;
|
||||
allSceneAssetsReady: boolean;
|
||||
}
|
||||
134
packages/shared/src/contracts/rpgAgentSession.ts
Normal file
134
packages/shared/src/contracts/rpgAgentSession.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import type { RpgAgentActionResponse, RpgAgentOperationRecord, RpgAgentSupportedAction, RpgAgentSuggestedAction } from './rpgAgentActions';
|
||||
import type { RpgCreationAnchorContent } from './rpgAgentAnchors';
|
||||
import type { RpgAgentAssetCoverageSummary, RpgAgentDraftCardDetail, RpgAgentDraftCardSummary } from './rpgAgentDraft';
|
||||
import type { RpgCreationPreviewEnvelope } from './rpgCreationPreview';
|
||||
|
||||
/**
|
||||
* RPG Agent 会话层契约。
|
||||
* 这里承载 session 真相源与会话编排元数据,同时预留 resultPreview 与 supportedActions 两个后续主链字段。
|
||||
*/
|
||||
|
||||
export interface RpgCreationIntentReadiness {
|
||||
isReady: boolean;
|
||||
completedKeys: string[];
|
||||
missingKeys: string[];
|
||||
}
|
||||
|
||||
export interface RpgAgentPendingClarification {
|
||||
id: string;
|
||||
label: string;
|
||||
question: string;
|
||||
targetKey:
|
||||
| 'world_hook'
|
||||
| 'player_premise'
|
||||
| 'theme_and_tone'
|
||||
| 'core_conflict'
|
||||
| 'relationship_seed'
|
||||
| 'iconic_element';
|
||||
priority: number;
|
||||
answer?: string;
|
||||
}
|
||||
|
||||
export type RpgAgentStage =
|
||||
| 'collecting_intent'
|
||||
| 'clarifying'
|
||||
| 'foundation_review'
|
||||
| 'object_refining'
|
||||
| 'visual_refining'
|
||||
| 'long_tail_review'
|
||||
| 'ready_to_publish'
|
||||
| 'published'
|
||||
| 'error';
|
||||
|
||||
export type RpgAgentMessageRole = 'user' | 'assistant' | 'system';
|
||||
|
||||
export type RpgAgentMessageKind =
|
||||
| 'chat'
|
||||
| 'clarification'
|
||||
| 'summary'
|
||||
| 'checkpoint'
|
||||
| 'warning'
|
||||
| 'action_result';
|
||||
|
||||
export interface RpgAgentMessage {
|
||||
id: string;
|
||||
role: RpgAgentMessageRole;
|
||||
kind: RpgAgentMessageKind;
|
||||
text: string;
|
||||
createdAt: string;
|
||||
relatedOperationId?: string | null;
|
||||
}
|
||||
|
||||
export interface RpgAgentQualityFinding {
|
||||
id: string;
|
||||
severity: 'info' | 'warning' | 'blocker';
|
||||
code: string;
|
||||
targetId?: string | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface RpgAgentSessionSnapshot {
|
||||
sessionId: string;
|
||||
currentTurn: number;
|
||||
anchorContent: RpgCreationAnchorContent;
|
||||
progressPercent: number;
|
||||
lastAssistantReply: string | null;
|
||||
stage: RpgAgentStage;
|
||||
focusCardId: string | null;
|
||||
creatorIntent: Record<string, unknown> | null;
|
||||
creatorIntentReadiness: RpgCreationIntentReadiness;
|
||||
anchorPack: Record<string, unknown> | null;
|
||||
lockState: Record<string, unknown> | null;
|
||||
draftProfile: Record<string, unknown> | null;
|
||||
messages: RpgAgentMessage[];
|
||||
draftCards: RpgAgentDraftCardSummary[];
|
||||
pendingClarifications: RpgAgentPendingClarification[];
|
||||
suggestedActions: RpgAgentSuggestedAction[];
|
||||
recommendedReplies: string[];
|
||||
qualityFindings: RpgAgentQualityFinding[];
|
||||
assetCoverage: RpgAgentAssetCoverageSummary;
|
||||
/**
|
||||
* checkpoint 元数据需要进入 session snapshot 主链,
|
||||
* 这样前端后续才能拿到真实可回滚目标,而不是只能盲发 checkpointId。
|
||||
*/
|
||||
checkpoints?: Array<{
|
||||
checkpointId: string;
|
||||
createdAt: string;
|
||||
label: string;
|
||||
}>;
|
||||
/**
|
||||
* 后续由工作包 E 的 action registry 真实填充。
|
||||
* 当前保持可选,确保主链迁移期间不影响旧 session snapshot。
|
||||
*/
|
||||
supportedActions?: RpgAgentSupportedAction[];
|
||||
/**
|
||||
* 后续由服务端 preview compiler 输出。
|
||||
* 当前保持可选,允许前端兼容层继续走 legacy profile。
|
||||
*/
|
||||
resultPreview?: RpgCreationPreviewEnvelope | null;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateRpgAgentSessionRequest {
|
||||
seedText?: string;
|
||||
}
|
||||
|
||||
export interface CreateRpgAgentSessionResponse {
|
||||
session: RpgAgentSessionSnapshot;
|
||||
}
|
||||
|
||||
export interface SendRpgAgentMessageRequest {
|
||||
clientMessageId: string;
|
||||
text: string;
|
||||
quickFillRequested?: boolean;
|
||||
focusCardId?: string | null;
|
||||
selectedCardIds?: string[];
|
||||
}
|
||||
|
||||
export interface SendRpgAgentMessageResponse extends RpgAgentActionResponse {
|
||||
operation: RpgAgentOperationRecord;
|
||||
}
|
||||
|
||||
export interface GetRpgAgentCardDetailResponse {
|
||||
card: RpgAgentDraftCardDetail;
|
||||
}
|
||||
146
packages/shared/src/contracts/rpgContracts.test.ts
Normal file
146
packages/shared/src/contracts/rpgContracts.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { CustomWorldAgentSessionSnapshot } from './customWorldAgentSession';
|
||||
import type { CustomWorldResultPreviewEnvelope } from './customWorldResultPreview';
|
||||
import type { CustomWorldWorkSummary } from './customWorldWorkSummary';
|
||||
|
||||
import {
|
||||
createRpgAgentFoundationDraftProfileFixture,
|
||||
createRpgAgentSupportedActionsFixture,
|
||||
createRpgAgentSessionFixture,
|
||||
createRpgCreationAnchorContentFixture,
|
||||
createRpgCreationPreviewEnvelopeFixture,
|
||||
createRpgCreationPublishedProfileFixture,
|
||||
createRpgCreationWorksResponseFixture,
|
||||
createRpgWorldLibraryEntryFixture,
|
||||
} from './rpgCreationFixtures';
|
||||
|
||||
describe('RPG 创作共享契约 fixture', () => {
|
||||
test('旧命名兼容分文件可以直接承接新 fixture 的类型消费', () => {
|
||||
const legacySession: CustomWorldAgentSessionSnapshot =
|
||||
createRpgAgentSessionFixture();
|
||||
const legacyPreview: CustomWorldResultPreviewEnvelope =
|
||||
createRpgCreationPreviewEnvelopeFixture();
|
||||
const legacyWork: CustomWorldWorkSummary =
|
||||
createRpgCreationWorksResponseFixture().items[0]!;
|
||||
|
||||
expect(legacySession.stage).toBe('ready_to_publish');
|
||||
expect(legacySession.resultPreview?.source).toBe(legacyPreview.source);
|
||||
expect(legacyWork.status).toBe('draft');
|
||||
});
|
||||
|
||||
test('anchor fixture 与 foundation draft fixture 保持最小创作真相源对应关系', () => {
|
||||
const anchors = createRpgCreationAnchorContentFixture();
|
||||
const draftProfile = createRpgAgentFoundationDraftProfileFixture();
|
||||
|
||||
expect(anchors.worldPromise?.hook).toContain('旧航路群岛');
|
||||
expect(draftProfile.worldHook).toContain('旧航路群岛');
|
||||
expect(draftProfile.playableNpcs).toHaveLength(1);
|
||||
expect(draftProfile.storyNpcs).toHaveLength(1);
|
||||
expect(draftProfile.sceneChapters[0]?.acts[0]?.backgroundImageSrc).toContain(
|
||||
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
||||
);
|
||||
});
|
||||
|
||||
test('session fixture 同时暴露 supportedActions 与 resultPreview', () => {
|
||||
const session = createRpgAgentSessionFixture();
|
||||
|
||||
expect(session.sessionId).toBe('rpg-session-fixture');
|
||||
expect(session.stage).toBe('ready_to_publish');
|
||||
expect(session.checkpoints?.[0]?.checkpointId).toBe(
|
||||
'checkpoint-foundation-v1',
|
||||
);
|
||||
expect(session.supportedActions?.map((entry) => entry.action)).toEqual(
|
||||
expect.arrayContaining(['draft_foundation', 'generate_role_assets', 'publish_world']),
|
||||
);
|
||||
expect(session.resultPreview?.source).toBe('session_preview');
|
||||
expect(session.resultPreview?.blockers).toEqual([]);
|
||||
});
|
||||
|
||||
test('preview fixture 保持预览来源、质量结论与 profile 载体三层边界', () => {
|
||||
const preview = createRpgCreationPreviewEnvelopeFixture();
|
||||
|
||||
expect(preview.source).toBe('session_preview');
|
||||
expect(preview.preview.previewId).toBe('preview-fixture-1');
|
||||
expect(preview.preview.sessionId).toBe('rpg-session-fixture');
|
||||
expect(preview.qualityFindings?.[0]).toMatchObject({
|
||||
severity: 'info',
|
||||
code: 'scene_asset_ready',
|
||||
});
|
||||
});
|
||||
|
||||
test('supported actions fixture 明确区分可执行能力矩阵,而不是让前端自行猜测按钮状态', () => {
|
||||
const supportedActions = createRpgAgentSupportedActionsFixture();
|
||||
|
||||
expect(supportedActions).toEqual([
|
||||
{ action: 'draft_foundation', enabled: true },
|
||||
{ action: 'generate_role_assets', enabled: true },
|
||||
{ action: 'publish_world', enabled: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('published profile fixture 能稳定承载作品库与结果页所需的封面、场景幕与角色资产字段', () => {
|
||||
const profile = createRpgCreationPublishedProfileFixture();
|
||||
|
||||
expect(profile.id).toBe('rpg-profile-fixture');
|
||||
expect(profile.playableNpcs).toHaveLength(1);
|
||||
expect(profile.landmarks).toHaveLength(1);
|
||||
expect(profile.sceneChapterBlueprints).toHaveLength(1);
|
||||
expect(
|
||||
(profile.sceneChapterBlueprints as Array<{ acts?: Array<{ backgroundImageSrc?: string }> }>)[0]
|
||||
?.acts?.[0]?.backgroundImageSrc,
|
||||
).toContain('/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png');
|
||||
});
|
||||
|
||||
test('regression: session preview 与 published profile 需要共同保留角色动作资产和分幕背景字段', () => {
|
||||
const session = createRpgAgentSessionFixture();
|
||||
const publishedProfile = createRpgCreationPublishedProfileFixture();
|
||||
const preview = createRpgCreationPreviewEnvelopeFixture();
|
||||
|
||||
expect(
|
||||
((session.draftProfile as { playableNpcs?: Array<{ animationMap?: { run?: { basePath?: string } } }> })
|
||||
.playableNpcs?.[0]?.animationMap?.run?.basePath ?? ''),
|
||||
).toContain('/generated-characters/playable-1/animations/run');
|
||||
expect(
|
||||
((preview.preview.playableNpcs as Array<{ generatedAnimationSetId?: string }>)[0]
|
||||
?.generatedAnimationSetId ?? ''),
|
||||
).toBe('animation-set-playable-1');
|
||||
expect(
|
||||
((publishedProfile.sceneChapterBlueprints as Array<{
|
||||
acts?: Array<{ backgroundAssetId?: string }>;
|
||||
}>)[0]?.acts?.[0]?.backgroundAssetId ?? ''),
|
||||
).toBe('scene-asset-runtime');
|
||||
});
|
||||
|
||||
test('works fixture 与 library fixture 对齐同一 published profile', () => {
|
||||
const works = createRpgCreationWorksResponseFixture();
|
||||
const libraryEntry = createRpgWorldLibraryEntryFixture();
|
||||
|
||||
const publishedWork = works.items.find((entry) => entry.status === 'published');
|
||||
|
||||
expect(publishedWork?.profileId).toBe(libraryEntry.profileId);
|
||||
expect(publishedWork?.title).toBe(libraryEntry.worldName);
|
||||
expect(publishedWork?.canEnterWorld).toBe(true);
|
||||
expect(libraryEntry.profile.id).toBe(libraryEntry.profileId);
|
||||
});
|
||||
|
||||
test('regression: works fixture 需要稳定保留草稿与发布态的作品门槛字段', () => {
|
||||
const works = createRpgCreationWorksResponseFixture();
|
||||
const draftWork = works.items.find((entry) => entry.status === 'draft');
|
||||
const publishedWork = works.items.find((entry) => entry.status === 'published');
|
||||
|
||||
expect(draftWork).toMatchObject({
|
||||
stage: 'ready_to_publish',
|
||||
stageLabel: '准备发布',
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
roleVisualReadyCount: 2,
|
||||
roleAnimationReadyCount: 2,
|
||||
});
|
||||
expect(publishedWork).toMatchObject({
|
||||
stage: 'published',
|
||||
stageLabel: '已发布',
|
||||
canResume: false,
|
||||
canEnterWorld: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
714
packages/shared/src/contracts/rpgCreationFixtures.ts
Normal file
714
packages/shared/src/contracts/rpgCreationFixtures.ts
Normal file
@@ -0,0 +1,714 @@
|
||||
import type {
|
||||
CustomWorldLibraryEntry,
|
||||
CustomWorldProfileRecord,
|
||||
} from './runtime';
|
||||
import type { RpgAgentSupportedAction } from './rpgAgentActions';
|
||||
import type { RpgCreationAnchorContent } from './rpgAgentAnchors';
|
||||
import type {
|
||||
RpgAgentAssetCoverageSummary,
|
||||
RpgAgentDraftCardSummary,
|
||||
RpgAgentFoundationDraftProfile,
|
||||
} from './rpgAgentDraft';
|
||||
import type { RpgAgentSessionSnapshot } from './rpgAgentSession';
|
||||
import type { RpgCreationPreviewEnvelope } from './rpgCreationPreview';
|
||||
import type {
|
||||
ListRpgCreationWorksResponse,
|
||||
RpgCreationWorkSummary,
|
||||
} from './rpgCreationWorkSummary';
|
||||
|
||||
const RPG_CREATION_FIXTURE_SESSION_ID = 'rpg-session-fixture';
|
||||
const RPG_CREATION_FIXTURE_PROFILE_ID = 'rpg-profile-fixture';
|
||||
const RPG_CREATION_FIXTURE_USER_ID = 'fixture-user';
|
||||
const RPG_CREATION_FIXTURE_UPDATED_AT = '2026-04-21T09:30:00.000Z';
|
||||
const RPG_CREATION_FIXTURE_PUBLISHED_AT = '2026-04-21T10:00:00.000Z';
|
||||
|
||||
function cloneFixture<T>(value: T): T {
|
||||
return JSON.parse(JSON.stringify(value)) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 共享八锚点 fixture。
|
||||
* 用于 contract test、session fixture 和 works 集成测试复用同一份创作意图样本。
|
||||
*/
|
||||
export function createRpgCreationAnchorContentFixture(): RpgCreationAnchorContent {
|
||||
return cloneFixture({
|
||||
worldPromise: {
|
||||
hook: '被海雾吞没的旧航路群岛',
|
||||
differentiator: '灯塔与禁航令共同决定谁能活着穿过去。',
|
||||
desiredExperience: '压抑、悬疑、潮湿',
|
||||
},
|
||||
playerFantasy: {
|
||||
playerRole: '玩家回到群岛调查沉船真相。',
|
||||
corePursuit: '找出失控航路背后的真相。',
|
||||
fearOfLoss: '失去最后一个还能对上旧案的人。',
|
||||
},
|
||||
themeBoundary: {
|
||||
toneKeywords: ['压抑', '潮湿', '悬疑'],
|
||||
aestheticDirectives: ['旧灯塔', '潮雾', '断裂航路'],
|
||||
forbiddenDirectives: ['不要出现现代枪械'],
|
||||
},
|
||||
playerEntryPoint: {
|
||||
openingIdentity: '被迫返乡的失职守灯人',
|
||||
openingProblem: '首夜就有陌生船只闯入禁航区。',
|
||||
entryMotivation: '查清沉船夜里被谁改动了灯册。',
|
||||
},
|
||||
coreConflict: {
|
||||
surfaceConflicts: ['守灯会与航运公会争夺旧航路控制权'],
|
||||
hiddenCrisis: '沉船夜的航灯与灯册被人动过手脚。',
|
||||
firstTouchedConflict: '玩家开局就会撞上新的封航命令。',
|
||||
},
|
||||
keyRelationships: [
|
||||
{
|
||||
pairs: '玩家 / 沈砺',
|
||||
relationshipType: '旧友兼潜在背叛者',
|
||||
secretOrCost: '沈砺暗地里在替沉船商盟引路。',
|
||||
},
|
||||
],
|
||||
hiddenLines: {
|
||||
hiddenTruths: ['沉船夜的真实失误并不是单纯天灾。'],
|
||||
misdirectionHints: ['所有人都会先把问题推给潮雾本身。'],
|
||||
revealPacing: '第一章露出痕迹,第二章才让玩家摸到灯册线。',
|
||||
},
|
||||
iconicElements: {
|
||||
iconicMotifs: ['会移动的海雾'],
|
||||
institutionsOrArtifacts: ['回潮旧灯塔', '封灯令', '旧潮图'],
|
||||
hardRules: ['禁航信号一旦点亮,任何船都必须退航。'],
|
||||
},
|
||||
} satisfies RpgCreationAnchorContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 共享 foundation draft fixture。
|
||||
* 这份样本同时服务 session 草稿、preview 适配回归测试和 works 聚合测试。
|
||||
*/
|
||||
export function createRpgAgentFoundationDraftProfileFixture(): RpgAgentFoundationDraftProfile {
|
||||
return cloneFixture({
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成,玩家将从回潮旧灯塔切入沉船旧案。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清沉船与禁航区异动的真相。',
|
||||
majorFactions: ['守灯会', '航运公会'],
|
||||
coreConflicts: ['守灯会与航运公会争夺旧航路控制权'],
|
||||
playableNpcs: [
|
||||
{
|
||||
id: 'playable-1',
|
||||
name: '沈砺',
|
||||
title: '旧航路引路人',
|
||||
role: '关键同行者',
|
||||
publicIdentity: '最熟悉旧航路的人。',
|
||||
publicMask: '看上去像可靠旧友。',
|
||||
currentPressure: '他必须在两股势力间站队。',
|
||||
hiddenHook: '暗中替沉船商盟引路。',
|
||||
relationToPlayer: '旧友兼潜在背叛者',
|
||||
threadIds: ['thread-1'],
|
||||
summary: '他像旧友,但也像一把始终没收回鞘的刀。',
|
||||
skills: [
|
||||
{
|
||||
id: 'skill-playable-1',
|
||||
name: '潮行引路',
|
||||
actionPreviewConfig: {
|
||||
basePath:
|
||||
'/generated-characters/playable-1/animations/skills/skill-playable-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
imageSrc:
|
||||
'/generated-characters/playable-1/visual/asset-runtime/master.png',
|
||||
generatedVisualAssetId: 'asset-runtime-playable',
|
||||
generatedAnimationSetId: 'animation-set-playable-1',
|
||||
animationMap: {
|
||||
idle: {
|
||||
basePath: '/generated-characters/playable-1/animations/idle',
|
||||
},
|
||||
run: {
|
||||
basePath: '/generated-characters/playable-1/animations/run',
|
||||
},
|
||||
attack: {
|
||||
basePath: '/generated-characters/playable-1/animations/attack',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
storyNpcs: [
|
||||
{
|
||||
id: 'story-1',
|
||||
name: '顾潮音',
|
||||
title: '守灯会值夜人',
|
||||
role: '场景关键角色',
|
||||
publicIdentity: '负责夜间巡灯与封锁。',
|
||||
publicMask: '对外一直冷静克制。',
|
||||
currentPressure: '她知道更多禁航区真相。',
|
||||
hiddenHook: '曾亲眼见过失控海雾吞船。',
|
||||
relationToPlayer: '最早愿意交换线索的人',
|
||||
threadIds: ['thread-1'],
|
||||
summary: '她总像比所有人更早知道海雾会往哪一侧压下来。',
|
||||
skills: [
|
||||
{
|
||||
id: 'skill-story-1',
|
||||
name: '夜潮灯语',
|
||||
actionPreviewConfig: {
|
||||
basePath:
|
||||
'/generated-characters/story-1/animations/skills/skill-story-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
imageSrc:
|
||||
'/generated-characters/story-1/visual/asset-runtime/master.png',
|
||||
generatedVisualAssetId: 'asset-runtime-story',
|
||||
generatedAnimationSetId: 'animation-set-story-1',
|
||||
animationMap: {
|
||||
run: {
|
||||
basePath: '/generated-characters/story-1/animations/run',
|
||||
},
|
||||
attack: {
|
||||
basePath: '/generated-characters/story-1/animations/attack',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
landmarks: [
|
||||
{
|
||||
id: 'landmark-1',
|
||||
name: '回潮旧灯塔',
|
||||
description: '被海雾啃旧的石塔仍在夜里维持着微弱灯火。',
|
||||
purpose: '观察雾潮与往来船只',
|
||||
mood: '潮湿、压抑、风声不止',
|
||||
importance: '开局核心场景',
|
||||
secret: '高处潮痕说明海面异常抬升过。',
|
||||
dangerLevel: 'high',
|
||||
imageSrc: '/generated-custom-world-scenes/landmark-1/latest-scene.png',
|
||||
generatedSceneAssetId: 'scene-asset-landmark-1',
|
||||
characterIds: ['story-1'],
|
||||
threadIds: ['thread-1'],
|
||||
summary: '旧灯塔是整片群岛最先看见异动的地方。',
|
||||
},
|
||||
],
|
||||
camp: {
|
||||
id: 'camp-1',
|
||||
name: '回潮暂栖所',
|
||||
description: '能暂时收拢队伍、整理灯册与潮路线索的落脚点。',
|
||||
mood: '克制、紧绷,但还能暂时收拢局势',
|
||||
dangerLevel: 'low',
|
||||
imageSrc: '/custom/camp/huichao.png',
|
||||
generatedSceneAssetId: 'scene-asset-camp-1',
|
||||
summary: '玩家能在这里整理情报、回看旧灯册和沉船名单。',
|
||||
},
|
||||
themePack: {
|
||||
id: 'theme-pack:tide',
|
||||
displayName: '潮雾悬疑',
|
||||
},
|
||||
storyGraph: {
|
||||
visibleThreads: [
|
||||
{
|
||||
id: 'thread-visible-1',
|
||||
title: '封航争夺',
|
||||
},
|
||||
],
|
||||
},
|
||||
factions: [
|
||||
{
|
||||
id: 'faction-1',
|
||||
name: '守灯会',
|
||||
title: '守灯会',
|
||||
subtitle: '把控禁航灯令的人',
|
||||
publicGoal: '维持封航秩序并压住灯册流出。',
|
||||
relatedConflict: '想把旧案继续压在禁航记录之下。',
|
||||
tension: '他们越强调规矩,越像在遮掩灯册。',
|
||||
playerRelation: '玩家迟早要与他们正面冲突。',
|
||||
summary: '掌握灯塔与封航令的势力,也是最怕旧案被翻出来的一方。',
|
||||
},
|
||||
],
|
||||
threads: [
|
||||
{
|
||||
id: 'thread-1',
|
||||
title: '沉船旧案',
|
||||
type: 'main',
|
||||
conflictType: '真相遮蔽',
|
||||
conflict: '沉船夜的航灯与灯册被人动过手脚。',
|
||||
stakes: '真相一旦坐实,群岛秩序会先崩。',
|
||||
characterIds: ['playable-1', 'story-1'],
|
||||
landmarkIds: ['landmark-1'],
|
||||
summary: '玩家会从灯塔高处潮痕一路追到沉船夜的真相。',
|
||||
},
|
||||
],
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-1',
|
||||
title: '灯塔回潮',
|
||||
openingEvent: '禁航区闯入了一艘不该出现的陌生船。',
|
||||
playerGoal: '先稳住局势,再拿到第一份灯册线索。',
|
||||
characterIds: ['playable-1', 'story-1'],
|
||||
landmarkIds: ['landmark-1'],
|
||||
understandingShift: '玩家会意识到沉船旧案至今仍在操控群岛秩序。',
|
||||
summary: '第一章聚焦灯塔与封航令,给玩家一条可追的旧案线索。',
|
||||
},
|
||||
],
|
||||
sceneChapters: [
|
||||
{
|
||||
id: 'scene-chapter-1',
|
||||
sceneId: 'landmark-1',
|
||||
sceneName: '回潮旧灯塔',
|
||||
title: '灯塔初章',
|
||||
summary: '围绕灯塔推进的首个场景章节。',
|
||||
linkedThreadIds: ['thread-1'],
|
||||
linkedLandmarkIds: ['landmark-1'],
|
||||
acts: [
|
||||
{
|
||||
id: 'scene-act-1',
|
||||
title: '第一幕',
|
||||
summary: '先接住回潮灯塔的入口压力。',
|
||||
stageCoverage: ['opening'],
|
||||
backgroundImageSrc:
|
||||
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
||||
backgroundAssetId: 'scene-asset-runtime',
|
||||
encounterNpcIds: ['story-1'],
|
||||
primaryNpcId: 'story-1',
|
||||
linkedThreadIds: ['thread-1'],
|
||||
actGoal: '接住首幕入口',
|
||||
transitionHook: '向第二幕推进。',
|
||||
advanceRule: 'after_primary_contact',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
worldHook: '被海雾吞没的旧航路群岛',
|
||||
playerPremise: '玩家回到群岛调查沉船真相。',
|
||||
openingSituation: '首夜就有陌生船只闯入禁航区。',
|
||||
iconicElements: ['会移动的海雾', '回潮旧灯塔'],
|
||||
sourceAnchorSummary: '海雾、旧灯塔、失控航路。',
|
||||
} satisfies RpgAgentFoundationDraftProfile);
|
||||
}
|
||||
|
||||
function createRpgAgentDraftCardsFixture(): RpgAgentDraftCardSummary[] {
|
||||
return cloneFixture([
|
||||
{
|
||||
id: 'world-foundation',
|
||||
kind: 'world',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成。',
|
||||
status: 'suggested',
|
||||
linkedIds: ['playable-1', 'story-1', 'landmark-1'],
|
||||
warningCount: 0,
|
||||
},
|
||||
{
|
||||
id: 'playable-1',
|
||||
kind: 'character',
|
||||
title: '沈砺',
|
||||
subtitle: '旧航路引路人 / 动作已齐',
|
||||
summary: '最熟悉旧航路的人,也可能是最危险的旧友。',
|
||||
status: 'suggested',
|
||||
linkedIds: ['thread-1', 'landmark-1'],
|
||||
warningCount: 0,
|
||||
assetStatus: 'complete',
|
||||
assetStatusLabel: '动作已齐',
|
||||
},
|
||||
{
|
||||
id: 'landmark-1',
|
||||
kind: 'landmark',
|
||||
title: '回潮旧灯塔',
|
||||
subtitle: '观察雾潮与往来船只',
|
||||
summary: '旧灯塔是整片群岛最先看见异动的地方。',
|
||||
status: 'suggested',
|
||||
linkedIds: ['story-1', 'thread-1'],
|
||||
warningCount: 0,
|
||||
},
|
||||
] satisfies RpgAgentDraftCardSummary[]);
|
||||
}
|
||||
|
||||
function createRpgAgentAssetCoverageFixture(): RpgAgentAssetCoverageSummary {
|
||||
return cloneFixture({
|
||||
roleAssets: [
|
||||
{
|
||||
roleId: 'playable-1',
|
||||
roleName: '沈砺',
|
||||
roleKind: 'playable',
|
||||
priorityTier: 'hero',
|
||||
portraitPath:
|
||||
'/generated-characters/playable-1/visual/asset-runtime/master.png',
|
||||
generatedVisualAssetId: 'asset-runtime-playable',
|
||||
generatedAnimationSetId: 'animation-set-playable-1',
|
||||
status: 'complete',
|
||||
missingAnimations: [],
|
||||
nextPointCost: 0,
|
||||
},
|
||||
{
|
||||
roleId: 'story-1',
|
||||
roleName: '顾潮音',
|
||||
roleKind: 'story',
|
||||
priorityTier: 'featured',
|
||||
portraitPath:
|
||||
'/generated-characters/story-1/visual/asset-runtime/master.png',
|
||||
generatedVisualAssetId: 'asset-runtime-story',
|
||||
generatedAnimationSetId: 'animation-set-story-1',
|
||||
status: 'complete',
|
||||
missingAnimations: [],
|
||||
nextPointCost: 0,
|
||||
},
|
||||
],
|
||||
sceneAssets: [
|
||||
{
|
||||
sceneId: 'landmark-1',
|
||||
sceneName: '回潮旧灯塔',
|
||||
actId: 'scene-act-1',
|
||||
actTitle: '第一幕',
|
||||
imageSrc:
|
||||
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
||||
assetId: 'scene-asset-runtime',
|
||||
status: 'ready',
|
||||
nextPointCost: 0,
|
||||
},
|
||||
],
|
||||
allRoleAssetsReady: true,
|
||||
allSceneAssetsReady: true,
|
||||
} satisfies RpgAgentAssetCoverageSummary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 已发布 profile fixture。
|
||||
* 用于 preview compiler、works 聚合和 library 元数据解析测试。
|
||||
*/
|
||||
export function createRpgCreationPublishedProfileFixture(): CustomWorldProfileRecord {
|
||||
const draft = createRpgAgentFoundationDraftProfileFixture();
|
||||
|
||||
return cloneFixture({
|
||||
id: RPG_CREATION_FIXTURE_PROFILE_ID,
|
||||
settingText: draft.worldHook,
|
||||
name: draft.name,
|
||||
subtitle: draft.subtitle,
|
||||
summary: draft.summary,
|
||||
tone: draft.tone,
|
||||
playerGoal: draft.playerGoal,
|
||||
templateWorldType: 'WUXIA',
|
||||
compatibilityTemplateWorldType: 'WUXIA',
|
||||
majorFactions: draft.majorFactions,
|
||||
coreConflicts: draft.coreConflicts,
|
||||
playableNpcs: draft.playableNpcs.map((role) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
title: role.title,
|
||||
role: role.role,
|
||||
description: role.publicIdentity,
|
||||
backstory: role.hiddenHook || role.summary,
|
||||
personality: role.publicMask || role.summary,
|
||||
motivation: role.currentPressure,
|
||||
combatStyle: '借地形和潮路换位,先拉扯再压近。',
|
||||
initialAffinity: 18,
|
||||
relationshipHooks: [role.relationToPlayer],
|
||||
tags: ['潮路', '旧案'],
|
||||
imageSrc: role.imageSrc,
|
||||
generatedVisualAssetId: role.generatedVisualAssetId,
|
||||
generatedAnimationSetId: role.generatedAnimationSetId,
|
||||
animationMap: role.animationMap,
|
||||
skills: [
|
||||
{
|
||||
id: 'skill-playable-1',
|
||||
name: '潮行引路',
|
||||
summary: '踩着旧潮阶切线前压,替队伍打开角度。',
|
||||
style: '机动周旋',
|
||||
},
|
||||
],
|
||||
templateCharacterId: 'archer-hero',
|
||||
})),
|
||||
storyNpcs: draft.storyNpcs.map((role) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
title: role.title,
|
||||
role: role.role,
|
||||
description: role.publicIdentity,
|
||||
backstory: role.hiddenHook || role.summary,
|
||||
personality: role.publicMask || role.summary,
|
||||
motivation: role.currentPressure,
|
||||
combatStyle: '借塔顶视角和风向压制,再用灯火错位扰乱。',
|
||||
initialAffinity: 8,
|
||||
relationshipHooks: [role.relationToPlayer],
|
||||
tags: ['守灯会', '灯塔'],
|
||||
imageSrc: role.imageSrc,
|
||||
generatedVisualAssetId: role.generatedVisualAssetId,
|
||||
skills: [
|
||||
{
|
||||
id: 'skill-story-1',
|
||||
name: '夜潮灯语',
|
||||
summary: '借灯语与潮声干扰对方判断。',
|
||||
style: '起手压制',
|
||||
},
|
||||
],
|
||||
})),
|
||||
camp: {
|
||||
name: draft.camp?.name,
|
||||
description: draft.camp?.description,
|
||||
dangerLevel: draft.camp?.dangerLevel,
|
||||
imageSrc: draft.camp?.imageSrc,
|
||||
},
|
||||
landmarks: draft.landmarks.map((landmark) => ({
|
||||
id: landmark.id,
|
||||
name: landmark.name,
|
||||
description: landmark.description,
|
||||
dangerLevel: landmark.dangerLevel,
|
||||
imageSrc: landmark.imageSrc,
|
||||
sceneNpcIds: landmark.characterIds,
|
||||
connections: [
|
||||
{
|
||||
targetLandmarkId: 'landmark-1',
|
||||
relativePosition: 'forward',
|
||||
summary: '沿着旧潮阶继续前压到雾栈尽头。',
|
||||
},
|
||||
],
|
||||
})),
|
||||
cover: {
|
||||
sourceType: 'default',
|
||||
characterRoleIds: ['playable-1'],
|
||||
},
|
||||
sceneChapterBlueprints: draft.sceneChapters.map((chapter) => ({
|
||||
id: chapter.id,
|
||||
sceneId: chapter.sceneId,
|
||||
sceneName: chapter.sceneName,
|
||||
title: chapter.title,
|
||||
summary: chapter.summary,
|
||||
acts: chapter.acts.map((act) => ({
|
||||
id: act.id,
|
||||
title: act.title,
|
||||
summary: act.summary,
|
||||
backgroundImageSrc: act.backgroundImageSrc,
|
||||
backgroundAssetId: act.backgroundAssetId,
|
||||
encounterNpcIds: act.encounterNpcIds,
|
||||
primaryNpcId: act.primaryNpcId,
|
||||
actGoal: act.actGoal,
|
||||
transitionHook: act.transitionHook,
|
||||
})),
|
||||
})),
|
||||
themePack: draft.themePack,
|
||||
storyGraph: draft.storyGraph,
|
||||
scenarioPackId: 'scenario-pack:tide',
|
||||
campaignPackId: 'campaign-pack:tide',
|
||||
generationMode: 'fast',
|
||||
generationStatus: 'key_only',
|
||||
updatedAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
publishedAt: RPG_CREATION_FIXTURE_PUBLISHED_AT,
|
||||
} satisfies CustomWorldProfileRecord);
|
||||
}
|
||||
|
||||
export function createRpgCreationPreviewEnvelopeFixture(): RpgCreationPreviewEnvelope {
|
||||
return cloneFixture({
|
||||
preview: {
|
||||
...createRpgCreationPublishedProfileFixture(),
|
||||
previewId: 'preview-fixture-1',
|
||||
sessionId: RPG_CREATION_FIXTURE_SESSION_ID,
|
||||
profileId: RPG_CREATION_FIXTURE_PROFILE_ID,
|
||||
},
|
||||
source: 'session_preview',
|
||||
generatedAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
qualityFindings: [
|
||||
{
|
||||
id: 'finding-scene-asset-ready',
|
||||
severity: 'info',
|
||||
code: 'scene_asset_ready',
|
||||
targetId: 'scene-act-1',
|
||||
message: '首幕背景图已经就绪,可直接用于结果页预览。',
|
||||
},
|
||||
],
|
||||
blockers: [],
|
||||
publishReady: true,
|
||||
canEnterWorld: false,
|
||||
} satisfies RpgCreationPreviewEnvelope);
|
||||
}
|
||||
|
||||
export function createRpgAgentSupportedActionsFixture(): RpgAgentSupportedAction[] {
|
||||
return cloneFixture([
|
||||
{
|
||||
action: 'draft_foundation',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
action: 'generate_role_assets',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
action: 'publish_world',
|
||||
enabled: true,
|
||||
},
|
||||
] satisfies RpgAgentSupportedAction[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 共享 session snapshot fixture。
|
||||
* 默认模拟“底稿、预览、资产都已准备好”的 ready_to_publish 状态。
|
||||
*/
|
||||
export function createRpgAgentSessionFixture(): RpgAgentSessionSnapshot {
|
||||
const draftProfile = createRpgAgentFoundationDraftProfileFixture();
|
||||
|
||||
return cloneFixture({
|
||||
sessionId: RPG_CREATION_FIXTURE_SESSION_ID,
|
||||
currentTurn: 6,
|
||||
anchorContent: createRpgCreationAnchorContentFixture(),
|
||||
progressPercent: 100,
|
||||
lastAssistantReply: '八锚点与底稿都已经齐备,可以进入结果页收口。',
|
||||
stage: 'ready_to_publish',
|
||||
focusCardId: null,
|
||||
creatorIntent: {
|
||||
sourceMode: 'card',
|
||||
rawSettingText: draftProfile.worldHook,
|
||||
worldHook: draftProfile.worldHook,
|
||||
playerPremise: draftProfile.playerPremise,
|
||||
themeKeywords: ['海雾', '旧航路'],
|
||||
toneDirectives: ['压抑', '悬疑'],
|
||||
openingSituation: draftProfile.openingSituation,
|
||||
coreConflicts: draftProfile.coreConflicts,
|
||||
keyFactions: ['守灯会'],
|
||||
keyCharacters: ['沈砺', '顾潮音'],
|
||||
keyLandmarks: ['回潮旧灯塔'],
|
||||
iconicElements: draftProfile.iconicElements,
|
||||
forbiddenDirectives: ['不要出现现代枪械'],
|
||||
},
|
||||
creatorIntentReadiness: {
|
||||
isReady: true,
|
||||
completedKeys: [
|
||||
'world_hook',
|
||||
'player_premise',
|
||||
'theme_and_tone',
|
||||
'core_conflict',
|
||||
'relationship_seed',
|
||||
'iconic_element',
|
||||
],
|
||||
missingKeys: [],
|
||||
},
|
||||
anchorPack: {
|
||||
summary: draftProfile.sourceAnchorSummary,
|
||||
},
|
||||
lockState: {
|
||||
lockedCardIds: ['world-foundation'],
|
||||
},
|
||||
draftProfile,
|
||||
messages: [
|
||||
{
|
||||
id: 'message-1',
|
||||
role: 'assistant',
|
||||
kind: 'summary',
|
||||
text: '世界底稿已整理完成,建议进入结果页确认资产与发布门槛。',
|
||||
createdAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
relatedOperationId: null,
|
||||
},
|
||||
],
|
||||
draftCards: createRpgAgentDraftCardsFixture(),
|
||||
pendingClarifications: [],
|
||||
suggestedActions: [
|
||||
{
|
||||
id: 'action-publish',
|
||||
type: 'publish_world',
|
||||
label: '发布世界',
|
||||
},
|
||||
],
|
||||
recommendedReplies: ['先看结果页', '继续精修角色关系'],
|
||||
qualityFindings: [
|
||||
{
|
||||
id: 'finding-scene-asset-ready',
|
||||
severity: 'info',
|
||||
code: 'scene_asset_ready',
|
||||
targetId: 'scene-act-1',
|
||||
message: '首幕背景图已经就绪,可直接用于结果页预览。',
|
||||
},
|
||||
],
|
||||
assetCoverage: createRpgAgentAssetCoverageFixture(),
|
||||
checkpoints: [
|
||||
{
|
||||
checkpointId: 'checkpoint-foundation-v1',
|
||||
createdAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
label: '世界底稿 V1',
|
||||
},
|
||||
],
|
||||
supportedActions: createRpgAgentSupportedActionsFixture(),
|
||||
resultPreview: createRpgCreationPreviewEnvelopeFixture(),
|
||||
updatedAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
} satisfies RpgAgentSessionSnapshot);
|
||||
}
|
||||
|
||||
export function createRpgWorldLibraryEntryFixture(): CustomWorldLibraryEntry<CustomWorldProfileRecord> {
|
||||
const profile = createRpgCreationPublishedProfileFixture();
|
||||
|
||||
return cloneFixture({
|
||||
ownerUserId: RPG_CREATION_FIXTURE_USER_ID,
|
||||
profileId: RPG_CREATION_FIXTURE_PROFILE_ID,
|
||||
profile,
|
||||
visibility: 'published',
|
||||
publishedAt: RPG_CREATION_FIXTURE_PUBLISHED_AT,
|
||||
updatedAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
authorDisplayName: '测试玩家',
|
||||
worldName: String(profile.name ?? '潮雾列岛'),
|
||||
subtitle: String(profile.subtitle ?? '旧灯塔与失控航路'),
|
||||
summaryText: String(profile.summary ?? '第一版世界底稿已经整理完成。'),
|
||||
coverImageSrc:
|
||||
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
||||
themeMode: 'tide',
|
||||
playableNpcCount: Array.isArray(profile.playableNpcs)
|
||||
? profile.playableNpcs.length
|
||||
: 0,
|
||||
landmarkCount: Array.isArray(profile.landmarks)
|
||||
? profile.landmarks.length
|
||||
: 0,
|
||||
} satisfies CustomWorldLibraryEntry<CustomWorldProfileRecord>);
|
||||
}
|
||||
|
||||
export function createRpgCreationWorksResponseFixture(): ListRpgCreationWorksResponse {
|
||||
return cloneFixture({
|
||||
items: [
|
||||
{
|
||||
workId: `draft:${RPG_CREATION_FIXTURE_SESSION_ID}`,
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成,玩家将从回潮旧灯塔切入沉船旧案。',
|
||||
coverImageSrc: '/custom/camp/huichao.png',
|
||||
coverRenderMode: 'scene_with_roles',
|
||||
coverCharacterImageSrcs: [
|
||||
'/generated-characters/playable-1/visual/asset-runtime/master.png',
|
||||
],
|
||||
updatedAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
publishedAt: null,
|
||||
stage: 'ready_to_publish',
|
||||
stageLabel: '准备发布',
|
||||
playableNpcCount: 2,
|
||||
landmarkCount: 1,
|
||||
roleVisualReadyCount: 2,
|
||||
roleAnimationReadyCount: 2,
|
||||
roleAssetSummaryLabel: '沈砺 · 动作已就绪',
|
||||
sessionId: RPG_CREATION_FIXTURE_SESSION_ID,
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
blockerCount: 0,
|
||||
publishReady: true,
|
||||
},
|
||||
{
|
||||
workId: `published:${RPG_CREATION_FIXTURE_PROFILE_ID}`,
|
||||
sourceType: 'published_profile',
|
||||
status: 'published',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成,玩家将从回潮旧灯塔切入沉船旧案。',
|
||||
coverImageSrc:
|
||||
'/generated-custom-world-scenes/landmark-1/scene-act-1/scene.png',
|
||||
coverRenderMode: 'scene_with_roles',
|
||||
coverCharacterImageSrcs: [
|
||||
'/generated-characters/playable-1/visual/asset-runtime/master.png',
|
||||
],
|
||||
updatedAt: RPG_CREATION_FIXTURE_UPDATED_AT,
|
||||
publishedAt: RPG_CREATION_FIXTURE_PUBLISHED_AT,
|
||||
stage: 'published',
|
||||
stageLabel: '已发布',
|
||||
playableNpcCount: 1,
|
||||
landmarkCount: 1,
|
||||
roleVisualReadyCount: 1,
|
||||
roleAnimationReadyCount: 1,
|
||||
roleAssetSummaryLabel: '动作已就绪 1',
|
||||
sessionId: null,
|
||||
profileId: RPG_CREATION_FIXTURE_PROFILE_ID,
|
||||
canResume: false,
|
||||
canEnterWorld: true,
|
||||
blockerCount: 0,
|
||||
publishReady: true,
|
||||
},
|
||||
] satisfies RpgCreationWorkSummary[],
|
||||
} satisfies ListRpgCreationWorksResponse);
|
||||
}
|
||||
40
packages/shared/src/contracts/rpgCreationPreview.ts
Normal file
40
packages/shared/src/contracts/rpgCreationPreview.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { CustomWorldProfileRecord } from './runtime';
|
||||
|
||||
/**
|
||||
* 结果页预览契约。
|
||||
* 当前 preview 仍以兼容 profile 作为承载体,但已经把来源、阻断项和质量结论从 session 草稿里显式剥离出来。
|
||||
*/
|
||||
|
||||
export type RpgCreationPreviewSource =
|
||||
| 'session_preview'
|
||||
| 'published_profile';
|
||||
|
||||
export interface RpgCreationPreviewFinding {
|
||||
id: string;
|
||||
severity: 'info' | 'warning' | 'blocker';
|
||||
code: string;
|
||||
targetId?: string | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface RpgCreationPreviewBlocker {
|
||||
id: string;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type RpgCreationPreview = CustomWorldProfileRecord & {
|
||||
previewId?: string;
|
||||
sessionId?: string | null;
|
||||
profileId?: string | null;
|
||||
};
|
||||
|
||||
export interface RpgCreationPreviewEnvelope {
|
||||
preview: RpgCreationPreview;
|
||||
source: RpgCreationPreviewSource;
|
||||
generatedAt?: string;
|
||||
qualityFindings?: RpgCreationPreviewFinding[];
|
||||
blockers?: RpgCreationPreviewBlocker[];
|
||||
publishReady?: boolean;
|
||||
canEnterWorld?: boolean;
|
||||
}
|
||||
38
packages/shared/src/contracts/rpgCreationWorkSummary.ts
Normal file
38
packages/shared/src/contracts/rpgCreationWorkSummary.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* RPG 创作作品卡读模型契约。
|
||||
* works 列表只暴露继续创作与进入世界判断所需的稳定字段。
|
||||
*/
|
||||
|
||||
export type RpgCreationWorkStatus = 'draft' | 'published';
|
||||
export type RpgCreationWorkSource = 'agent_session' | 'published_profile';
|
||||
|
||||
export interface RpgCreationWorkSummary {
|
||||
workId: string;
|
||||
sourceType: RpgCreationWorkSource;
|
||||
status: RpgCreationWorkStatus;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
summary: string;
|
||||
coverImageSrc?: string | null;
|
||||
coverRenderMode?: 'image' | 'scene_with_roles';
|
||||
coverCharacterImageSrcs?: string[];
|
||||
updatedAt: string;
|
||||
publishedAt?: string | null;
|
||||
stage?: string | null;
|
||||
stageLabel?: string | null;
|
||||
playableNpcCount: number;
|
||||
landmarkCount: number;
|
||||
roleVisualReadyCount?: number;
|
||||
roleAnimationReadyCount?: number;
|
||||
roleAssetSummaryLabel?: string | null;
|
||||
sessionId?: string | null;
|
||||
profileId?: string | null;
|
||||
canResume: boolean;
|
||||
canEnterWorld: boolean;
|
||||
blockerCount?: number;
|
||||
publishReady?: boolean;
|
||||
}
|
||||
|
||||
export interface ListRpgCreationWorksResponse {
|
||||
items: RpgCreationWorkSummary[];
|
||||
}
|
||||
184
packages/shared/src/contracts/rpgRuntimeChat.ts
Normal file
184
packages/shared/src/contracts/rpgRuntimeChat.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* RPG 运行时聊天相关共享契约。
|
||||
* 将角色聊天、NPC 对话与轻量 story 请求载荷从旧 story.ts 中独立出来。
|
||||
*/
|
||||
import type { JsonObject } from './common';
|
||||
|
||||
export type NpcChatTurnLimitReason = 'negative_affinity';
|
||||
|
||||
export type NpcChatTurnClosingMode = 'free' | 'foreshadow_close';
|
||||
|
||||
export type NpcChatTurnDirective = {
|
||||
sceneActId?: string | null;
|
||||
turnLimit?: number | null;
|
||||
remainingTurns?: number | null;
|
||||
limitReason?: NpcChatTurnLimitReason | null;
|
||||
closingMode?: NpcChatTurnClosingMode | null;
|
||||
forceExitAfterTurn?: boolean;
|
||||
};
|
||||
|
||||
export type NpcChatTurnCompletionDirective = {
|
||||
turnLimit?: number | null;
|
||||
remainingTurns?: number | null;
|
||||
forceExit?: boolean;
|
||||
closingMode?: NpcChatTurnClosingMode;
|
||||
};
|
||||
|
||||
export type CharacterChatReplyRequest<
|
||||
TCharacter = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TTargetStatus = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
playerCharacter: TCharacter;
|
||||
targetCharacter: TCharacter;
|
||||
storyHistory: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory: TConversationTurn[];
|
||||
conversationSummary: string;
|
||||
playerMessage: string;
|
||||
targetStatus: TTargetStatus;
|
||||
};
|
||||
|
||||
export type CharacterChatSuggestionsRequest<
|
||||
TCharacter = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TTargetStatus = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
playerCharacter: TCharacter;
|
||||
targetCharacter: TCharacter;
|
||||
storyHistory: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory: TConversationTurn[];
|
||||
conversationSummary: string;
|
||||
targetStatus: TTargetStatus;
|
||||
};
|
||||
|
||||
export type CharacterChatSummaryRequest<
|
||||
TCharacter = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TTargetStatus = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
playerCharacter: TCharacter;
|
||||
targetCharacter: TCharacter;
|
||||
storyHistory: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory: TConversationTurn[];
|
||||
previousSummary: string;
|
||||
targetStatus: TTargetStatus;
|
||||
};
|
||||
|
||||
export type NpcChatDialogueRequest<
|
||||
TCharacter = unknown,
|
||||
TEncounter = unknown,
|
||||
TMonster = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
character: TCharacter;
|
||||
encounter: TEncounter;
|
||||
monsters: TMonster[];
|
||||
history: TStoryMoment[];
|
||||
context: TContext;
|
||||
topic: string;
|
||||
resultSummary: string;
|
||||
npcInitiatesConversation?: boolean;
|
||||
};
|
||||
|
||||
export type NpcChatTurnRequest<
|
||||
TCharacter = unknown,
|
||||
TEncounter = unknown,
|
||||
TMonster = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TCombatContext = unknown,
|
||||
TNpcState = unknown,
|
||||
TQuestOfferState = unknown,
|
||||
TQuestOfferEncounter = unknown,
|
||||
TChatDirective = NpcChatTurnDirective,
|
||||
> = {
|
||||
worldType: string;
|
||||
character?: TCharacter;
|
||||
player?: TCharacter;
|
||||
encounter: TEncounter;
|
||||
monsters: TMonster[];
|
||||
history: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory?: TConversationTurn[];
|
||||
dialogue?: TConversationTurn[];
|
||||
combatContext?: TCombatContext | null;
|
||||
playerMessage: string;
|
||||
npcState: TNpcState;
|
||||
npcInitiatesConversation?: boolean;
|
||||
questOfferContext?: {
|
||||
state: TQuestOfferState;
|
||||
encounter: TQuestOfferEncounter;
|
||||
turnCount: number;
|
||||
} | null;
|
||||
chatDirective?: TChatDirective | null;
|
||||
};
|
||||
|
||||
export type NpcChatPendingQuestOffer<TQuest = unknown> = {
|
||||
quest: TQuest;
|
||||
introText?: string;
|
||||
};
|
||||
|
||||
export type NpcChatTurnResult<TQuest = unknown> = {
|
||||
npcReply: string;
|
||||
affinityDelta: number;
|
||||
affinityText: string;
|
||||
suggestions: string[];
|
||||
pendingQuestOffer?: NpcChatPendingQuestOffer<TQuest> | null;
|
||||
chatDirective?: NpcChatTurnCompletionDirective | null;
|
||||
};
|
||||
|
||||
export type NpcRecruitDialogueRequest<
|
||||
TCharacter = unknown,
|
||||
TEncounter = unknown,
|
||||
TMonster = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
character: TCharacter;
|
||||
encounter: TEncounter;
|
||||
monsters: TMonster[];
|
||||
history: TStoryMoment[];
|
||||
context: TContext;
|
||||
invitationText: string;
|
||||
recruitSummary: string;
|
||||
};
|
||||
|
||||
export type StoryRequestOptionsPayload = {
|
||||
availableOptions?: JsonObject[];
|
||||
optionCatalog?: JsonObject[];
|
||||
};
|
||||
|
||||
export type StoryRequestPayload<TWorldType extends string = string> = {
|
||||
worldType: TWorldType;
|
||||
character: JsonObject;
|
||||
monsters?: JsonObject[];
|
||||
history?: JsonObject[];
|
||||
choice?: string;
|
||||
context: JsonObject;
|
||||
requestOptions?: StoryRequestOptionsPayload;
|
||||
};
|
||||
|
||||
export type PlainTextPromptRequest = {
|
||||
systemPrompt: string;
|
||||
userPrompt: string;
|
||||
};
|
||||
|
||||
export type PlainTextResponse = {
|
||||
text: string;
|
||||
};
|
||||
51
packages/shared/src/contracts/rpgRuntimeContracts.test.ts
Normal file
51
packages/shared/src/contracts/rpgRuntimeContracts.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import type { CharacterChatReplyRequest } from './rpgRuntimeChat';
|
||||
import { QUEST_NARRATIVE_TYPES } from './rpgRuntimeQuestAssist';
|
||||
import {
|
||||
SERVER_RUNTIME_FUNCTION_IDS,
|
||||
TASK5_RUNTIME_OPTION_SCOPES,
|
||||
TASK6_RUNTIME_FUNCTION_IDS,
|
||||
type RuntimeStoryActionRequest,
|
||||
} from './rpgRuntimeStoryAction';
|
||||
import type { RuntimeStoryStateRequest } from './rpgRuntimeStoryState';
|
||||
|
||||
describe('RPG runtime shared contracts', () => {
|
||||
test('拆分后的 runtime story action 契约继续导出常量与类型', () => {
|
||||
expect(SERVER_RUNTIME_FUNCTION_IDS).toContain('npc_chat');
|
||||
expect(TASK6_RUNTIME_FUNCTION_IDS).toContain('npc_trade');
|
||||
expect(TASK5_RUNTIME_OPTION_SCOPES).toEqual(['story', 'combat', 'npc']);
|
||||
|
||||
const request: RuntimeStoryActionRequest = {
|
||||
sessionId: 'runtime-session-1',
|
||||
action: {
|
||||
type: 'story_choice',
|
||||
functionId: 'npc_chat',
|
||||
},
|
||||
};
|
||||
|
||||
expect(request.action.functionId).toBe('npc_chat');
|
||||
});
|
||||
|
||||
test('拆分后的 chat 与 quest assist 契约继续导出运行时类型', () => {
|
||||
const payload: CharacterChatReplyRequest = {
|
||||
worldType: 'WUXIA',
|
||||
playerCharacter: {},
|
||||
targetCharacter: {},
|
||||
storyHistory: [],
|
||||
context: {},
|
||||
conversationHistory: [],
|
||||
conversationSummary: '测试摘要',
|
||||
playerMessage: '近况如何?',
|
||||
targetStatus: {},
|
||||
};
|
||||
|
||||
const stateRequest: RuntimeStoryStateRequest = {
|
||||
sessionId: 'runtime-session-2',
|
||||
};
|
||||
|
||||
expect(payload.playerMessage).toBe('近况如何?');
|
||||
expect(stateRequest.sessionId).toBe('runtime-session-2');
|
||||
expect(QUEST_NARRATIVE_TYPES).toContain('relationship');
|
||||
});
|
||||
});
|
||||
83
packages/shared/src/contracts/rpgRuntimeQuestAssist.ts
Normal file
83
packages/shared/src/contracts/rpgRuntimeQuestAssist.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* RPG 运行时任务辅助与道具意图共享契约。
|
||||
* 该文件只承载 quest / runtime item 辅助类型,不混入 runtime story 主状态。
|
||||
*/
|
||||
import type { JsonObject } from './common';
|
||||
|
||||
export const QUEST_NARRATIVE_TYPES = [
|
||||
'bounty',
|
||||
'escort',
|
||||
'investigation',
|
||||
'retrieval',
|
||||
'relationship',
|
||||
'trial',
|
||||
] as const;
|
||||
export type SharedQuestNarrativeType = (typeof QUEST_NARRATIVE_TYPES)[number];
|
||||
|
||||
export const QUEST_OBJECTIVE_KINDS = [
|
||||
'defeat_hostile_npc',
|
||||
'inspect_treasure',
|
||||
'spar_with_npc',
|
||||
'talk_to_npc',
|
||||
'reach_scene',
|
||||
'deliver_item',
|
||||
] as const;
|
||||
export type SharedQuestObjectiveKind = (typeof QUEST_OBJECTIVE_KINDS)[number];
|
||||
|
||||
export const QUEST_URGENCY_LEVELS = ['low', 'medium', 'high'] as const;
|
||||
export type SharedQuestUrgency = (typeof QUEST_URGENCY_LEVELS)[number];
|
||||
|
||||
export const QUEST_INTIMACY_LEVELS = [
|
||||
'transactional',
|
||||
'cooperative',
|
||||
'trust_based',
|
||||
] as const;
|
||||
export type SharedQuestIntimacy = (typeof QUEST_INTIMACY_LEVELS)[number];
|
||||
|
||||
export const QUEST_REWARD_THEMES = [
|
||||
'currency',
|
||||
'resource',
|
||||
'relationship',
|
||||
'intel',
|
||||
'rare_item',
|
||||
] as const;
|
||||
export type SharedQuestRewardTheme = (typeof QUEST_REWARD_THEMES)[number];
|
||||
|
||||
export const RUNTIME_ITEM_FUNCTIONAL_BIAS_VALUES = [
|
||||
'heal',
|
||||
'mana',
|
||||
'cooldown',
|
||||
'guard',
|
||||
'damage',
|
||||
] as const;
|
||||
export type SharedRuntimeItemFunctionalBias =
|
||||
(typeof RUNTIME_ITEM_FUNCTIONAL_BIAS_VALUES)[number];
|
||||
|
||||
export const RUNTIME_ITEM_TONE_VALUES = [
|
||||
'grim',
|
||||
'mysterious',
|
||||
'martial',
|
||||
'ritual',
|
||||
'survival',
|
||||
] as const;
|
||||
export type SharedRuntimeItemTone = (typeof RUNTIME_ITEM_TONE_VALUES)[number];
|
||||
|
||||
export type RuntimeItemIntentRequest<
|
||||
TContext = JsonObject,
|
||||
TPlan = JsonObject,
|
||||
> = {
|
||||
context: TContext;
|
||||
plans: TPlan[];
|
||||
};
|
||||
|
||||
export type RuntimeItemIntentResponse<TIntent = JsonObject> = {
|
||||
intents: TIntent[];
|
||||
};
|
||||
|
||||
export type QuestGenerationRequest<
|
||||
TState = JsonObject,
|
||||
TEncounter = JsonObject,
|
||||
> = {
|
||||
state: TState;
|
||||
encounter: TEncounter;
|
||||
};
|
||||
136
packages/shared/src/contracts/rpgRuntimeStoryAction.ts
Normal file
136
packages/shared/src/contracts/rpgRuntimeStoryAction.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* RPG runtime story 动作层共享契约。
|
||||
* 将 function id、动作请求与交互元数据从旧 story.ts 中单独收口。
|
||||
*/
|
||||
import type { JsonObject } from './common';
|
||||
|
||||
export type RuntimeAction<
|
||||
TType extends string = string,
|
||||
TPayload = JsonObject,
|
||||
> = {
|
||||
type: TType;
|
||||
functionId?: string;
|
||||
targetId?: string;
|
||||
payload?: TPayload;
|
||||
};
|
||||
|
||||
export type RuntimeActionRequest<
|
||||
TAction extends RuntimeAction = RuntimeAction,
|
||||
> = {
|
||||
sessionId: string;
|
||||
clientVersion?: number;
|
||||
action: TAction;
|
||||
};
|
||||
|
||||
export type RuntimeActionResponse<
|
||||
TViewModel = JsonObject,
|
||||
TPresentation = JsonObject,
|
||||
TPatch = JsonObject,
|
||||
> = {
|
||||
sessionId: string;
|
||||
serverVersion: number;
|
||||
viewModel: TViewModel;
|
||||
presentation: TPresentation;
|
||||
patches: TPatch[];
|
||||
};
|
||||
|
||||
export const TASK5_RUNTIME_FUNCTION_IDS = [
|
||||
'story_continue_adventure',
|
||||
'story_opening_camp_dialogue',
|
||||
'camp_travel_home_scene',
|
||||
'idle_call_out',
|
||||
'idle_explore_forward',
|
||||
'idle_observe_signs',
|
||||
'idle_rest_focus',
|
||||
'idle_travel_next_scene',
|
||||
'battle_attack_basic',
|
||||
'battle_use_skill',
|
||||
'battle_all_in_crush',
|
||||
'battle_escape_breakout',
|
||||
'battle_feint_step',
|
||||
'battle_finisher_window',
|
||||
'battle_guard_break',
|
||||
'battle_probe_pressure',
|
||||
'battle_recover_breath',
|
||||
'npc_chat',
|
||||
'npc_fight',
|
||||
'npc_help',
|
||||
'npc_leave',
|
||||
'npc_preview_talk',
|
||||
'npc_recruit',
|
||||
'npc_spar',
|
||||
] as const;
|
||||
export type Task5RuntimeFunctionId =
|
||||
(typeof TASK5_RUNTIME_FUNCTION_IDS)[number];
|
||||
|
||||
export const TASK6_RUNTIME_FUNCTION_IDS = [
|
||||
'equipment_equip',
|
||||
'equipment_unequip',
|
||||
'forge_craft',
|
||||
'forge_dismantle',
|
||||
'forge_reforge',
|
||||
'inventory_use',
|
||||
'npc_gift',
|
||||
'npc_chat_quest_offer_abandon',
|
||||
'npc_chat_quest_offer_replace',
|
||||
'npc_chat_quest_offer_view',
|
||||
'npc_quest_accept',
|
||||
'npc_quest_turn_in',
|
||||
'npc_trade',
|
||||
'treasure_inspect',
|
||||
'treasure_leave',
|
||||
'treasure_secure',
|
||||
] as const;
|
||||
export type Task6RuntimeFunctionId =
|
||||
(typeof TASK6_RUNTIME_FUNCTION_IDS)[number];
|
||||
|
||||
export const SERVER_RUNTIME_FUNCTION_IDS = [
|
||||
...TASK5_RUNTIME_FUNCTION_IDS,
|
||||
...TASK6_RUNTIME_FUNCTION_IDS,
|
||||
] as const;
|
||||
export type ServerRuntimeFunctionId =
|
||||
(typeof SERVER_RUNTIME_FUNCTION_IDS)[number];
|
||||
|
||||
export const TASK5_RUNTIME_OPTION_SCOPES = ['story', 'combat', 'npc'] as const;
|
||||
export type Task5RuntimeOptionScope =
|
||||
(typeof TASK5_RUNTIME_OPTION_SCOPES)[number];
|
||||
|
||||
export type RuntimeStoryChoicePayload = JsonObject & {
|
||||
optionText?: string;
|
||||
note?: string;
|
||||
releaseNpcId?: string;
|
||||
preludeText?: string;
|
||||
};
|
||||
|
||||
export type RuntimeStoryOptionInteraction =
|
||||
| {
|
||||
kind: 'npc';
|
||||
npcId: string;
|
||||
action:
|
||||
| 'chat'
|
||||
| 'help'
|
||||
| 'fight'
|
||||
| 'leave'
|
||||
| 'quest_offer_abandon'
|
||||
| 'quest_offer_replace'
|
||||
| 'quest_offer_view'
|
||||
| 'recruit'
|
||||
| 'spar'
|
||||
| 'trade'
|
||||
| 'gift'
|
||||
| 'quest_accept'
|
||||
| 'quest_turn_in';
|
||||
questId?: string;
|
||||
}
|
||||
| {
|
||||
kind: 'treasure';
|
||||
action: 'inspect' | 'leave' | 'secure';
|
||||
};
|
||||
|
||||
export type RuntimeStoryChoiceAction = RuntimeAction<
|
||||
'story_choice',
|
||||
RuntimeStoryChoicePayload
|
||||
> & {
|
||||
functionId: string;
|
||||
targetId?: string;
|
||||
};
|
||||
146
packages/shared/src/contracts/rpgRuntimeStoryState.ts
Normal file
146
packages/shared/src/contracts/rpgRuntimeStoryState.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* RPG runtime story 状态与响应共享契约。
|
||||
* 该文件只负责 view model、presentation、patch 与 snapshot 回包结构。
|
||||
*/
|
||||
import type { JsonObject } from './common';
|
||||
import type { SavedGameSnapshot, SavedGameSnapshotInput } from './runtime';
|
||||
import type {
|
||||
RuntimeActionRequest,
|
||||
RuntimeActionResponse,
|
||||
RuntimeStoryChoiceAction,
|
||||
RuntimeStoryChoicePayload,
|
||||
RuntimeStoryOptionInteraction,
|
||||
Task5RuntimeOptionScope,
|
||||
} from './rpgRuntimeStoryAction';
|
||||
|
||||
export type RuntimeStoryOptionView = {
|
||||
functionId: string;
|
||||
actionText: string;
|
||||
detailText?: string;
|
||||
scope: Task5RuntimeOptionScope;
|
||||
interaction?: RuntimeStoryOptionInteraction;
|
||||
payload?: RuntimeStoryChoicePayload;
|
||||
disabled?: boolean;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
export type RuntimeStoryPlayerViewModel = {
|
||||
hp: number;
|
||||
maxHp: number;
|
||||
mana: number;
|
||||
maxMana: number;
|
||||
};
|
||||
|
||||
export type RuntimeStoryCompanionViewModel = {
|
||||
npcId: string;
|
||||
characterId?: string;
|
||||
joinedAtAffinity: number;
|
||||
};
|
||||
|
||||
export type RuntimeStoryEncounterViewModel = {
|
||||
id: string;
|
||||
kind: 'npc' | 'treasure';
|
||||
npcName: string;
|
||||
hostile: boolean;
|
||||
affinity?: number;
|
||||
recruited?: boolean;
|
||||
interactionActive: boolean;
|
||||
battleMode?: 'fight' | 'spar' | null;
|
||||
};
|
||||
|
||||
export type RuntimeStoryStatusViewModel = {
|
||||
inBattle: boolean;
|
||||
npcInteractionActive: boolean;
|
||||
currentNpcBattleMode: 'fight' | 'spar' | null;
|
||||
currentNpcBattleOutcome: 'fight_victory' | 'spar_complete' | null;
|
||||
};
|
||||
|
||||
export type RuntimeBattlePresentation = {
|
||||
targetId?: string;
|
||||
targetName?: string;
|
||||
damageDealt?: number;
|
||||
damageTaken?: number;
|
||||
outcome?: 'ongoing' | 'victory' | 'spar_complete' | 'escaped';
|
||||
};
|
||||
|
||||
export type RuntimeStoryViewModel = {
|
||||
player: RuntimeStoryPlayerViewModel;
|
||||
encounter: RuntimeStoryEncounterViewModel | null;
|
||||
companions: RuntimeStoryCompanionViewModel[];
|
||||
availableOptions: RuntimeStoryOptionView[];
|
||||
status: RuntimeStoryStatusViewModel;
|
||||
};
|
||||
|
||||
export type RuntimeStoryPresentation = {
|
||||
actionText: string;
|
||||
resultText: string;
|
||||
storyText: string;
|
||||
options: RuntimeStoryOptionView[];
|
||||
toast?: string | null;
|
||||
battle?: RuntimeBattlePresentation | null;
|
||||
};
|
||||
|
||||
export type RuntimeStoryPatch =
|
||||
| {
|
||||
type: 'story_history_append';
|
||||
actionText: string;
|
||||
resultText: string;
|
||||
}
|
||||
| {
|
||||
type: 'npc_affinity_changed';
|
||||
npcId: string;
|
||||
previousAffinity: number;
|
||||
nextAffinity: number;
|
||||
}
|
||||
| {
|
||||
type: 'battle_resolved';
|
||||
functionId: string;
|
||||
targetId?: string;
|
||||
damageDealt?: number;
|
||||
damageTaken?: number;
|
||||
outcome: 'ongoing' | 'victory' | 'spar_complete' | 'escaped';
|
||||
}
|
||||
| {
|
||||
type: 'status_changed';
|
||||
inBattle: boolean;
|
||||
npcInteractionActive: boolean;
|
||||
currentNpcBattleMode: 'fight' | 'spar' | null;
|
||||
currentNpcBattleOutcome: 'fight_victory' | 'spar_complete' | null;
|
||||
}
|
||||
| {
|
||||
type: 'encounter_changed';
|
||||
encounterId: string | null;
|
||||
};
|
||||
|
||||
export type RuntimeStoryActionRequest =
|
||||
RuntimeActionRequest<RuntimeStoryChoiceAction> & {
|
||||
snapshot?: SavedGameSnapshotInput;
|
||||
};
|
||||
|
||||
export type RuntimeStoryStateRequest<
|
||||
TSnapshotGameState = JsonObject,
|
||||
TSnapshotCurrentStory = JsonObject,
|
||||
> = {
|
||||
sessionId: string;
|
||||
clientVersion?: number;
|
||||
snapshot?: SavedGameSnapshotInput<
|
||||
TSnapshotGameState,
|
||||
string,
|
||||
TSnapshotCurrentStory
|
||||
>;
|
||||
};
|
||||
|
||||
export type RuntimeStoryActionResponse<
|
||||
TSnapshotGameState = JsonObject,
|
||||
TSnapshotCurrentStory = JsonObject,
|
||||
> = RuntimeActionResponse<
|
||||
RuntimeStoryViewModel,
|
||||
RuntimeStoryPresentation,
|
||||
RuntimeStoryPatch
|
||||
> & {
|
||||
snapshot: SavedGameSnapshot<
|
||||
TSnapshotGameState,
|
||||
string,
|
||||
TSnapshotCurrentStory
|
||||
>;
|
||||
};
|
||||
@@ -1,499 +0,0 @@
|
||||
import type { JsonObject } from './common';
|
||||
import type { SavedGameSnapshot } from './runtime';
|
||||
|
||||
export const QUEST_NARRATIVE_TYPES = [
|
||||
'bounty',
|
||||
'escort',
|
||||
'investigation',
|
||||
'retrieval',
|
||||
'relationship',
|
||||
'trial',
|
||||
] as const;
|
||||
export type SharedQuestNarrativeType = (typeof QUEST_NARRATIVE_TYPES)[number];
|
||||
|
||||
export const QUEST_OBJECTIVE_KINDS = [
|
||||
'defeat_hostile_npc',
|
||||
'inspect_treasure',
|
||||
'spar_with_npc',
|
||||
'talk_to_npc',
|
||||
'reach_scene',
|
||||
'deliver_item',
|
||||
] as const;
|
||||
export type SharedQuestObjectiveKind = (typeof QUEST_OBJECTIVE_KINDS)[number];
|
||||
|
||||
export const QUEST_URGENCY_LEVELS = ['low', 'medium', 'high'] as const;
|
||||
export type SharedQuestUrgency = (typeof QUEST_URGENCY_LEVELS)[number];
|
||||
|
||||
export const QUEST_INTIMACY_LEVELS = [
|
||||
'transactional',
|
||||
'cooperative',
|
||||
'trust_based',
|
||||
] as const;
|
||||
export type SharedQuestIntimacy = (typeof QUEST_INTIMACY_LEVELS)[number];
|
||||
|
||||
export const QUEST_REWARD_THEMES = [
|
||||
'currency',
|
||||
'resource',
|
||||
'relationship',
|
||||
'intel',
|
||||
'rare_item',
|
||||
] as const;
|
||||
export type SharedQuestRewardTheme = (typeof QUEST_REWARD_THEMES)[number];
|
||||
|
||||
export const RUNTIME_ITEM_FUNCTIONAL_BIAS_VALUES = [
|
||||
'heal',
|
||||
'mana',
|
||||
'cooldown',
|
||||
'guard',
|
||||
'damage',
|
||||
] as const;
|
||||
export type SharedRuntimeItemFunctionalBias =
|
||||
(typeof RUNTIME_ITEM_FUNCTIONAL_BIAS_VALUES)[number];
|
||||
|
||||
export const RUNTIME_ITEM_TONE_VALUES = [
|
||||
'grim',
|
||||
'mysterious',
|
||||
'martial',
|
||||
'ritual',
|
||||
'survival',
|
||||
] as const;
|
||||
export type SharedRuntimeItemTone = (typeof RUNTIME_ITEM_TONE_VALUES)[number];
|
||||
|
||||
export type StoryRequestOptionsPayload = {
|
||||
availableOptions?: JsonObject[];
|
||||
optionCatalog?: JsonObject[];
|
||||
};
|
||||
|
||||
export type StoryRequestPayload<TWorldType extends string = string> = {
|
||||
worldType: TWorldType;
|
||||
character: JsonObject;
|
||||
monsters?: JsonObject[];
|
||||
history?: JsonObject[];
|
||||
choice?: string;
|
||||
context: JsonObject;
|
||||
requestOptions?: StoryRequestOptionsPayload;
|
||||
};
|
||||
|
||||
export type PlainTextPromptRequest = {
|
||||
systemPrompt: string;
|
||||
userPrompt: string;
|
||||
};
|
||||
|
||||
export type PlainTextResponse = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type NpcChatTurnLimitReason = 'negative_affinity';
|
||||
|
||||
export type NpcChatTurnClosingMode = 'free' | 'foreshadow_close';
|
||||
|
||||
export type NpcChatTurnDirective = {
|
||||
sceneActId?: string | null;
|
||||
turnLimit?: number | null;
|
||||
remainingTurns?: number | null;
|
||||
limitReason?: NpcChatTurnLimitReason | null;
|
||||
closingMode?: NpcChatTurnClosingMode | null;
|
||||
forceExitAfterTurn?: boolean;
|
||||
};
|
||||
|
||||
export type NpcChatTurnCompletionDirective = {
|
||||
turnLimit?: number | null;
|
||||
remainingTurns?: number | null;
|
||||
forceExit?: boolean;
|
||||
closingMode?: NpcChatTurnClosingMode;
|
||||
};
|
||||
|
||||
export type CharacterChatReplyRequest<
|
||||
TCharacter = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TTargetStatus = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
playerCharacter: TCharacter;
|
||||
targetCharacter: TCharacter;
|
||||
storyHistory: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory: TConversationTurn[];
|
||||
conversationSummary: string;
|
||||
playerMessage: string;
|
||||
targetStatus: TTargetStatus;
|
||||
};
|
||||
|
||||
export type CharacterChatSuggestionsRequest<
|
||||
TCharacter = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TTargetStatus = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
playerCharacter: TCharacter;
|
||||
targetCharacter: TCharacter;
|
||||
storyHistory: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory: TConversationTurn[];
|
||||
conversationSummary: string;
|
||||
targetStatus: TTargetStatus;
|
||||
};
|
||||
|
||||
export type CharacterChatSummaryRequest<
|
||||
TCharacter = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TTargetStatus = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
playerCharacter: TCharacter;
|
||||
targetCharacter: TCharacter;
|
||||
storyHistory: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory: TConversationTurn[];
|
||||
previousSummary: string;
|
||||
targetStatus: TTargetStatus;
|
||||
};
|
||||
|
||||
export type NpcChatDialogueRequest<
|
||||
TCharacter = unknown,
|
||||
TEncounter = unknown,
|
||||
TMonster = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
character: TCharacter;
|
||||
encounter: TEncounter;
|
||||
monsters: TMonster[];
|
||||
history: TStoryMoment[];
|
||||
context: TContext;
|
||||
topic: string;
|
||||
resultSummary: string;
|
||||
npcInitiatesConversation?: boolean;
|
||||
};
|
||||
|
||||
export type NpcChatTurnRequest<
|
||||
TCharacter = unknown,
|
||||
TEncounter = unknown,
|
||||
TMonster = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
TConversationTurn = unknown,
|
||||
TCombatContext = unknown,
|
||||
TNpcState = unknown,
|
||||
TQuestOfferState = unknown,
|
||||
TQuestOfferEncounter = unknown,
|
||||
TChatDirective = NpcChatTurnDirective,
|
||||
> = {
|
||||
worldType: string;
|
||||
character?: TCharacter;
|
||||
player?: TCharacter;
|
||||
encounter: TEncounter;
|
||||
monsters: TMonster[];
|
||||
history: TStoryMoment[];
|
||||
context: TContext;
|
||||
conversationHistory?: TConversationTurn[];
|
||||
dialogue?: TConversationTurn[];
|
||||
combatContext?: TCombatContext | null;
|
||||
playerMessage: string;
|
||||
npcState: TNpcState;
|
||||
npcInitiatesConversation?: boolean;
|
||||
questOfferContext?: {
|
||||
state: TQuestOfferState;
|
||||
encounter: TQuestOfferEncounter;
|
||||
turnCount: number;
|
||||
} | null;
|
||||
chatDirective?: TChatDirective | null;
|
||||
};
|
||||
|
||||
export type NpcChatPendingQuestOffer<TQuest = unknown> = {
|
||||
quest: TQuest;
|
||||
introText?: string;
|
||||
};
|
||||
|
||||
export type NpcChatTurnResult<TQuest = unknown> = {
|
||||
npcReply: string;
|
||||
affinityDelta: number;
|
||||
affinityText: string;
|
||||
suggestions: string[];
|
||||
pendingQuestOffer?: NpcChatPendingQuestOffer<TQuest> | null;
|
||||
chatDirective?: NpcChatTurnCompletionDirective | null;
|
||||
};
|
||||
|
||||
export type NpcRecruitDialogueRequest<
|
||||
TCharacter = unknown,
|
||||
TEncounter = unknown,
|
||||
TMonster = unknown,
|
||||
TStoryMoment = unknown,
|
||||
TContext = unknown,
|
||||
> = {
|
||||
worldType: string;
|
||||
character: TCharacter;
|
||||
encounter: TEncounter;
|
||||
monsters: TMonster[];
|
||||
history: TStoryMoment[];
|
||||
context: TContext;
|
||||
invitationText: string;
|
||||
recruitSummary: string;
|
||||
};
|
||||
|
||||
export type RuntimeItemIntentRequest<
|
||||
TContext = JsonObject,
|
||||
TPlan = JsonObject,
|
||||
> = {
|
||||
context: TContext;
|
||||
plans: TPlan[];
|
||||
};
|
||||
|
||||
export type RuntimeItemIntentResponse<TIntent = JsonObject> = {
|
||||
intents: TIntent[];
|
||||
};
|
||||
|
||||
export type QuestGenerationRequest<
|
||||
TState = JsonObject,
|
||||
TEncounter = JsonObject,
|
||||
> = {
|
||||
state: TState;
|
||||
encounter: TEncounter;
|
||||
};
|
||||
|
||||
export type RuntimeAction<
|
||||
TType extends string = string,
|
||||
TPayload = JsonObject,
|
||||
> = {
|
||||
type: TType;
|
||||
functionId?: string;
|
||||
targetId?: string;
|
||||
payload?: TPayload;
|
||||
};
|
||||
|
||||
export type RuntimeActionRequest<
|
||||
TAction extends RuntimeAction = RuntimeAction,
|
||||
> = {
|
||||
sessionId: string;
|
||||
clientVersion?: number;
|
||||
action: TAction;
|
||||
};
|
||||
|
||||
export type RuntimeActionResponse<
|
||||
TViewModel = JsonObject,
|
||||
TPresentation = JsonObject,
|
||||
TPatch = JsonObject,
|
||||
> = {
|
||||
sessionId: string;
|
||||
serverVersion: number;
|
||||
viewModel: TViewModel;
|
||||
presentation: TPresentation;
|
||||
patches: TPatch[];
|
||||
};
|
||||
|
||||
export const TASK5_RUNTIME_FUNCTION_IDS = [
|
||||
'story_continue_adventure',
|
||||
'story_opening_camp_dialogue',
|
||||
'camp_travel_home_scene',
|
||||
'idle_call_out',
|
||||
'idle_explore_forward',
|
||||
'idle_observe_signs',
|
||||
'idle_rest_focus',
|
||||
'idle_travel_next_scene',
|
||||
'battle_attack_basic',
|
||||
'battle_use_skill',
|
||||
'battle_all_in_crush',
|
||||
'battle_escape_breakout',
|
||||
'battle_feint_step',
|
||||
'battle_finisher_window',
|
||||
'battle_guard_break',
|
||||
'battle_probe_pressure',
|
||||
'battle_recover_breath',
|
||||
'npc_chat',
|
||||
'npc_fight',
|
||||
'npc_help',
|
||||
'npc_leave',
|
||||
'npc_preview_talk',
|
||||
'npc_recruit',
|
||||
'npc_spar',
|
||||
] as const;
|
||||
export type Task5RuntimeFunctionId =
|
||||
(typeof TASK5_RUNTIME_FUNCTION_IDS)[number];
|
||||
|
||||
export const TASK6_RUNTIME_FUNCTION_IDS = [
|
||||
'equipment_equip',
|
||||
'equipment_unequip',
|
||||
'forge_craft',
|
||||
'forge_dismantle',
|
||||
'forge_reforge',
|
||||
'inventory_use',
|
||||
'npc_gift',
|
||||
'npc_quest_accept',
|
||||
'npc_quest_turn_in',
|
||||
'npc_trade',
|
||||
'treasure_inspect',
|
||||
'treasure_leave',
|
||||
'treasure_secure',
|
||||
] as const;
|
||||
export type Task6RuntimeFunctionId =
|
||||
(typeof TASK6_RUNTIME_FUNCTION_IDS)[number];
|
||||
|
||||
export const SERVER_RUNTIME_FUNCTION_IDS = [
|
||||
...TASK5_RUNTIME_FUNCTION_IDS,
|
||||
...TASK6_RUNTIME_FUNCTION_IDS,
|
||||
] as const;
|
||||
export type ServerRuntimeFunctionId =
|
||||
(typeof SERVER_RUNTIME_FUNCTION_IDS)[number];
|
||||
|
||||
export const TASK5_RUNTIME_OPTION_SCOPES = ['story', 'combat', 'npc'] as const;
|
||||
export type Task5RuntimeOptionScope =
|
||||
(typeof TASK5_RUNTIME_OPTION_SCOPES)[number];
|
||||
|
||||
export type RuntimeStoryChoicePayload = JsonObject & {
|
||||
optionText?: string;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type RuntimeStoryOptionInteraction =
|
||||
| {
|
||||
kind: 'npc';
|
||||
npcId: string;
|
||||
action:
|
||||
| 'chat'
|
||||
| 'help'
|
||||
| 'fight'
|
||||
| 'leave'
|
||||
| 'recruit'
|
||||
| 'spar'
|
||||
| 'trade'
|
||||
| 'gift'
|
||||
| 'quest_accept'
|
||||
| 'quest_turn_in';
|
||||
questId?: string;
|
||||
}
|
||||
| {
|
||||
kind: 'treasure';
|
||||
action: 'inspect' | 'leave' | 'secure';
|
||||
};
|
||||
|
||||
export type RuntimeStoryChoiceAction = RuntimeAction<
|
||||
'story_choice',
|
||||
RuntimeStoryChoicePayload
|
||||
> & {
|
||||
functionId: string;
|
||||
targetId?: string;
|
||||
};
|
||||
|
||||
export type RuntimeStoryOptionView = {
|
||||
functionId: string;
|
||||
actionText: string;
|
||||
detailText?: string;
|
||||
scope: Task5RuntimeOptionScope;
|
||||
interaction?: RuntimeStoryOptionInteraction;
|
||||
payload?: RuntimeStoryChoicePayload;
|
||||
disabled?: boolean;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
export type RuntimeStoryPlayerViewModel = {
|
||||
hp: number;
|
||||
maxHp: number;
|
||||
mana: number;
|
||||
maxMana: number;
|
||||
};
|
||||
|
||||
export type RuntimeStoryCompanionViewModel = {
|
||||
npcId: string;
|
||||
characterId?: string;
|
||||
joinedAtAffinity: number;
|
||||
};
|
||||
|
||||
export type RuntimeStoryEncounterViewModel = {
|
||||
id: string;
|
||||
kind: 'npc' | 'treasure';
|
||||
npcName: string;
|
||||
hostile: boolean;
|
||||
affinity?: number;
|
||||
recruited?: boolean;
|
||||
interactionActive: boolean;
|
||||
battleMode?: 'fight' | 'spar' | null;
|
||||
};
|
||||
|
||||
export type RuntimeStoryStatusViewModel = {
|
||||
inBattle: boolean;
|
||||
npcInteractionActive: boolean;
|
||||
currentNpcBattleMode: 'fight' | 'spar' | null;
|
||||
currentNpcBattleOutcome: 'fight_victory' | 'spar_complete' | null;
|
||||
};
|
||||
|
||||
export type RuntimeBattlePresentation = {
|
||||
targetId?: string;
|
||||
targetName?: string;
|
||||
damageDealt?: number;
|
||||
damageTaken?: number;
|
||||
outcome?: 'ongoing' | 'victory' | 'spar_complete' | 'escaped';
|
||||
};
|
||||
|
||||
export type RuntimeStoryViewModel = {
|
||||
player: RuntimeStoryPlayerViewModel;
|
||||
encounter: RuntimeStoryEncounterViewModel | null;
|
||||
companions: RuntimeStoryCompanionViewModel[];
|
||||
availableOptions: RuntimeStoryOptionView[];
|
||||
status: RuntimeStoryStatusViewModel;
|
||||
};
|
||||
|
||||
export type RuntimeStoryPresentation = {
|
||||
actionText: string;
|
||||
resultText: string;
|
||||
storyText: string;
|
||||
options: RuntimeStoryOptionView[];
|
||||
toast?: string | null;
|
||||
battle?: RuntimeBattlePresentation | null;
|
||||
};
|
||||
|
||||
export type RuntimeStoryPatch =
|
||||
| {
|
||||
type: 'story_history_append';
|
||||
actionText: string;
|
||||
resultText: string;
|
||||
}
|
||||
| {
|
||||
type: 'npc_affinity_changed';
|
||||
npcId: string;
|
||||
previousAffinity: number;
|
||||
nextAffinity: number;
|
||||
}
|
||||
| {
|
||||
type: 'battle_resolved';
|
||||
functionId: string;
|
||||
targetId?: string;
|
||||
damageDealt?: number;
|
||||
damageTaken?: number;
|
||||
outcome: 'ongoing' | 'victory' | 'spar_complete' | 'escaped';
|
||||
}
|
||||
| {
|
||||
type: 'status_changed';
|
||||
inBattle: boolean;
|
||||
npcInteractionActive: boolean;
|
||||
currentNpcBattleMode: 'fight' | 'spar' | null;
|
||||
currentNpcBattleOutcome: 'fight_victory' | 'spar_complete' | null;
|
||||
}
|
||||
| {
|
||||
type: 'encounter_changed';
|
||||
encounterId: string | null;
|
||||
};
|
||||
|
||||
export type RuntimeStoryActionRequest =
|
||||
RuntimeActionRequest<RuntimeStoryChoiceAction>;
|
||||
|
||||
export type RuntimeStoryActionResponse<
|
||||
TSnapshotGameState = JsonObject,
|
||||
TSnapshotCurrentStory = JsonObject,
|
||||
> = RuntimeActionResponse<
|
||||
RuntimeStoryViewModel,
|
||||
RuntimeStoryPresentation,
|
||||
RuntimeStoryPatch
|
||||
> & {
|
||||
snapshot: SavedGameSnapshot<
|
||||
TSnapshotGameState,
|
||||
string,
|
||||
TSnapshotCurrentStory
|
||||
>;
|
||||
};
|
||||
@@ -1,8 +1,19 @@
|
||||
export * from './assets/qwenSprite';
|
||||
export * from './contracts/auth';
|
||||
export * from './contracts/common';
|
||||
export type * from './contracts/customWorldAgent';
|
||||
export * from './contracts/rpgAgentActions';
|
||||
export * from './contracts/rpgAgentAnchors';
|
||||
export * from './contracts/rpgAgentDraft';
|
||||
export * from './contracts/rpgAgentSession';
|
||||
export * from './contracts/rpgCreationFixtures';
|
||||
export * from './contracts/rpgCreationPreview';
|
||||
export * from './contracts/rpgCreationWorkSummary';
|
||||
export * from './contracts/rpgRuntimeChat';
|
||||
export * from './contracts/rpgRuntimeQuestAssist';
|
||||
export * from './contracts/rpgRuntimeStoryAction';
|
||||
export * from './contracts/rpgRuntimeStoryState';
|
||||
export * from './contracts/runtime';
|
||||
export * from './contracts/story';
|
||||
export * from './http';
|
||||
export * from './llm/narrativeLanguage';
|
||||
export * from './llm/parsers';
|
||||
|
||||
Reference in New Issue
Block a user