From 224a26d318b38cebeae69aca5f3c38b0bae15c8b Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Thu, 21 May 2026 17:49:07 +0800 Subject: [PATCH] fix: tolerate null legacy custom-world profile --- .hermes/shared-memory/pitfalls.md | 8 +++ ...玩法创作】平å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md | 2 + .../module-custom-world/src/application.rs | 51 ++++++++++++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 88b413fb..9c47745c 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -398,6 +398,14 @@ - 验è¯ï¼š`npm run test -- src/data/customWorldLibrary.test.ts src/components/CustomWorldResultView.test.tsx`,确认生æˆåŽå³ä½¿çˆ¶å±‚åšä¸€æ¬¡å½’一化回写,开局 CG ä»ç»§ç»­æ˜¾ç¤ºã€‚ - å…³è”:`src/data/customWorldLibrary.ts`ã€`src/components/rpg-creation-result/RpgCreationResultViewImpl.tsx`ã€`src/components/CustomWorldEntityCatalog.tsx`。 +## RPG å‘布报 legacy_result_profile_json éžæ³•先查 null 兼容 + +- 现象:RPG 结果页å‘布动作返回 `UPSTREAM_ERROR`,SpacetimeDB details 里是 `custom_world.compile.legacy_result_profile_json 䏿˜¯åˆæ³• JSON object`。 +- 原因:`publish_world` å‰ç«¯å¥‘约åªè¦æ±‚ `{ action: 'publish_world' }`ï¼›`ExecuteCustomWorldAgentActionRequest.legacy_result_profile` 是å¯é€‰å­—æ®µï¼Œç» HTTP / serde / SpacetimeDB payload 传递时å¯èƒ½æ˜¾å¼æˆä¸º JSON `null`ã€‚æ—§çš„ç¼–è¯‘å™¨åªæŽ¥å— object 或缺çœï¼ŒæŠŠ `Some("null")` 当æˆéžæ³• legacy JSON。 +- 处ç†ï¼š`module-custom-world` çš„ optional JSON object è§£æžè¦æŠŠ `null` 视为未æä¾›ï¼Œä»æ‹’ç»æ•°ç»„ã€å­—ç¬¦ä¸²ã€æ•°å­—å’Œå JSON;正å¼å‘布继续以 session `draft_profile_json` 为è‰ç¨¿çœŸç›¸ã€‚ +- 验è¯ï¼š`cargo test -p module-custom-world published_profile_compile --manifest-path server-rs/Cargo.toml`。 +- å…³è”:`server-rs/crates/module-custom-world/src/application.rs`ã€`server-rs/crates/spacetime-module/src/custom_world.rs`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 + ## 本地脚本调 VectorEngine 生图å¡ä½å…ˆåŒºåˆ† fetch 首部超时 - 现象:用 Node `fetch` 直接请求 `POST /v1/images/generations`,已ç»è®¾ç½®è¾ƒé•¿çš„ AbortController 超时,但ä»åœ¨çº¦ 180 到 300 ç§’åŽæŠ› `AbortError`ã€`TypeError: fetch failed` 或 `UND_ERR_HEADERS_TIMEOUT`ï¼›åŒä¸€ prompt 改用原生 `https.request` å¯ä»¥åœ¨è¾ƒçŸ­æ—¶é—´å†…æˆåŠŸè¿”å›žå›¾ç‰‡ã€‚ diff --git a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md index eb07fc54..2584be6b 100644 --- a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md +++ b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md @@ -50,6 +50,8 @@ RPG API 仿²¿ç”¨åކå²å‘½å空间:`/api/runtime/custom-world*`ã€`/api/story RPG Agent 结果页å‘布动作的å‰ç«¯å¥‘约åªä¿è¯æäº¤ `{ action: 'publish_world' }`ï¼›åŽç«¯å‘å¸ƒæ—¶ä»¥å½“å‰ `custom_world_agent_session.draft_profile_json` 为è‰ç¨¿çœŸç›¸ï¼Œä»Ž `settingText`ã€`creatorIntent.rawSettingText`ã€`creatorIntent.worldHook`ã€`worldHook`ã€`anchorContent.worldPromise(.hook)`ã€`summary`ã€`name/title` 便¬¡æ´¾ç”Ÿæ­£å¼ `setting_text`ï¼Œæœ€åŽæ‰å›žé€€ `seed_text`。ä¸è¦æŠŠ `seed_text` å½“ä½œå”¯ä¸€è®¾å®šæ¥æºï¼Œæ—§ä¼šè¯å¯èƒ½ä¸ºç©ºã€‚ +`legacyResultProfile` åªä½œä¸ºåކå²ç»“果页 profile 兼容兜底;`publish_world` è¯·æ±‚ç¼ºçœæˆ–显å¼ä¸º `null` 时等价于未æä¾›ï¼Œç¼–è¯‘æ­£å¼ profile æ—¶ä¸å¾—因此报 `custom_world.compile.legacy_result_profile_json 䏿˜¯åˆæ³• JSON object`。真正的数组ã€å­—ç¬¦ä¸²ã€æ•°å­—ç­‰éž object legacy è½½è·ä»åº”æ‹’ç»ã€‚ + RPG 结果页开局 CG 是 `profile.openingCg` 资产槽ä½ï¼š`api-server` è´Ÿè´£ VectorEngine / OSS 副作用并返回故事æ¿å’Œè§†é¢‘引用,å‰ç«¯åªæŠŠç»“æžœå†™å›žå½“å‰ profileï¼›`sync_result_profile`ã€ä½œå“库ä¿å­˜å’Œ `normalizeCustomWorldProfileRecord` 都必须ä¿ç•™è¯¥æ§½ä½ã€‚è‹¥ç”ŸæˆæˆåŠŸåŽç”»é¢çŸ­æš‚显示åˆå˜å›žç©ºç™½ï¼Œä¼˜å…ˆæ£€æŸ¥çˆ¶å±‚釿–°åŒæ­¥æˆ– profile å½’ä¸€åŒ–æ˜¯å¦æŠŠ `openingCg` ä¸¢æŽ‰ï¼Œè€Œä¸æ˜¯å…ˆæ€€ç–‘已生æˆèµ„æºæœ¬èº«å¤±æ•ˆã€‚ ## 拼图 diff --git a/server-rs/crates/module-custom-world/src/application.rs b/server-rs/crates/module-custom-world/src/application.rs index a518b3d1..57f8f41c 100644 --- a/server-rs/crates/module-custom-world/src/application.rs +++ b/server-rs/crates/module-custom-world/src/application.rs @@ -694,7 +694,13 @@ fn parse_optional_json_object( error: CustomWorldFieldError, ) -> Result, CustomWorldFieldError> { match normalize_optional_json_slice(value) { - Some(value) => parse_required_json_object(&value, error), + Some(value) => match serde_json::from_str::(&value) { + Ok(Value::Object(object)) => Ok(object), + // 中文注释:跨层å¯é€‰å­—æ®µç» serde 结构体åºåˆ—化åŽå¯èƒ½æ˜¾å¼è½æˆ nullï¼› + // 对 optional JSON object 而言 null 等价于未æä¾›ï¼Œä¸èƒ½é˜»æ–­å‘布链路。 + Ok(Value::Null) => Ok(Map::new()), + _ => Err(error), + }, None => Ok(Map::new()), } } @@ -1018,6 +1024,49 @@ mod tests { use super::*; use serde_json::json; + fn build_test_compile_input( + legacy_result_profile_json: Option, + ) -> CustomWorldPublishedProfileCompileInput { + CustomWorldPublishedProfileCompileInput { + session_id: "session-1".to_string(), + profile_id: "cwprof_001".to_string(), + owner_user_id: "user-1".to_string(), + draft_profile_json: json!({ + "name": "潮雾列岛", + "summary": "群岛与旧ç¯å¡”之间的沉船疑案。", + "playableNpcs": [], + "storyNpcs": [], + "landmarks": [] + }) + .to_string(), + legacy_result_profile_json, + setting_text: "海图会在åˆå¤œæ”¹å†™ç¾¤å²›èˆªè·¯ã€‚".to_string(), + author_display_name: "创作者".to_string(), + updated_at_micros: 1, + } + } + + #[test] + fn published_profile_compile_treats_null_legacy_result_profile_as_absent() { + let snapshot = build_custom_world_published_profile_compile_snapshot( + build_test_compile_input(Some("null".to_string())), + ) + .expect("null legacy result profile should be treated as absent"); + + assert_eq!(snapshot.profile_id, "cwprof_001"); + assert_eq!(snapshot.world_name, "潮雾列岛"); + } + + #[test] + fn published_profile_compile_rejects_non_object_legacy_result_profile() { + let error = build_custom_world_published_profile_compile_snapshot( + build_test_compile_input(Some("[]".to_string())), + ) + .expect_err("array legacy result profile should still be invalid"); + + assert_eq!(error, CustomWorldFieldError::InvalidLegacyResultProfileJson); + } + #[test] fn publish_setting_text_falls_back_to_draft_profile_when_seed_is_empty() { let payload = Map::new();