From adc57ba49be2e49cc33045b0ab49a8453cade4a3 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 20 Apr 2026 23:07:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E7=90=86=E5=90=8E=E7=AB=AF=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codex/environments/environment.toml | 11 + .../NODE_BACKEND_MODULE_AND_API_INDEX.md | 397 +++ docs/technical/README.md | 1 + package.json | 1 + .../manifests/backend-capability-index.json | 2250 +++++++++++++++++ server-node/package.json | 3 +- .../generateBackendCapabilityArtifacts.ts | 372 +++ .../src/manifest/backendCapabilityManifest.ts | 1696 +++++++++++++ .../modules/assets/characterAssetRoutes.ts | 50 +- .../src/modules/assets/qwenSpriteRoutes.ts | 13 +- .../src/modules/editor/editorRoutes.ts | 4 + 11 files changed, 4777 insertions(+), 21 deletions(-) create mode 100644 .codex/environments/environment.toml create mode 100644 docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md create mode 100644 server-node/manifests/backend-capability-index.json create mode 100644 server-node/scripts/generateBackendCapabilityArtifacts.ts create mode 100644 server-node/src/manifest/backendCapabilityManifest.ts diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml new file mode 100644 index 00000000..5bfac747 --- /dev/null +++ b/.codex/environments/environment.toml @@ -0,0 +1,11 @@ +# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY +version = 1 +name = "Genarrative" + +[setup] +script = ''' +npm install +cd ./node-server +npm install +cd .. +''' diff --git a/docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md b/docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md new file mode 100644 index 00000000..faf61b64 --- /dev/null +++ b/docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md @@ -0,0 +1,397 @@ +# Node 后端模块与接口索引 + +> 该文档由 `server-node/src/manifest/backendCapabilityManifest.ts` 自动生成。 +> 生成命令:`npm run server-node:manifest:backend` +> 生成时间:`2026-04-20T14:26:38.663Z` + +## 总览 + +- 对外挂载面:6 个 +- 已登记路由:96 条 +- 内部模块目录:12 个 +- 公开接口:10 条 +- JWT 接口:69 条 +- 受环境开关控制的接口:17 条 +- 流式接口:6 条 + +## 产物 + +- JSON 清单:`server-node/manifests/backend-capability-index.json` +- Markdown 索引:`docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md` +- Manifest 源:`server-node/src/manifest/backendCapabilityManifest.ts` + +## 对外挂载面 + +### 资产生成工具面 + +- 标识:`assets` +- 路由数:14 +- 入口:`server-node/src/app.ts -> /api/assets -> createCharacterAssetRoutes`;`server-node/src/app.ts -> /api/assets/qwen-sprite -> createQwenSpriteRoutes` +- 关联模块:`assets` +- 责任: + - 生成角色主形象、动作、动作模板与工作流缓存。 + - 承接 Qwen 精灵表主图、整表、修帧与保存链路。 + - 把产物发布到 `public/generated-*` 目录并落地局部 manifest。 +- 主要服务边界: + - 负责对接 DashScope、Ark 等外部媒体供应商,但不维护 runtime 快照与业务状态。 + - 统一受 `ASSETS_API_ENABLED` 开关控制,产物以文件与 JSON manifest 形式落在仓库工作区。 + +### 鉴权与会话面 + +- 标识:`auth` +- 路由数:17 +- 入口:`server-node/src/app.ts -> /api/auth -> createAuthRoutes` +- 关联模块:无 +- 责任: + - 承接本地账号、短信验证码与微信登录流程。 + - 管理 refresh session、用户信息、会话吊销、审计日志与风险拦截。 +- 主要服务边界: + - HTTP 层只做 schema 校验、请求上下文拼装与 Cookie 管理,核心鉴权逻辑统一收口到 `server-node/src/auth/*`。 + - 用户、身份、会话、风控与短信事件等持久化职责全部下沉到 repository 层,避免路由直接碰数据库细节。 + +### 编辑器工具面 + +- 标识:`editor` +- 路由数:3 +- 入口:`server-node/src/app.ts -> /api/editor -> createEditorRoutes` +- 关联模块:`editor` +- 责任: + - 读取编辑器资源 JSON。 + - 回写编辑器覆盖文件。 + - 枚举 `public/Icons` 下的物品图标资源。 +- 主要服务边界: + - 只对工作区文件系统与 `public` 目录负责,不参与运行时数据库存储。 + - 统一受 `EDITOR_API_ENABLED` 开关控制,生产环境可按需关闭。 + +### 基础健康检查 + +- 标识:`health` +- 路由数:1 +- 入口:`server-node/src/app.ts -> /healthz -> createApp` +- 关联模块:无 +- 责任: + - 提供 Node 后端进程级健康探针。 + - 给反向代理、部署平台和本地联调提供最小可用状态确认。 +- 主要服务边界: + - 只返回服务静态信息,不触达数据库、鉴权或外部模型供应商。 + +### 运行时主能力面 + +- 标识:`runtime-main` +- 路由数:59 +- 入口:`server-node/src/app.ts -> /api -> createRuntimeRoutes`;`server-node/src/routes/runtimeRoutes.ts -> /runtime/custom-world/agent -> createCustomWorldAgentRoutes` +- 关联模块:`ai`、`custom-world`、`quest`、`runtime`、`runtime-item`、`story` +- 责任: + - 承接运行时资料库、公开画廊、存档、设置与个人档案接口。 + - 承接剧情生成、聊天流、任务生成、运行时物品意图与自定义世界链路。 + - 承接 Custom World Agent 会话、消息流和操作回放。 +- 主要服务边界: + - HTTP contract 收口在 `runtimeRoutes.ts`,真正的世界生成、剧情、聊天、任务和资源逻辑继续下沉到 `services/*` 与 `src/modules/*`。 + - 除公开画廊外,运行时接口统一走 JWT 鉴权,并依赖 `runtimeRepository`、session store 与 LLM client 执行。 + +### 运行时 Story Action 面 + +- 标识:`runtime-story-action` +- 路由数:2 +- 入口:`server-node/src/app.ts -> /api/runtime/story -> createStoryActionRoutes` +- 关联模块:`story`、`quest`、`inventory`、`runtime-item`、`npc`、`progression`、`combat`、`runtime` +- 责任: + - 把前端 story choice 动作解析为新的运行时状态。 + - 查询指定 story session 的可恢复状态。 +- 主要服务边界: + - 路由层只做鉴权与 schema 校验,真正的动作分发与跨模块协作集中在 `storyActionService.ts`。 + - Story Action 会联动 quest、inventory、runtime-item、npc 等内部模块,但对前端只暴露 story 这一条稳定入口。 + +## 接口索引 + +| 方法 | 路径 | 访问 | 响应 | 挂载面 | 内部模块 | 说明 | +| --- | --- | --- | --- | --- | --- | --- | +| POST | `/api/assets/character-animation/generate` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成角色动作草稿。 | +| POST | `/api/assets/character-animation/import-video` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 导入动作参考视频并转为可消费素材。 | +| GET | `/api/assets/character-animation/jobs/:taskId` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 查询角色动作生成任务状态。 | +| POST | `/api/assets/character-animation/publish` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 发布角色动作帧集到 public 目录。 | +| GET | `/api/assets/character-animation/templates` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 列出内置角色动作模板。 | +| POST | `/api/assets/character-visual/generate` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成角色主形象候选图。 | +| GET | `/api/assets/character-visual/jobs/:taskId` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 查询角色主形象生成任务状态。 | +| POST | `/api/assets/character-visual/publish` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 发布选中的角色主形象到 public 目录。 | +| POST | `/api/assets/character-workflow-cache` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 保存角色资产工作流缓存。 | +| GET | `/api/assets/character-workflow-cache/:characterId` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 按角色读取角色资产工作流缓存。 | +| POST | `/api/assets/qwen-sprite/frame-repair` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 对单帧做 Qwen 修复。 | +| POST | `/api/assets/qwen-sprite/master` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成 Qwen 精灵主图。 | +| POST | `/api/assets/qwen-sprite/save` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 保存 Qwen 精灵资产到 public 目录。 | +| POST | `/api/assets/qwen-sprite/sheet` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成 Qwen 精灵表。 | +| GET | `/api/auth/audit-logs` | JWT | json | `auth` | 无 | 查询当前账号的鉴权审计日志。 | +| POST | `/api/auth/entry` | 公开 | json | `auth` | 无 | 用户名密码登录;不存在则创建本地账号。 | +| GET | `/api/auth/login-options` | 公开 | json | `auth` | 无 | 返回当前启用的登录方式与入口配置。 | +| POST | `/api/auth/logout` | JWT | json | `auth` | 无 | 退出当前会话并清理 refresh cookie。 | +| POST | `/api/auth/logout-all` | JWT | json | `auth` | 无 | 退出当前账号的全部会话。 | +| GET | `/api/auth/me` | JWT | json | `auth` | 无 | 读取当前登录用户的鉴权资料。 | +| POST | `/api/auth/phone/change` | JWT | json | `auth` | 无 | 已登录用户更换绑定手机号。 | +| POST | `/api/auth/phone/login` | 公开 | json | `auth` | 无 | 手机号验证码登录。 | +| POST | `/api/auth/phone/send-code` | 公开 | json | `auth` | 无 | 发送手机号登录或绑定验证码。 | +| POST | `/api/auth/refresh` | 公开 | json | `auth` | 无 | 使用 refresh session 刷新 JWT。 | +| GET | `/api/auth/risk-blocks` | JWT | json | `auth` | 无 | 查询当前用户命中的风控封禁。 | +| POST | `/api/auth/risk-blocks/:scopeType/lift` | JWT | json | `auth` | 无 | 请求解除指定维度的风控拦截。 | +| GET | `/api/auth/sessions` | JWT | json | `auth` | 无 | 列出当前账号的活跃会话。 | +| POST | `/api/auth/sessions/:sessionId/revoke` | JWT | json | `auth` | 无 | 吊销指定会话。 | +| POST | `/api/auth/wechat/bind-phone` | JWT | json | `auth` | 无 | 为已登录微信账号绑定手机号。 | +| GET | `/api/auth/wechat/callback` | 公开 | redirect | `auth` | 无 | 处理微信回调并重定向回前端。 | +| GET | `/api/auth/wechat/start` | 公开 | json | `auth` | 无 | 发起微信登录并返回授权 URL。 | +| POST | `/api/custom-world/cover-image` | JWT | json | `runtime-main` | `custom-world`、`assets` | 生成自定义世界封面图。 | +| POST | `/api/custom-world/cover-upload` | JWT | json | `runtime-main` | `custom-world`、`assets` | 上传并落地自定义世界封面图。 | +| POST | `/api/custom-world/entity` | JWT | json | `runtime-main` | `custom-world`、`ai` | 按世界 profile 生成单个角色或地标实体。 | +| POST | `/api/custom-world/scene-image` | JWT | json | `runtime-main` | `custom-world`、`assets` | 生成自定义世界场景图。 | +| POST | `/api/custom-world/scene-npc` | JWT | json | `runtime-main` | `custom-world`、`ai`、`npc` | 按地标生成场景 NPC。 | +| GET | `/api/editor/catalog/items` | 开关: EDITOR_API_ENABLED | json | `editor` | `editor` | 列出 `public/Icons` 下的物品图标资源。 | +| GET | `/api/editor/json/:resourceId` | 开关: EDITOR_API_ENABLED | json | `editor` | `editor` | 读取指定编辑器资源 JSON。 | +| POST | `/api/editor/json/:resourceId` | 开关: EDITOR_API_ENABLED | json | `editor` | `editor` | 回写指定编辑器资源 JSON。 | +| POST | `/api/llm/chat/completions` | JWT | proxy | `runtime-main` | `ai` | 把聊天补全请求透传到上游模型。 | +| DELETE | `/api/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 清空平台浏览历史。 | +| GET | `/api/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 读取平台浏览历史。 | +| POST | `/api/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 写入或批量同步平台浏览历史。 | +| GET | `/api/profile/dashboard` | JWT | json | `runtime-main` | `runtime` | 读取运行时个人主页汇总。 | +| GET | `/api/profile/play-stats` | JWT | json | `runtime-main` | `runtime` | 读取个人游玩统计。 | +| GET | `/api/profile/save-archives` | JWT | json | `runtime-main` | `runtime` | 列出个人存档摘要。 | +| POST | `/api/profile/save-archives/:worldKey` | JWT | json | `runtime-main` | `runtime` | 恢复指定世界的最近存档。 | +| GET | `/api/profile/wallet-ledger` | JWT | json | `runtime-main` | `runtime` | 列出个人资产流水。 | +| POST | `/api/runtime/chat/character/reply/stream` | JWT | stream | `runtime-main` | `ai`、`story` | 流式生成角色回复。 | +| POST | `/api/runtime/chat/character/suggestions` | JWT | json | `runtime-main` | `ai`、`story` | 生成角色聊天建议语。 | +| POST | `/api/runtime/chat/character/summary` | JWT | json | `runtime-main` | `ai`、`story` | 生成角色聊天摘要。 | +| POST | `/api/runtime/chat/npc/dialogue/stream` | JWT | stream | `runtime-main` | `ai`、`npc`、`story` | 流式生成 NPC 对话。 | +| POST | `/api/runtime/chat/npc/recruit/stream` | JWT | stream | `runtime-main` | `ai`、`npc`、`story` | 流式生成招募 NPC 对话。 | +| POST | `/api/runtime/chat/npc/turn/stream` | JWT | stream | `runtime-main` | `ai`、`npc`、`story` | 流式生成 NPC 单回合发言。 | +| GET | `/api/runtime/custom-world-gallery` | 公开 | json | `runtime-main` | `custom-world`、`runtime` | 列出公开的自定义世界画廊。 | +| GET | `/api/runtime/custom-world-gallery/:ownerUserId/:profileId` | 公开 | json | `runtime-main` | `custom-world`、`runtime` | 读取指定公开世界作品详情。 | +| GET | `/api/runtime/custom-world-library` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 列出当前账号的自定义世界资料库。 | +| DELETE | `/api/runtime/custom-world-library/:profileId` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 删除指定自定义世界 profile。 | +| PUT | `/api/runtime/custom-world-library/:profileId` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 写入或更新指定自定义世界 profile。 | +| POST | `/api/runtime/custom-world-library/:profileId/publish` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 发布指定世界到公开画廊。 | +| POST | `/api/runtime/custom-world-library/:profileId/unpublish` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 撤回指定世界的公开发布状态。 | +| POST | `/api/runtime/custom-world/agent/sessions` | JWT | json | `runtime-main` | `custom-world`、`ai` | 创建 Custom World Agent 会话。 | +| GET | `/api/runtime/custom-world/agent/sessions/:sessionId` | JWT | json | `runtime-main` | `custom-world`、`ai` | 读取 Agent 会话快照。 | +| POST | `/api/runtime/custom-world/agent/sessions/:sessionId/actions` | JWT | json | `runtime-main` | `custom-world`、`ai`、`assets` | 执行 Agent 卡片生成、资产同步或发布动作。 | +| GET | `/api/runtime/custom-world/agent/sessions/:sessionId/cards/:cardId` | JWT | json | `runtime-main` | `custom-world`、`ai` | 读取 Agent 卡片详情。 | +| POST | `/api/runtime/custom-world/agent/sessions/:sessionId/messages` | JWT | json | `runtime-main` | `custom-world`、`ai` | 向 Agent 会话提交一条创作消息。 | +| POST | `/api/runtime/custom-world/agent/sessions/:sessionId/messages/stream` | JWT | stream | `runtime-main` | `custom-world`、`ai` | 流式提交 Agent 消息并实时接收回执。 | +| GET | `/api/runtime/custom-world/agent/sessions/:sessionId/operations/:operationId` | JWT | json | `runtime-main` | `custom-world`、`ai` | 查询 Agent 后台操作状态。 | +| POST | `/api/runtime/custom-world/entity` | JWT | json | `runtime-main` | `custom-world`、`ai` | 按世界 profile 生成单个角色或地标实体(兼容路径)。 | +| POST | `/api/runtime/custom-world/scene-npc` | JWT | json | `runtime-main` | `custom-world`、`ai`、`npc` | 按地标生成场景 NPC(兼容路径)。 | +| POST | `/api/runtime/custom-world/sessions` | JWT | json | `runtime-main` | `custom-world` | 创建传统自定义世界问答会话。 | +| GET | `/api/runtime/custom-world/sessions/:sessionId` | JWT | json | `runtime-main` | `custom-world` | 读取传统自定义世界问答会话。 | +| POST | `/api/runtime/custom-world/sessions/:sessionId/answers` | JWT | json | `runtime-main` | `custom-world` | 回答传统自定义世界问答题目。 | +| GET | `/api/runtime/custom-world/sessions/:sessionId/generate/stream` | JWT | stream | `runtime-main` | `custom-world`、`ai` | 流式编译传统自定义世界 profile。 | +| GET | `/api/runtime/custom-world/works` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 列出当前账号的自定义世界作品汇总。 | +| POST | `/api/runtime/items/runtime-intent` | JWT | json | `runtime-main` | `runtime-item`、`ai` | 生成运行时物品意图。 | +| DELETE | `/api/runtime/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 清空平台浏览历史。(兼容路径) | +| GET | `/api/runtime/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 读取平台浏览历史。(兼容路径) | +| POST | `/api/runtime/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 写入或批量同步平台浏览历史。(兼容路径) | +| GET | `/api/runtime/profile/dashboard` | JWT | json | `runtime-main` | `runtime` | 读取运行时个人主页汇总。(兼容路径) | +| GET | `/api/runtime/profile/play-stats` | JWT | json | `runtime-main` | `runtime` | 读取个人游玩统计。(兼容路径) | +| GET | `/api/runtime/profile/save-archives` | JWT | json | `runtime-main` | `runtime` | 列出个人存档摘要。(兼容路径) | +| POST | `/api/runtime/profile/save-archives/:worldKey` | JWT | json | `runtime-main` | `runtime` | 恢复指定世界的最近存档(兼容路径)。 | +| GET | `/api/runtime/profile/wallet-ledger` | JWT | json | `runtime-main` | `runtime` | 列出个人资产流水。(兼容路径) | +| POST | `/api/runtime/quests/generate` | JWT | json | `runtime-main` | `quest`、`ai` | 按当前遭遇生成任务候选。 | +| DELETE | `/api/runtime/save/snapshot` | JWT | json | `runtime-main` | `runtime` | 删除当前用户的运行时存档。 | +| GET | `/api/runtime/save/snapshot` | JWT | json | `runtime-main` | `runtime`、`progression`、`quest` | 读取当前用户的运行时存档。 | +| PUT | `/api/runtime/save/snapshot` | JWT | json | `runtime-main` | `runtime`、`progression`、`quest` | 保存并归一化当前运行时存档。 | +| GET | `/api/runtime/settings` | JWT | json | `runtime-main` | `runtime` | 读取运行时设置。 | +| PUT | `/api/runtime/settings` | JWT | json | `runtime-main` | `runtime` | 更新运行时设置。 | +| POST | `/api/runtime/story/actions/resolve` | JWT | json | `runtime-story-action` | `story`、`quest`、`inventory`、`runtime-item`、`npc`、`progression`、`combat`、`runtime` | 解析前端 story choice 动作为新的运行时结果。 | +| POST | `/api/runtime/story/continue` | JWT | json | `runtime-main` | `story`、`ai` | 生成下一段故事内容。 | +| POST | `/api/runtime/story/initial` | JWT | json | `runtime-main` | `story`、`ai` | 生成首段故事内容。 | +| GET | `/api/runtime/story/state/:sessionId` | JWT | json | `runtime-story-action` | `story`、`runtime` | 读取指定 story session 的运行时状态。 | +| GET | `/api/ws/health` | JWT | json | `runtime-main` | `runtime` | 保留给未来实时链路的占位健康检查。 | +| GET | `/healthz` | 公开 | json | `health` | 无 | 返回 Node 后端进程健康状态。 | + +## 内部模块边界 + +### AI 编排模块 + +- 标识:`ai` +- 目录:`server-node/src/modules/ai` +- 对外可见面:`runtime-main` +- 关联路由数:23 +- 职责: + - 统一剧情、多轮聊天与自定义世界编排器的 prompt 构造与输出归一化。 + - 屏蔽前端对不同 AI 链路的直接拼装细节。 +- 主要服务边界: + - 专注提示词与编排,不负责持久化与 HTTP 传输。 + - 通过 `services/llmClient.ts` 与外部模型交互,由路由与 service 层决定何时调用。 +- 关键文件: + - `server-node/src/modules/ai/chatOrchestrator.ts` + - `server-node/src/modules/ai/customWorldOrchestrator.ts` + - `server-node/src/modules/ai/storyOrchestrator.ts` + +### 资产工具模块 + +- 标识:`assets` +- 目录:`server-node/src/modules/assets` +- 对外可见面:`assets` +- 关联路由数:18 +- 职责: + - 承接角色资产与 Qwen 精灵表的生成、查询、发布和保存。 + - 维护资产流程需要的缓存、草稿与产物 manifest。 +- 主要服务边界: + - 以文件系统和外部媒体模型为主要边界,不碰 runtimeRepository。 + - 对外暴露稳定 HTTP 路径,对内通过私有 helper 处理媒体编码、任务轮询与写盘。 +- 关键文件: + - `server-node/src/modules/assets/characterAssetRoutes.ts` + - `server-node/src/modules/assets/qwenSpriteRoutes.ts` + +### 战斗结算模块 + +- 标识:`combat` +- 目录:`server-node/src/modules/combat` +- 对外可见面:`runtime-story-action` +- 关联路由数:1 +- 职责: + - 提供运行时战斗结算与数值变更能力。 + - 为 story action 里的战斗型交互提供纯计算服务。 +- 主要服务边界: + - 聚焦状态推导与结果计算,不负责 transport 与持久化。 +- 关键文件: + - `server-node/src/modules/combat/combatResolutionService.ts` + +### 自定义世界运行时模块 + +- 标识:`custom-world` +- 目录:`server-node/src/modules/custom-world` +- 对外可见面:`runtime-main` +- 关联路由数:26 +- 职责: + - 规范 creator intent、世界运行时类型与 profile compile。 + - 把世界创作输入整理成运行时可消费的数据结构。 +- 主要服务边界: + - 偏纯领域建模与 compile,不直接做 HTTP、数据库查询或模型调用。 +- 关键文件: + - `server-node/src/modules/custom-world/creatorIntentRuntime.ts` + - `server-node/src/modules/custom-world/runtimeProfile.ts` + - `server-node/src/modules/custom-world/runtimeTypes.ts` + +### 编辑器资源模块 + +- 标识:`editor` +- 目录:`server-node/src/modules/editor` +- 对外可见面:`editor` +- 关联路由数:3 +- 职责: + - 提供编辑器资源目录枚举与 JSON 读写入口。 +- 主要服务边界: + - 只负责工作区文件输入输出,不参与运行时业务计算。 +- 关键文件: + - `server-node/src/modules/editor/editorRoutes.ts` + +### 背包与物品变更模块 + +- 标识:`inventory` +- 目录:`server-node/src/modules/inventory` +- 对外可见面:`runtime-story-action` +- 关联路由数:1 +- 职责: + - 维护背包变更、NPC 背包交互与 story action 里的物品副作用。 +- 主要服务边界: + - 对运行时状态做局部变更,不直接暴露 HTTP 路由。 +- 关键文件: + - `server-node/src/modules/inventory/inventoryMutationService.ts` + - `server-node/src/modules/inventory/inventoryStoryActionService.ts` + - `server-node/src/modules/inventory/npcInventoryStoryActionService.ts` + +### NPC 交互模块 + +- 标识:`npc` +- 目录:`server-node/src/modules/npc` +- 对外可见面:`runtime-story-action`、`runtime-main` +- 关联路由数:6 +- 职责: + - 维护 NPC 互动规则、任务 primitive 与关系变更逻辑。 +- 主要服务边界: + - 专注 NPC 侧状态推导,供 story action 与聊天/任务链路复用。 +- 关键文件: + - `server-node/src/modules/npc/npcInteractionService.ts` + - `server-node/src/modules/npc/npcTask6Primitives.ts` + +### 成长与关卡进程模块 + +- 标识:`progression` +- 目录:`server-node/src/modules/progression` +- 对外可见面:`runtime-story-action`、`runtime-main` +- 关联路由数:3 +- 职责: + - 提供角色成长、敌对等级、章节推进与 benchmark 逻辑。 +- 主要服务边界: + - 只做成长数值与章节进度计算,由 runtime hydrate 与 story action 复用。 +- 关键文件: + - `server-node/src/modules/progression/playerProgressionService.ts` + - `server-node/src/modules/progression/hostileProgressionService.ts` + - `server-node/src/modules/progression/chapterProgressionPlanner.ts` + +### 任务运行时模块 + +- 标识:`quest` +- 目录:`server-node/src/modules/quest` +- 对外可见面:`runtime-main`、`runtime-story-action` +- 关联路由数:4 +- 职责: + - 生成任务意图、维护任务日志与处理任务进度信号。 + - 为运行时 quest 接口与 story action 提供统一任务语义。 +- 主要服务边界: + - 领域逻辑以 quest module 为中心,AI 生成只是一种输入来源。 + - 不直接处理 HTTP 响应,统一由 routes/service 层调用。 +- 关键文件: + - `server-node/src/modules/quest/runtimeQuestModule.ts` + - `server-node/src/modules/quest/questProgressionService.ts` + - `server-node/src/modules/quest/questStoryActionService.ts` + +### 运行时状态基座模块 + +- 标识:`runtime` +- 目录:`server-node/src/modules/runtime` +- 对外可见面:`runtime-main`、`runtime-story-action` +- 关联路由数:32 +- 职责: + - 定义运行时状态 primitive、经济与装备规则。 + - 负责存档 hydration、兼容迁移与状态归一化。 +- 主要服务边界: + - 是 runtimeRepository 与 story action 的共同状态基座,不承担 HTTP 入口职责。 +- 关键文件: + - `server-node/src/modules/runtime/runtimeSnapshotHydration.ts` + - `server-node/src/modules/runtime/runtimeStatePrimitives.ts` + - `server-node/src/modules/runtime/runtimeEquipmentModule.ts` + +### 运行时物品模块 + +- 标识:`runtime-item` +- 目录:`server-node/src/modules/runtime-item` +- 对外可见面:`runtime-main`、`runtime-story-action` +- 关联路由数:2 +- 职责: + - 生成运行时物品意图、物品奖励与剧情指纹。 + - 维护宝藏与物品解析逻辑。 +- 主要服务边界: + - 聚焦物品领域编译与奖励拼装,由 route/service 选择具体触发时机。 +- 关键文件: + - `server-node/src/modules/runtime-item/runtimeItemModule.ts` + - `server-node/src/modules/runtime-item/runtimeItemResolutionService.ts` + - `server-node/src/modules/runtime-item/treasureStoryActionService.ts` + +### 故事会话模块 + +- 标识:`story` +- 目录:`server-node/src/modules/story` +- 对外可见面:`runtime-main`、`runtime-story-action` +- 关联路由数:10 +- 职责: + - 维护运行时故事会话状态与 action 分发。 + - 为 story resolve、story state 查询提供统一入口。 +- 主要服务边界: + - story 模块是 runtime 主循环的编排层,必要时再向 quest、inventory、combat 等领域模块分发。 +- 关键文件: + - `server-node/src/modules/story/runtimeSession.ts` + - `server-node/src/modules/story/storyActionRoutes.ts` + - `server-node/src/modules/story/storyActionService.ts` + +## 维护规则 + +- 新增 `server-node/src/modules/*` 目录时,必须先补充 manifest 里的模块说明,再重新生成产物。 +- 新增或下线路由时,先更新 manifest 里的路由清单,再运行生成命令同步 JSON 与文档。 +- 如果路由来自兼容路径或中间件派生路径,`sourceHint` 需要指向源代码里的真实表达式,确保生成脚本能做最小校验。 diff --git a/docs/technical/README.md b/docs/technical/README.md index 30c71580..3c79fc45 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -10,6 +10,7 @@ - [CUSTOM_WORLD_AUTO_ASSET_VISIBILITY_FIX_2026-04-20.md](./CUSTOM_WORLD_AUTO_ASSET_VISIBILITY_FIX_2026-04-20.md):世界草稿里“资产已生成但结果页看不到”的根因拆解,包含角色主形象展示、分幕背景露出和 fallback 资源格式修复。 - [CUSTOM_WORLD_PHASE4_COUNT_SEMANTICS_ALIGNMENT_2026-04-20.md](./CUSTOM_WORLD_PHASE4_COUNT_SEMANTICS_ALIGNMENT_2026-04-20.md):Phase4 新增角色/地点后草稿作品卡数量统计与测试断言的语义对齐说明。 - [TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md](./TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md):把外部仓库 TXT 模式完整迁入当前项目的冻结边界、模块映射、分阶段计划与验收清单。 +- [NODE_BACKEND_MODULE_AND_API_INDEX.md](./NODE_BACKEND_MODULE_AND_API_INDEX.md):由 `server-node/src/manifest/backendCapabilityManifest.ts` 生成的 Node 后端模块职责、挂载面与接口索引,后续新增模块/接口时同步更新这一份。 - [NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md](./NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md):当前 Node 运行时后端的技术栈、入口、鉴权、存储与接口知识图谱。 - [EXPRESS_BACKEND_INTEGRATION_FREEZE_2026-04-09.md](./EXPRESS_BACKEND_INTEGRATION_FREEZE_2026-04-09.md):Express 后端当前 contract 冻结版本、热点文件编辑规则与集成窗口清单。 - [EXPRESS_BACKEND_WORKSTREAM_AUDIT_2026-04-09.md](./EXPRESS_BACKEND_WORKSTREAM_AUDIT_2026-04-09.md):按并行工作流文档逐项核对后的完成度审计与剩余收口点。 diff --git a/package.json b/package.json index 42655d8c..bd07345f 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "server-node:dev": "npm --prefix server-node run dev", "server-node:build": "npm --prefix server-node run build", "server-node:db:migrate": "npm --prefix server-node run db:migrate", + "server-node:manifest:backend": "npm --prefix server-node run manifest:backend", "server-node:test": "npm --prefix server-node run test", "server-node:test:baseline": "npx tsx --test server-node/src/observability.test.ts", "server-node:smoke": "npx tsx scripts/smoke-server-node.ts", diff --git a/server-node/manifests/backend-capability-index.json b/server-node/manifests/backend-capability-index.json new file mode 100644 index 00000000..9097ca17 --- /dev/null +++ b/server-node/manifests/backend-capability-index.json @@ -0,0 +1,2250 @@ +{ + "generatedAt": "2026-04-20T14:26:38.663Z", + "manifestVersion": "2026-04-20", + "generatedCommand": "npm run server-node:manifest:backend", + "outputTargets": { + "json": "server-node/manifests/backend-capability-index.json", + "markdown": "docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md" + }, + "summary": { + "surfaceCount": 6, + "routeCount": 96, + "moduleCount": 12, + "publicRouteCount": 10, + "jwtRouteCount": 69, + "envSwitchRouteCount": 17, + "streamRouteCount": 6 + }, + "surfaces": [ + { + "id": "assets", + "title": "资产生成工具面", + "mounts": [ + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/api/assets", + "routeFactory": "createCharacterAssetRoutes" + }, + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/api/assets/qwen-sprite", + "routeFactory": "createQwenSpriteRoutes" + } + ], + "responsibilities": [ + "生成角色主形象、动作、动作模板与工作流缓存。", + "承接 Qwen 精灵表主图、整表、修帧与保存链路。", + "把产物发布到 `public/generated-*` 目录并落地局部 manifest。" + ], + "primaryServiceBoundaries": [ + "负责对接 DashScope、Ark 等外部媒体供应商,但不维护 runtime 快照与业务状态。", + "统一受 `ASSETS_API_ENABLED` 开关控制,产物以文件与 JSON manifest 形式落在仓库工作区。" + ], + "relatedModuleIds": [ + "assets" + ], + "routeCount": 14, + "routeIds": [ + "assets.characterAnimationGenerate", + "assets.characterAnimationImportVideo", + "assets.characterAnimationJobGet", + "assets.characterAnimationPublish", + "assets.characterAnimationTemplatesList", + "assets.characterVisualGenerate", + "assets.characterVisualJobGet", + "assets.characterVisualPublish", + "assets.characterWorkflowCacheSave", + "assets.characterWorkflowCacheGet", + "assets.qwenSpriteFrameRepairGenerate", + "assets.qwenSpriteMasterGenerate", + "assets.qwenSpriteAssetSave", + "assets.qwenSpriteSheetGenerate" + ] + }, + { + "id": "auth", + "title": "鉴权与会话面", + "mounts": [ + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/api/auth", + "routeFactory": "createAuthRoutes" + } + ], + "responsibilities": [ + "承接本地账号、短信验证码与微信登录流程。", + "管理 refresh session、用户信息、会话吊销、审计日志与风险拦截。" + ], + "primaryServiceBoundaries": [ + "HTTP 层只做 schema 校验、请求上下文拼装与 Cookie 管理,核心鉴权逻辑统一收口到 `server-node/src/auth/*`。", + "用户、身份、会话、风控与短信事件等持久化职责全部下沉到 repository 层,避免路由直接碰数据库细节。" + ], + "relatedModuleIds": [], + "routeCount": 17, + "routeIds": [ + "auth.auditLogs", + "auth.entry", + "auth.loginOptions", + "auth.logout", + "auth.logoutAll", + "auth.me", + "auth.phoneChange", + "auth.phoneLogin", + "auth.phoneSendCode", + "auth.refresh", + "auth.riskBlocks", + "auth.riskBlocksLift", + "auth.sessions", + "auth.sessionRevoke", + "auth.wechatBindPhone", + "auth.wechatCallback", + "auth.wechatStart" + ] + }, + { + "id": "editor", + "title": "编辑器工具面", + "mounts": [ + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/api/editor", + "routeFactory": "createEditorRoutes" + } + ], + "responsibilities": [ + "读取编辑器资源 JSON。", + "回写编辑器覆盖文件。", + "枚举 `public/Icons` 下的物品图标资源。" + ], + "primaryServiceBoundaries": [ + "只对工作区文件系统与 `public` 目录负责,不参与运行时数据库存储。", + "统一受 `EDITOR_API_ENABLED` 开关控制,生产环境可按需关闭。" + ], + "relatedModuleIds": [ + "editor" + ], + "routeCount": 3, + "routeIds": [ + "editor.catalogItems", + "editor.resourceRead", + "editor.resourceWrite" + ] + }, + { + "id": "health", + "title": "基础健康检查", + "mounts": [ + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/healthz", + "routeFactory": "createApp" + } + ], + "responsibilities": [ + "提供 Node 后端进程级健康探针。", + "给反向代理、部署平台和本地联调提供最小可用状态确认。" + ], + "primaryServiceBoundaries": [ + "只返回服务静态信息,不触达数据库、鉴权或外部模型供应商。" + ], + "relatedModuleIds": [], + "routeCount": 1, + "routeIds": [ + "health.check" + ] + }, + { + "id": "runtime-main", + "title": "运行时主能力面", + "mounts": [ + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/api", + "routeFactory": "createRuntimeRoutes" + }, + { + "entryFile": "server-node/src/routes/runtimeRoutes.ts", + "mountPath": "/runtime/custom-world/agent", + "routeFactory": "createCustomWorldAgentRoutes" + } + ], + "responsibilities": [ + "承接运行时资料库、公开画廊、存档、设置与个人档案接口。", + "承接剧情生成、聊天流、任务生成、运行时物品意图与自定义世界链路。", + "承接 Custom World Agent 会话、消息流和操作回放。" + ], + "primaryServiceBoundaries": [ + "HTTP contract 收口在 `runtimeRoutes.ts`,真正的世界生成、剧情、聊天、任务和资源逻辑继续下沉到 `services/*` 与 `src/modules/*`。", + "除公开画廊外,运行时接口统一走 JWT 鉴权,并依赖 `runtimeRepository`、session store 与 LLM client 执行。" + ], + "relatedModuleIds": [ + "ai", + "custom-world", + "quest", + "runtime", + "runtime-item", + "story" + ], + "routeCount": 59, + "routeIds": [ + "runtime.customWorldCoverImage", + "runtime.customWorldCoverUpload", + "runtime.customWorldEntity.primary", + "runtime.customWorldSceneImage", + "runtime.customWorldSceneNpc.primary", + "runtime.llmChatCompletionsProxy", + "runtime.profileBrowseHistoryDelete.primary", + "runtime.profileBrowseHistoryGet.primary", + "runtime.profileBrowseHistoryPost.primary", + "runtime.profileDashboard.primary", + "runtime.profilePlayStats.primary", + "runtime.profileSaveArchivesList.primary", + "runtime.profileSaveArchivesResume.primary", + "runtime.profileWalletLedger.primary", + "runtime.characterReplyStream", + "runtime.characterSuggestions", + "runtime.characterSummary", + "runtime.npcDialogueStream", + "runtime.npcRecruitStream", + "runtime.npcTurnStream", + "runtime.customWorldGalleryList", + "runtime.customWorldGalleryDetail", + "runtime.customWorldLibraryList", + "runtime.customWorldLibraryDelete", + "runtime.customWorldLibraryUpsert", + "runtime.customWorldLibraryPublish", + "runtime.customWorldLibraryUnpublish", + "runtime.customWorldAgentCreateSession", + "runtime.customWorldAgentGetSession", + "runtime.customWorldAgentExecuteAction", + "runtime.customWorldAgentGetCardDetail", + "runtime.customWorldAgentSendMessage", + "runtime.customWorldAgentStreamMessage", + "runtime.customWorldAgentGetOperation", + "runtime.customWorldEntity.compat", + "runtime.customWorldSceneNpc.compat", + "runtime.customWorldSessionCreate", + "runtime.customWorldSessionGet", + "runtime.customWorldSessionAnswer", + "runtime.customWorldSessionGenerateStream", + "runtime.customWorldWorksList", + "runtime.itemsIntent", + "runtime.profileBrowseHistoryDelete.compat", + "runtime.profileBrowseHistoryGet.compat", + "runtime.profileBrowseHistoryPost.compat", + "runtime.profileDashboard.compat", + "runtime.profilePlayStats.compat", + "runtime.profileSaveArchivesList.compat", + "runtime.profileSaveArchivesResume.compat", + "runtime.profileWalletLedger.compat", + "runtime.questsGenerate", + "runtime.snapshotDelete", + "runtime.snapshotGet", + "runtime.snapshotPut", + "runtime.settingsGet", + "runtime.settingsPut", + "runtime.storyContinue", + "runtime.storyInitial", + "runtime.wsHealth" + ] + }, + { + "id": "runtime-story-action", + "title": "运行时 Story Action 面", + "mounts": [ + { + "entryFile": "server-node/src/app.ts", + "mountPath": "/api/runtime/story", + "routeFactory": "createStoryActionRoutes" + } + ], + "responsibilities": [ + "把前端 story choice 动作解析为新的运行时状态。", + "查询指定 story session 的可恢复状态。" + ], + "primaryServiceBoundaries": [ + "路由层只做鉴权与 schema 校验,真正的动作分发与跨模块协作集中在 `storyActionService.ts`。", + "Story Action 会联动 quest、inventory、runtime-item、npc 等内部模块,但对前端只暴露 story 这一条稳定入口。" + ], + "relatedModuleIds": [ + "story", + "quest", + "inventory", + "runtime-item", + "npc", + "progression", + "combat", + "runtime" + ], + "routeCount": 2, + "routeIds": [ + "storyAction.resolve", + "storyAction.stateGet" + ] + } + ], + "modules": [ + { + "id": "ai", + "title": "AI 编排模块", + "directory": "server-node/src/modules/ai", + "exposedBySurfaceIds": [ + "runtime-main" + ], + "responsibilities": [ + "统一剧情、多轮聊天与自定义世界编排器的 prompt 构造与输出归一化。", + "屏蔽前端对不同 AI 链路的直接拼装细节。" + ], + "primaryServiceBoundaries": [ + "专注提示词与编排,不负责持久化与 HTTP 传输。", + "通过 `services/llmClient.ts` 与外部模型交互,由路由与 service 层决定何时调用。" + ], + "keyFiles": [ + "server-node/src/modules/ai/chatOrchestrator.ts", + "server-node/src/modules/ai/customWorldOrchestrator.ts", + "server-node/src/modules/ai/storyOrchestrator.ts" + ], + "routeCount": 23, + "routeIds": [ + "runtime.customWorldEntity.primary", + "runtime.customWorldSceneNpc.primary", + "runtime.llmChatCompletionsProxy", + "runtime.characterReplyStream", + "runtime.characterSuggestions", + "runtime.characterSummary", + "runtime.npcDialogueStream", + "runtime.npcRecruitStream", + "runtime.npcTurnStream", + "runtime.customWorldAgentCreateSession", + "runtime.customWorldAgentGetSession", + "runtime.customWorldAgentExecuteAction", + "runtime.customWorldAgentGetCardDetail", + "runtime.customWorldAgentSendMessage", + "runtime.customWorldAgentStreamMessage", + "runtime.customWorldAgentGetOperation", + "runtime.customWorldEntity.compat", + "runtime.customWorldSceneNpc.compat", + "runtime.customWorldSessionGenerateStream", + "runtime.itemsIntent", + "runtime.questsGenerate", + "runtime.storyContinue", + "runtime.storyInitial" + ] + }, + { + "id": "assets", + "title": "资产工具模块", + "directory": "server-node/src/modules/assets", + "exposedBySurfaceIds": [ + "assets" + ], + "responsibilities": [ + "承接角色资产与 Qwen 精灵表的生成、查询、发布和保存。", + "维护资产流程需要的缓存、草稿与产物 manifest。" + ], + "primaryServiceBoundaries": [ + "以文件系统和外部媒体模型为主要边界,不碰 runtimeRepository。", + "对外暴露稳定 HTTP 路径,对内通过私有 helper 处理媒体编码、任务轮询与写盘。" + ], + "keyFiles": [ + "server-node/src/modules/assets/characterAssetRoutes.ts", + "server-node/src/modules/assets/qwenSpriteRoutes.ts" + ], + "routeCount": 18, + "routeIds": [ + "assets.characterAnimationGenerate", + "assets.characterAnimationImportVideo", + "assets.characterAnimationJobGet", + "assets.characterAnimationPublish", + "assets.characterAnimationTemplatesList", + "assets.characterVisualGenerate", + "assets.characterVisualJobGet", + "assets.characterVisualPublish", + "assets.characterWorkflowCacheSave", + "assets.characterWorkflowCacheGet", + "assets.qwenSpriteFrameRepairGenerate", + "assets.qwenSpriteMasterGenerate", + "assets.qwenSpriteAssetSave", + "assets.qwenSpriteSheetGenerate", + "runtime.customWorldCoverImage", + "runtime.customWorldCoverUpload", + "runtime.customWorldSceneImage", + "runtime.customWorldAgentExecuteAction" + ] + }, + { + "id": "combat", + "title": "战斗结算模块", + "directory": "server-node/src/modules/combat", + "exposedBySurfaceIds": [ + "runtime-story-action" + ], + "responsibilities": [ + "提供运行时战斗结算与数值变更能力。", + "为 story action 里的战斗型交互提供纯计算服务。" + ], + "primaryServiceBoundaries": [ + "聚焦状态推导与结果计算,不负责 transport 与持久化。" + ], + "keyFiles": [ + "server-node/src/modules/combat/combatResolutionService.ts" + ], + "routeCount": 1, + "routeIds": [ + "storyAction.resolve" + ] + }, + { + "id": "custom-world", + "title": "自定义世界运行时模块", + "directory": "server-node/src/modules/custom-world", + "exposedBySurfaceIds": [ + "runtime-main" + ], + "responsibilities": [ + "规范 creator intent、世界运行时类型与 profile compile。", + "把世界创作输入整理成运行时可消费的数据结构。" + ], + "primaryServiceBoundaries": [ + "偏纯领域建模与 compile,不直接做 HTTP、数据库查询或模型调用。" + ], + "keyFiles": [ + "server-node/src/modules/custom-world/creatorIntentRuntime.ts", + "server-node/src/modules/custom-world/runtimeProfile.ts", + "server-node/src/modules/custom-world/runtimeTypes.ts" + ], + "routeCount": 26, + "routeIds": [ + "runtime.customWorldCoverImage", + "runtime.customWorldCoverUpload", + "runtime.customWorldEntity.primary", + "runtime.customWorldSceneImage", + "runtime.customWorldSceneNpc.primary", + "runtime.customWorldGalleryList", + "runtime.customWorldGalleryDetail", + "runtime.customWorldLibraryList", + "runtime.customWorldLibraryDelete", + "runtime.customWorldLibraryUpsert", + "runtime.customWorldLibraryPublish", + "runtime.customWorldLibraryUnpublish", + "runtime.customWorldAgentCreateSession", + "runtime.customWorldAgentGetSession", + "runtime.customWorldAgentExecuteAction", + "runtime.customWorldAgentGetCardDetail", + "runtime.customWorldAgentSendMessage", + "runtime.customWorldAgentStreamMessage", + "runtime.customWorldAgentGetOperation", + "runtime.customWorldEntity.compat", + "runtime.customWorldSceneNpc.compat", + "runtime.customWorldSessionCreate", + "runtime.customWorldSessionGet", + "runtime.customWorldSessionAnswer", + "runtime.customWorldSessionGenerateStream", + "runtime.customWorldWorksList" + ] + }, + { + "id": "editor", + "title": "编辑器资源模块", + "directory": "server-node/src/modules/editor", + "exposedBySurfaceIds": [ + "editor" + ], + "responsibilities": [ + "提供编辑器资源目录枚举与 JSON 读写入口。" + ], + "primaryServiceBoundaries": [ + "只负责工作区文件输入输出,不参与运行时业务计算。" + ], + "keyFiles": [ + "server-node/src/modules/editor/editorRoutes.ts" + ], + "routeCount": 3, + "routeIds": [ + "editor.catalogItems", + "editor.resourceRead", + "editor.resourceWrite" + ] + }, + { + "id": "inventory", + "title": "背包与物品变更模块", + "directory": "server-node/src/modules/inventory", + "exposedBySurfaceIds": [ + "runtime-story-action" + ], + "responsibilities": [ + "维护背包变更、NPC 背包交互与 story action 里的物品副作用。" + ], + "primaryServiceBoundaries": [ + "对运行时状态做局部变更,不直接暴露 HTTP 路由。" + ], + "keyFiles": [ + "server-node/src/modules/inventory/inventoryMutationService.ts", + "server-node/src/modules/inventory/inventoryStoryActionService.ts", + "server-node/src/modules/inventory/npcInventoryStoryActionService.ts" + ], + "routeCount": 1, + "routeIds": [ + "storyAction.resolve" + ] + }, + { + "id": "npc", + "title": "NPC 交互模块", + "directory": "server-node/src/modules/npc", + "exposedBySurfaceIds": [ + "runtime-story-action", + "runtime-main" + ], + "responsibilities": [ + "维护 NPC 互动规则、任务 primitive 与关系变更逻辑。" + ], + "primaryServiceBoundaries": [ + "专注 NPC 侧状态推导,供 story action 与聊天/任务链路复用。" + ], + "keyFiles": [ + "server-node/src/modules/npc/npcInteractionService.ts", + "server-node/src/modules/npc/npcTask6Primitives.ts" + ], + "routeCount": 6, + "routeIds": [ + "runtime.customWorldSceneNpc.primary", + "runtime.npcDialogueStream", + "runtime.npcRecruitStream", + "runtime.npcTurnStream", + "runtime.customWorldSceneNpc.compat", + "storyAction.resolve" + ] + }, + { + "id": "progression", + "title": "成长与关卡进程模块", + "directory": "server-node/src/modules/progression", + "exposedBySurfaceIds": [ + "runtime-story-action", + "runtime-main" + ], + "responsibilities": [ + "提供角色成长、敌对等级、章节推进与 benchmark 逻辑。" + ], + "primaryServiceBoundaries": [ + "只做成长数值与章节进度计算,由 runtime hydrate 与 story action 复用。" + ], + "keyFiles": [ + "server-node/src/modules/progression/playerProgressionService.ts", + "server-node/src/modules/progression/hostileProgressionService.ts", + "server-node/src/modules/progression/chapterProgressionPlanner.ts" + ], + "routeCount": 3, + "routeIds": [ + "runtime.snapshotGet", + "runtime.snapshotPut", + "storyAction.resolve" + ] + }, + { + "id": "quest", + "title": "任务运行时模块", + "directory": "server-node/src/modules/quest", + "exposedBySurfaceIds": [ + "runtime-main", + "runtime-story-action" + ], + "responsibilities": [ + "生成任务意图、维护任务日志与处理任务进度信号。", + "为运行时 quest 接口与 story action 提供统一任务语义。" + ], + "primaryServiceBoundaries": [ + "领域逻辑以 quest module 为中心,AI 生成只是一种输入来源。", + "不直接处理 HTTP 响应,统一由 routes/service 层调用。" + ], + "keyFiles": [ + "server-node/src/modules/quest/runtimeQuestModule.ts", + "server-node/src/modules/quest/questProgressionService.ts", + "server-node/src/modules/quest/questStoryActionService.ts" + ], + "routeCount": 4, + "routeIds": [ + "runtime.questsGenerate", + "runtime.snapshotGet", + "runtime.snapshotPut", + "storyAction.resolve" + ] + }, + { + "id": "runtime", + "title": "运行时状态基座模块", + "directory": "server-node/src/modules/runtime", + "exposedBySurfaceIds": [ + "runtime-main", + "runtime-story-action" + ], + "responsibilities": [ + "定义运行时状态 primitive、经济与装备规则。", + "负责存档 hydration、兼容迁移与状态归一化。" + ], + "primaryServiceBoundaries": [ + "是 runtimeRepository 与 story action 的共同状态基座,不承担 HTTP 入口职责。" + ], + "keyFiles": [ + "server-node/src/modules/runtime/runtimeSnapshotHydration.ts", + "server-node/src/modules/runtime/runtimeStatePrimitives.ts", + "server-node/src/modules/runtime/runtimeEquipmentModule.ts" + ], + "routeCount": 32, + "routeIds": [ + "runtime.profileBrowseHistoryDelete.primary", + "runtime.profileBrowseHistoryGet.primary", + "runtime.profileBrowseHistoryPost.primary", + "runtime.profileDashboard.primary", + "runtime.profilePlayStats.primary", + "runtime.profileSaveArchivesList.primary", + "runtime.profileSaveArchivesResume.primary", + "runtime.profileWalletLedger.primary", + "runtime.customWorldGalleryList", + "runtime.customWorldGalleryDetail", + "runtime.customWorldLibraryList", + "runtime.customWorldLibraryDelete", + "runtime.customWorldLibraryUpsert", + "runtime.customWorldLibraryPublish", + "runtime.customWorldLibraryUnpublish", + "runtime.customWorldWorksList", + "runtime.profileBrowseHistoryDelete.compat", + "runtime.profileBrowseHistoryGet.compat", + "runtime.profileBrowseHistoryPost.compat", + "runtime.profileDashboard.compat", + "runtime.profilePlayStats.compat", + "runtime.profileSaveArchivesList.compat", + "runtime.profileSaveArchivesResume.compat", + "runtime.profileWalletLedger.compat", + "runtime.snapshotDelete", + "runtime.snapshotGet", + "runtime.snapshotPut", + "runtime.settingsGet", + "runtime.settingsPut", + "storyAction.resolve", + "storyAction.stateGet", + "runtime.wsHealth" + ] + }, + { + "id": "runtime-item", + "title": "运行时物品模块", + "directory": "server-node/src/modules/runtime-item", + "exposedBySurfaceIds": [ + "runtime-main", + "runtime-story-action" + ], + "responsibilities": [ + "生成运行时物品意图、物品奖励与剧情指纹。", + "维护宝藏与物品解析逻辑。" + ], + "primaryServiceBoundaries": [ + "聚焦物品领域编译与奖励拼装,由 route/service 选择具体触发时机。" + ], + "keyFiles": [ + "server-node/src/modules/runtime-item/runtimeItemModule.ts", + "server-node/src/modules/runtime-item/runtimeItemResolutionService.ts", + "server-node/src/modules/runtime-item/treasureStoryActionService.ts" + ], + "routeCount": 2, + "routeIds": [ + "runtime.itemsIntent", + "storyAction.resolve" + ] + }, + { + "id": "story", + "title": "故事会话模块", + "directory": "server-node/src/modules/story", + "exposedBySurfaceIds": [ + "runtime-main", + "runtime-story-action" + ], + "responsibilities": [ + "维护运行时故事会话状态与 action 分发。", + "为 story resolve、story state 查询提供统一入口。" + ], + "primaryServiceBoundaries": [ + "story 模块是 runtime 主循环的编排层,必要时再向 quest、inventory、combat 等领域模块分发。" + ], + "keyFiles": [ + "server-node/src/modules/story/runtimeSession.ts", + "server-node/src/modules/story/storyActionRoutes.ts", + "server-node/src/modules/story/storyActionService.ts" + ], + "routeCount": 10, + "routeIds": [ + "runtime.characterReplyStream", + "runtime.characterSuggestions", + "runtime.characterSummary", + "runtime.npcDialogueStream", + "runtime.npcRecruitStream", + "runtime.npcTurnStream", + "storyAction.resolve", + "runtime.storyContinue", + "runtime.storyInitial", + "storyAction.stateGet" + ] + } + ], + "routes": [ + { + "id": "assets.characterAnimationGenerate", + "group": "assets-character-animation", + "method": "POST", + "path": "/api/assets/character-animation/generate", + "operation": "assets.character.animation.generate", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "生成角色动作草稿。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.animation.generate", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterAnimationImportVideo", + "group": "assets-character-animation", + "method": "POST", + "path": "/api/assets/character-animation/import-video", + "operation": "assets.character.animation.importVideo", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "导入动作参考视频并转为可消费素材。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.animation.importVideo", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterAnimationJobGet", + "group": "assets-character-animation", + "method": "GET", + "path": "/api/assets/character-animation/jobs/:taskId", + "operation": "assets.character.animation.job.get", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "查询角色动作生成任务状态。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.animation.job.get", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterAnimationPublish", + "group": "assets-character-animation", + "method": "POST", + "path": "/api/assets/character-animation/publish", + "operation": "assets.character.animation.publish", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "发布角色动作帧集到 public 目录。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.animation.publish", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterAnimationTemplatesList", + "group": "assets-character-animation", + "method": "GET", + "path": "/api/assets/character-animation/templates", + "operation": "assets.character.animation.templates.list", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "列出内置角色动作模板。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.animation.templates.list", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterVisualGenerate", + "group": "assets-character-visual", + "method": "POST", + "path": "/api/assets/character-visual/generate", + "operation": "assets.character.visual.generate", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "生成角色主形象候选图。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.visual.generate", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterVisualJobGet", + "group": "assets-character-visual", + "method": "GET", + "path": "/api/assets/character-visual/jobs/:taskId", + "operation": "assets.character.visual.job.get", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "查询角色主形象生成任务状态。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.visual.job.get", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterVisualPublish", + "group": "assets-character-visual", + "method": "POST", + "path": "/api/assets/character-visual/publish", + "operation": "assets.character.visual.publish", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "发布选中的角色主形象到 public 目录。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.visual.publish", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterWorkflowCacheSave", + "group": "assets-character-cache", + "method": "POST", + "path": "/api/assets/character-workflow-cache", + "operation": "assets.character.workflowCache.save", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "保存角色资产工作流缓存。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.workflowCache.save", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.characterWorkflowCacheGet", + "group": "assets-character-cache", + "method": "GET", + "path": "/api/assets/character-workflow-cache/:characterId", + "operation": "assets.character.workflowCache.get", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "按角色读取角色资产工作流缓存。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.character.workflowCache.get", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/characterAssetRoutes.ts" + }, + { + "id": "assets.qwenSpriteFrameRepairGenerate", + "group": "assets-qwen", + "method": "POST", + "path": "/api/assets/qwen-sprite/frame-repair", + "operation": "assets.qwenSprite.frameRepair.generate", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "对单帧做 Qwen 修复。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.qwenSprite.frameRepair.generate", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/qwenSpriteRoutes.ts" + }, + { + "id": "assets.qwenSpriteMasterGenerate", + "group": "assets-qwen", + "method": "POST", + "path": "/api/assets/qwen-sprite/master", + "operation": "assets.qwenSprite.master.generate", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "生成 Qwen 精灵主图。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.qwenSprite.master.generate", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/qwenSpriteRoutes.ts" + }, + { + "id": "assets.qwenSpriteAssetSave", + "group": "assets-qwen", + "method": "POST", + "path": "/api/assets/qwen-sprite/save", + "operation": "assets.qwenSprite.asset.save", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "保存 Qwen 精灵资产到 public 目录。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.qwenSprite.asset.save", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/qwenSpriteRoutes.ts" + }, + { + "id": "assets.qwenSpriteSheetGenerate", + "group": "assets-qwen", + "method": "POST", + "path": "/api/assets/qwen-sprite/sheet", + "operation": "assets.qwenSprite.sheet.generate", + "access": "开关: ASSETS_API_ENABLED", + "responseMode": "json", + "summary": "生成 Qwen 精灵表。", + "domainModuleIds": [ + "assets" + ], + "sourceHint": "assets.qwenSprite.sheet.generate", + "surfaceId": "assets", + "sourceFile": "server-node/src/modules/assets/qwenSpriteRoutes.ts" + }, + { + "id": "auth.auditLogs", + "group": "auth-audit", + "method": "GET", + "path": "/api/auth/audit-logs", + "operation": "auth.audit_logs", + "access": "JWT", + "responseMode": "json", + "summary": "查询当前账号的鉴权审计日志。", + "domainModuleIds": [], + "sourceHint": "/audit-logs", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.entry", + "group": "auth-entry", + "method": "POST", + "path": "/api/auth/entry", + "operation": "auth.entry", + "access": "公开", + "responseMode": "json", + "summary": "用户名密码登录;不存在则创建本地账号。", + "domainModuleIds": [], + "sourceHint": "/entry", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.loginOptions", + "group": "auth-entry", + "method": "GET", + "path": "/api/auth/login-options", + "operation": "auth.login_options", + "access": "公开", + "responseMode": "json", + "summary": "返回当前启用的登录方式与入口配置。", + "domainModuleIds": [], + "sourceHint": "/login-options", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.logout", + "group": "auth-session", + "method": "POST", + "path": "/api/auth/logout", + "operation": "auth.logout", + "access": "JWT", + "responseMode": "json", + "summary": "退出当前会话并清理 refresh cookie。", + "domainModuleIds": [], + "sourceHint": "/logout", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.logoutAll", + "group": "auth-session", + "method": "POST", + "path": "/api/auth/logout-all", + "operation": "auth.logout_all", + "access": "JWT", + "responseMode": "json", + "summary": "退出当前账号的全部会话。", + "domainModuleIds": [], + "sourceHint": "/logout-all", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.me", + "group": "auth-profile", + "method": "GET", + "path": "/api/auth/me", + "operation": "auth.me", + "access": "JWT", + "responseMode": "json", + "summary": "读取当前登录用户的鉴权资料。", + "domainModuleIds": [], + "sourceHint": "/me", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.phoneChange", + "group": "auth-phone", + "method": "POST", + "path": "/api/auth/phone/change", + "operation": "auth.phone.change", + "access": "JWT", + "responseMode": "json", + "summary": "已登录用户更换绑定手机号。", + "domainModuleIds": [], + "sourceHint": "/phone/change", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.phoneLogin", + "group": "auth-phone", + "method": "POST", + "path": "/api/auth/phone/login", + "operation": "auth.phone.login", + "access": "公开", + "responseMode": "json", + "summary": "手机号验证码登录。", + "domainModuleIds": [], + "sourceHint": "/phone/login", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.phoneSendCode", + "group": "auth-phone", + "method": "POST", + "path": "/api/auth/phone/send-code", + "operation": "auth.phone.send_code", + "access": "公开", + "responseMode": "json", + "summary": "发送手机号登录或绑定验证码。", + "domainModuleIds": [], + "sourceHint": "/phone/send-code", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.refresh", + "group": "auth-session", + "method": "POST", + "path": "/api/auth/refresh", + "operation": "auth.refresh", + "access": "公开", + "responseMode": "json", + "summary": "使用 refresh session 刷新 JWT。", + "domainModuleIds": [], + "sourceHint": "/refresh", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.riskBlocks", + "group": "auth-risk", + "method": "GET", + "path": "/api/auth/risk-blocks", + "operation": "auth.risk_blocks", + "access": "JWT", + "responseMode": "json", + "summary": "查询当前用户命中的风控封禁。", + "domainModuleIds": [], + "sourceHint": "/risk-blocks", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.riskBlocksLift", + "group": "auth-risk", + "method": "POST", + "path": "/api/auth/risk-blocks/:scopeType/lift", + "operation": "auth.risk_blocks.lift", + "access": "JWT", + "responseMode": "json", + "summary": "请求解除指定维度的风控拦截。", + "domainModuleIds": [], + "sourceHint": "/risk-blocks/:scopeType/lift", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.sessions", + "group": "auth-session", + "method": "GET", + "path": "/api/auth/sessions", + "operation": "auth.sessions", + "access": "JWT", + "responseMode": "json", + "summary": "列出当前账号的活跃会话。", + "domainModuleIds": [], + "sourceHint": "/sessions", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.sessionRevoke", + "group": "auth-session", + "method": "POST", + "path": "/api/auth/sessions/:sessionId/revoke", + "operation": "auth.sessions.revoke", + "access": "JWT", + "responseMode": "json", + "summary": "吊销指定会话。", + "domainModuleIds": [], + "sourceHint": "/sessions/:sessionId/revoke", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.wechatBindPhone", + "group": "auth-wechat", + "method": "POST", + "path": "/api/auth/wechat/bind-phone", + "operation": "auth.wechat.bind_phone", + "access": "JWT", + "responseMode": "json", + "summary": "为已登录微信账号绑定手机号。", + "domainModuleIds": [], + "sourceHint": "/wechat/bind-phone", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.wechatCallback", + "group": "auth-wechat", + "method": "GET", + "path": "/api/auth/wechat/callback", + "operation": "auth.wechat.callback", + "access": "公开", + "responseMode": "redirect", + "summary": "处理微信回调并重定向回前端。", + "domainModuleIds": [], + "sourceHint": "/wechat/callback", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "auth.wechatStart", + "group": "auth-wechat", + "method": "GET", + "path": "/api/auth/wechat/start", + "operation": "auth.wechat.start", + "access": "公开", + "responseMode": "json", + "summary": "发起微信登录并返回授权 URL。", + "domainModuleIds": [], + "sourceHint": "/wechat/start", + "surfaceId": "auth", + "sourceFile": "server-node/src/routes/authRoutes.ts" + }, + { + "id": "runtime.customWorldCoverImage", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/custom-world/cover-image", + "operation": "runtime.customWorld.coverImage", + "access": "JWT", + "responseMode": "json", + "summary": "生成自定义世界封面图。", + "domainModuleIds": [ + "custom-world", + "assets" + ], + "sourceHint": "/custom-world/cover-image", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldCoverUpload", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/custom-world/cover-upload", + "operation": "runtime.customWorld.coverUpload", + "access": "JWT", + "responseMode": "json", + "summary": "上传并落地自定义世界封面图。", + "domainModuleIds": [ + "custom-world", + "assets" + ], + "sourceHint": "/custom-world/cover-upload", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldEntity.primary", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/custom-world/entity", + "operation": "runtime.customWorld.entity", + "access": "JWT", + "responseMode": "json", + "summary": "按世界 profile 生成单个角色或地标实体。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/custom-world/entity", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSceneImage", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/custom-world/scene-image", + "operation": "runtime.customWorld.sceneImage", + "access": "JWT", + "responseMode": "json", + "summary": "生成自定义世界场景图。", + "domainModuleIds": [ + "custom-world", + "assets" + ], + "sourceHint": "/custom-world/scene-image", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSceneNpc.primary", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/custom-world/scene-npc", + "operation": "runtime.customWorld.sceneNpc", + "access": "JWT", + "responseMode": "json", + "summary": "按地标生成场景 NPC。", + "domainModuleIds": [ + "custom-world", + "ai", + "npc" + ], + "sourceHint": "/custom-world/scene-npc", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "editor.catalogItems", + "group": "editor-catalog", + "method": "GET", + "path": "/api/editor/catalog/items", + "operation": "editor.catalog.items.list", + "access": "开关: EDITOR_API_ENABLED", + "responseMode": "json", + "summary": "列出 `public/Icons` 下的物品图标资源。", + "domainModuleIds": [ + "editor" + ], + "sourceHint": "/api/editor/catalog/items", + "surfaceId": "editor", + "sourceFile": "server-node/src/modules/editor/editorRoutes.ts" + }, + { + "id": "editor.resourceRead", + "group": "editor-json", + "method": "GET", + "path": "/api/editor/json/:resourceId", + "operation": "editor.resource.read", + "access": "开关: EDITOR_API_ENABLED", + "responseMode": "json", + "summary": "读取指定编辑器资源 JSON。", + "domainModuleIds": [ + "editor" + ], + "sourceHint": "/api/editor/json/:resourceId", + "surfaceId": "editor", + "sourceFile": "server-node/src/modules/editor/editorRoutes.ts" + }, + { + "id": "editor.resourceWrite", + "group": "editor-json", + "method": "POST", + "path": "/api/editor/json/:resourceId", + "operation": "editor.resource.write", + "access": "开关: EDITOR_API_ENABLED", + "responseMode": "json", + "summary": "回写指定编辑器资源 JSON。", + "domainModuleIds": [ + "editor" + ], + "sourceHint": "/api/editor/json/:resourceId", + "surfaceId": "editor", + "sourceFile": "server-node/src/modules/editor/editorRoutes.ts" + }, + { + "id": "runtime.llmChatCompletionsProxy", + "group": "runtime-proxy", + "method": "POST", + "path": "/api/llm/chat/completions", + "operation": "runtime.llm.chatCompletionsProxy", + "access": "JWT", + "responseMode": "proxy", + "summary": "把聊天补全请求透传到上游模型。", + "domainModuleIds": [ + "ai" + ], + "sourceHint": "/llm/chat/completions", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileBrowseHistoryDelete.primary", + "group": "runtime-profile", + "method": "DELETE", + "path": "/api/profile/browse-history", + "operation": "profile.browseHistory.clear", + "access": "JWT", + "responseMode": "json", + "summary": "清空平台浏览历史。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/browse-history')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileBrowseHistoryGet.primary", + "group": "runtime-profile", + "method": "GET", + "path": "/api/profile/browse-history", + "operation": "profile.browseHistory.list", + "access": "JWT", + "responseMode": "json", + "summary": "读取平台浏览历史。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/browse-history')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileBrowseHistoryPost.primary", + "group": "runtime-profile", + "method": "POST", + "path": "/api/profile/browse-history", + "operation": "profile.browseHistory.upsert", + "access": "JWT", + "responseMode": "json", + "summary": "写入或批量同步平台浏览历史。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/browse-history')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileDashboard.primary", + "group": "runtime-profile", + "method": "GET", + "path": "/api/profile/dashboard", + "operation": "profile.dashboard.get", + "access": "JWT", + "responseMode": "json", + "summary": "读取运行时个人主页汇总。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/dashboard')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profilePlayStats.primary", + "group": "runtime-profile", + "method": "GET", + "path": "/api/profile/play-stats", + "operation": "profile.playStats.get", + "access": "JWT", + "responseMode": "json", + "summary": "读取个人游玩统计。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/play-stats')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileSaveArchivesList.primary", + "group": "runtime-save", + "method": "GET", + "path": "/api/profile/save-archives", + "operation": "profile.saveArchives.list", + "access": "JWT", + "responseMode": "json", + "summary": "列出个人存档摘要。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/save-archives')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileSaveArchivesResume.primary", + "group": "runtime-save", + "method": "POST", + "path": "/api/profile/save-archives/:worldKey", + "operation": "profile.saveArchives.resume", + "access": "JWT", + "responseMode": "json", + "summary": "恢复指定世界的最近存档。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "'/profile/save-archives/:worldKey'", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileWalletLedger.primary", + "group": "runtime-profile", + "method": "GET", + "path": "/api/profile/wallet-ledger", + "operation": "profile.walletLedger.list", + "access": "JWT", + "responseMode": "json", + "summary": "列出个人资产流水。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/wallet-ledger')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.characterReplyStream", + "group": "runtime-chat", + "method": "POST", + "path": "/api/runtime/chat/character/reply/stream", + "operation": "runtime.chat.character.replyStream", + "access": "JWT", + "responseMode": "stream", + "summary": "流式生成角色回复。", + "domainModuleIds": [ + "ai", + "story" + ], + "sourceHint": "/runtime/chat/character/reply/stream", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.characterSuggestions", + "group": "runtime-chat", + "method": "POST", + "path": "/api/runtime/chat/character/suggestions", + "operation": "runtime.chat.character.suggestions", + "access": "JWT", + "responseMode": "json", + "summary": "生成角色聊天建议语。", + "domainModuleIds": [ + "ai", + "story" + ], + "sourceHint": "/runtime/chat/character/suggestions", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.characterSummary", + "group": "runtime-chat", + "method": "POST", + "path": "/api/runtime/chat/character/summary", + "operation": "runtime.chat.character.summary", + "access": "JWT", + "responseMode": "json", + "summary": "生成角色聊天摘要。", + "domainModuleIds": [ + "ai", + "story" + ], + "sourceHint": "/runtime/chat/character/summary", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.npcDialogueStream", + "group": "runtime-chat", + "method": "POST", + "path": "/api/runtime/chat/npc/dialogue/stream", + "operation": "runtime.chat.npc.dialogueStream", + "access": "JWT", + "responseMode": "stream", + "summary": "流式生成 NPC 对话。", + "domainModuleIds": [ + "ai", + "npc", + "story" + ], + "sourceHint": "/runtime/chat/npc/dialogue/stream", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.npcRecruitStream", + "group": "runtime-chat", + "method": "POST", + "path": "/api/runtime/chat/npc/recruit/stream", + "operation": "runtime.chat.npc.recruitStream", + "access": "JWT", + "responseMode": "stream", + "summary": "流式生成招募 NPC 对话。", + "domainModuleIds": [ + "ai", + "npc", + "story" + ], + "sourceHint": "/runtime/chat/npc/recruit/stream", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.npcTurnStream", + "group": "runtime-chat", + "method": "POST", + "path": "/api/runtime/chat/npc/turn/stream", + "operation": "runtime.chat.npc.turnStream", + "access": "JWT", + "responseMode": "stream", + "summary": "流式生成 NPC 单回合发言。", + "domainModuleIds": [ + "ai", + "npc", + "story" + ], + "sourceHint": "/runtime/chat/npc/turn/stream", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldGalleryList", + "group": "runtime-gallery", + "method": "GET", + "path": "/api/runtime/custom-world-gallery", + "operation": "runtime.customWorldGallery.list", + "access": "公开", + "responseMode": "json", + "summary": "列出公开的自定义世界画廊。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-gallery", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldGalleryDetail", + "group": "runtime-gallery", + "method": "GET", + "path": "/api/runtime/custom-world-gallery/:ownerUserId/:profileId", + "operation": "runtime.customWorldGallery.detail", + "access": "公开", + "responseMode": "json", + "summary": "读取指定公开世界作品详情。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-gallery/:ownerUserId/:profileId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldLibraryList", + "group": "runtime-custom-world-library", + "method": "GET", + "path": "/api/runtime/custom-world-library", + "operation": "runtime.customWorldLibrary.list", + "access": "JWT", + "responseMode": "json", + "summary": "列出当前账号的自定义世界资料库。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-library", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldLibraryDelete", + "group": "runtime-custom-world-library", + "method": "DELETE", + "path": "/api/runtime/custom-world-library/:profileId", + "operation": "runtime.customWorldLibrary.delete", + "access": "JWT", + "responseMode": "json", + "summary": "删除指定自定义世界 profile。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-library/:profileId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldLibraryUpsert", + "group": "runtime-custom-world-library", + "method": "PUT", + "path": "/api/runtime/custom-world-library/:profileId", + "operation": "runtime.customWorldLibrary.upsert", + "access": "JWT", + "responseMode": "json", + "summary": "写入或更新指定自定义世界 profile。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-library/:profileId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldLibraryPublish", + "group": "runtime-custom-world-library", + "method": "POST", + "path": "/api/runtime/custom-world-library/:profileId/publish", + "operation": "runtime.customWorldLibrary.publish", + "access": "JWT", + "responseMode": "json", + "summary": "发布指定世界到公开画廊。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-library/:profileId/publish", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldLibraryUnpublish", + "group": "runtime-custom-world-library", + "method": "POST", + "path": "/api/runtime/custom-world-library/:profileId/unpublish", + "operation": "runtime.customWorldLibrary.unpublish", + "access": "JWT", + "responseMode": "json", + "summary": "撤回指定世界的公开发布状态。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world-library/:profileId/unpublish", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldAgentCreateSession", + "group": "runtime-custom-world-agent", + "method": "POST", + "path": "/api/runtime/custom-world/agent/sessions", + "operation": "runtime.customWorldAgent.createSession", + "access": "JWT", + "responseMode": "json", + "summary": "创建 Custom World Agent 会话。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/sessions", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldAgentGetSession", + "group": "runtime-custom-world-agent", + "method": "GET", + "path": "/api/runtime/custom-world/agent/sessions/:sessionId", + "operation": "runtime.customWorldAgent.getSession", + "access": "JWT", + "responseMode": "json", + "summary": "读取 Agent 会话快照。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/sessions/:sessionId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldAgentExecuteAction", + "group": "runtime-custom-world-agent", + "method": "POST", + "path": "/api/runtime/custom-world/agent/sessions/:sessionId/actions", + "operation": "runtime.customWorldAgent.executeAction", + "access": "JWT", + "responseMode": "json", + "summary": "执行 Agent 卡片生成、资产同步或发布动作。", + "domainModuleIds": [ + "custom-world", + "ai", + "assets" + ], + "sourceHint": "/sessions/:sessionId/actions", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldAgentGetCardDetail", + "group": "runtime-custom-world-agent", + "method": "GET", + "path": "/api/runtime/custom-world/agent/sessions/:sessionId/cards/:cardId", + "operation": "runtime.customWorldAgent.getCardDetail", + "access": "JWT", + "responseMode": "json", + "summary": "读取 Agent 卡片详情。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/sessions/:sessionId/cards/:cardId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldAgentSendMessage", + "group": "runtime-custom-world-agent", + "method": "POST", + "path": "/api/runtime/custom-world/agent/sessions/:sessionId/messages", + "operation": "runtime.customWorldAgent.sendMessage", + "access": "JWT", + "responseMode": "json", + "summary": "向 Agent 会话提交一条创作消息。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/sessions/:sessionId/messages", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldAgentStreamMessage", + "group": "runtime-custom-world-agent", + "method": "POST", + "path": "/api/runtime/custom-world/agent/sessions/:sessionId/messages/stream", + "operation": "runtime.customWorldAgent.streamMessage", + "access": "JWT", + "responseMode": "stream", + "summary": "流式提交 Agent 消息并实时接收回执。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/sessions/:sessionId/messages/stream", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldAgentGetOperation", + "group": "runtime-custom-world-agent", + "method": "GET", + "path": "/api/runtime/custom-world/agent/sessions/:sessionId/operations/:operationId", + "operation": "runtime.customWorldAgent.getOperation", + "access": "JWT", + "responseMode": "json", + "summary": "查询 Agent 后台操作状态。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/sessions/:sessionId/operations/:operationId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/customWorldAgent.ts" + }, + { + "id": "runtime.customWorldEntity.compat", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/runtime/custom-world/entity", + "operation": "runtime.customWorld.entity.compat", + "access": "JWT", + "responseMode": "json", + "summary": "按世界 profile 生成单个角色或地标实体(兼容路径)。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/runtime/custom-world/entity", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSceneNpc.compat", + "group": "runtime-custom-world-assets", + "method": "POST", + "path": "/api/runtime/custom-world/scene-npc", + "operation": "runtime.customWorld.sceneNpc.compat", + "access": "JWT", + "responseMode": "json", + "summary": "按地标生成场景 NPC(兼容路径)。", + "domainModuleIds": [ + "custom-world", + "ai", + "npc" + ], + "sourceHint": "/runtime/custom-world/scene-npc", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSessionCreate", + "group": "runtime-custom-world-session", + "method": "POST", + "path": "/api/runtime/custom-world/sessions", + "operation": "runtime.customWorldSession.create", + "access": "JWT", + "responseMode": "json", + "summary": "创建传统自定义世界问答会话。", + "domainModuleIds": [ + "custom-world" + ], + "sourceHint": "/runtime/custom-world/sessions", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSessionGet", + "group": "runtime-custom-world-session", + "method": "GET", + "path": "/api/runtime/custom-world/sessions/:sessionId", + "operation": "runtime.customWorldSession.get", + "access": "JWT", + "responseMode": "json", + "summary": "读取传统自定义世界问答会话。", + "domainModuleIds": [ + "custom-world" + ], + "sourceHint": "/runtime/custom-world/sessions/:sessionId", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSessionAnswer", + "group": "runtime-custom-world-session", + "method": "POST", + "path": "/api/runtime/custom-world/sessions/:sessionId/answers", + "operation": "runtime.customWorldSession.answer", + "access": "JWT", + "responseMode": "json", + "summary": "回答传统自定义世界问答题目。", + "domainModuleIds": [ + "custom-world" + ], + "sourceHint": "/runtime/custom-world/sessions/:sessionId/answers", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldSessionGenerateStream", + "group": "runtime-custom-world-session", + "method": "GET", + "path": "/api/runtime/custom-world/sessions/:sessionId/generate/stream", + "operation": "runtime.customWorldSession.generateStream", + "access": "JWT", + "responseMode": "stream", + "summary": "流式编译传统自定义世界 profile。", + "domainModuleIds": [ + "custom-world", + "ai" + ], + "sourceHint": "/runtime/custom-world/sessions/:sessionId/generate/stream", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.customWorldWorksList", + "group": "runtime-custom-world-library", + "method": "GET", + "path": "/api/runtime/custom-world/works", + "operation": "runtime.customWorldWorks.list", + "access": "JWT", + "responseMode": "json", + "summary": "列出当前账号的自定义世界作品汇总。", + "domainModuleIds": [ + "custom-world", + "runtime" + ], + "sourceHint": "/runtime/custom-world/works", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.itemsIntent", + "group": "runtime-loot", + "method": "POST", + "path": "/api/runtime/items/runtime-intent", + "operation": "runtime.items.intent", + "access": "JWT", + "responseMode": "json", + "summary": "生成运行时物品意图。", + "domainModuleIds": [ + "runtime-item", + "ai" + ], + "sourceHint": "/runtime/items/runtime-intent", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileBrowseHistoryDelete.compat", + "group": "runtime-profile", + "method": "DELETE", + "path": "/api/runtime/profile/browse-history", + "operation": "profile.browseHistory.clear.compat", + "access": "JWT", + "responseMode": "json", + "summary": "清空平台浏览历史。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/browse-history')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileBrowseHistoryGet.compat", + "group": "runtime-profile", + "method": "GET", + "path": "/api/runtime/profile/browse-history", + "operation": "profile.browseHistory.list.compat", + "access": "JWT", + "responseMode": "json", + "summary": "读取平台浏览历史。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/browse-history')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileBrowseHistoryPost.compat", + "group": "runtime-profile", + "method": "POST", + "path": "/api/runtime/profile/browse-history", + "operation": "profile.browseHistory.upsert.compat", + "access": "JWT", + "responseMode": "json", + "summary": "写入或批量同步平台浏览历史。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/browse-history')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileDashboard.compat", + "group": "runtime-profile", + "method": "GET", + "path": "/api/runtime/profile/dashboard", + "operation": "profile.dashboard.get.compat", + "access": "JWT", + "responseMode": "json", + "summary": "读取运行时个人主页汇总。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/dashboard')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profilePlayStats.compat", + "group": "runtime-profile", + "method": "GET", + "path": "/api/runtime/profile/play-stats", + "operation": "profile.playStats.get.compat", + "access": "JWT", + "responseMode": "json", + "summary": "读取个人游玩统计。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/play-stats')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileSaveArchivesList.compat", + "group": "runtime-save", + "method": "GET", + "path": "/api/runtime/profile/save-archives", + "operation": "profile.saveArchives.list.compat", + "access": "JWT", + "responseMode": "json", + "summary": "列出个人存档摘要。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/save-archives')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileSaveArchivesResume.compat", + "group": "runtime-save", + "method": "POST", + "path": "/api/runtime/profile/save-archives/:worldKey", + "operation": "profile.saveArchives.resume.compat", + "access": "JWT", + "responseMode": "json", + "summary": "恢复指定世界的最近存档(兼容路径)。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "'/runtime/profile/save-archives/:worldKey'", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.profileWalletLedger.compat", + "group": "runtime-profile", + "method": "GET", + "path": "/api/runtime/profile/wallet-ledger", + "operation": "profile.walletLedger.list.compat", + "access": "JWT", + "responseMode": "json", + "summary": "列出个人资产流水。(兼容路径)", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "routeCompatPaths('/profile/wallet-ledger')", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.questsGenerate", + "group": "runtime-quest", + "method": "POST", + "path": "/api/runtime/quests/generate", + "operation": "runtime.quests.generate", + "access": "JWT", + "responseMode": "json", + "summary": "按当前遭遇生成任务候选。", + "domainModuleIds": [ + "quest", + "ai" + ], + "sourceHint": "/runtime/quests/generate", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.snapshotDelete", + "group": "runtime-save", + "method": "DELETE", + "path": "/api/runtime/save/snapshot", + "operation": "runtime.snapshot.delete", + "access": "JWT", + "responseMode": "json", + "summary": "删除当前用户的运行时存档。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "/runtime/save/snapshot", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.snapshotGet", + "group": "runtime-save", + "method": "GET", + "path": "/api/runtime/save/snapshot", + "operation": "runtime.snapshot.get", + "access": "JWT", + "responseMode": "json", + "summary": "读取当前用户的运行时存档。", + "domainModuleIds": [ + "runtime", + "progression", + "quest" + ], + "sourceHint": "/runtime/save/snapshot", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.snapshotPut", + "group": "runtime-save", + "method": "PUT", + "path": "/api/runtime/save/snapshot", + "operation": "runtime.snapshot.put", + "access": "JWT", + "responseMode": "json", + "summary": "保存并归一化当前运行时存档。", + "domainModuleIds": [ + "runtime", + "progression", + "quest" + ], + "sourceHint": "/runtime/save/snapshot", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.settingsGet", + "group": "runtime-settings", + "method": "GET", + "path": "/api/runtime/settings", + "operation": "runtime.settings.get", + "access": "JWT", + "responseMode": "json", + "summary": "读取运行时设置。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "/runtime/settings", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.settingsPut", + "group": "runtime-settings", + "method": "PUT", + "path": "/api/runtime/settings", + "operation": "runtime.settings.put", + "access": "JWT", + "responseMode": "json", + "summary": "更新运行时设置。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "/runtime/settings", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "storyAction.resolve", + "group": "story-action", + "method": "POST", + "path": "/api/runtime/story/actions/resolve", + "operation": "runtime.story.actions.resolve", + "access": "JWT", + "responseMode": "json", + "summary": "解析前端 story choice 动作为新的运行时结果。", + "domainModuleIds": [ + "story", + "quest", + "inventory", + "runtime-item", + "npc", + "progression", + "combat", + "runtime" + ], + "sourceHint": "/actions/resolve", + "surfaceId": "runtime-story-action", + "sourceFile": "server-node/src/modules/story/storyActionRoutes.ts" + }, + { + "id": "runtime.storyContinue", + "group": "runtime-story-generation", + "method": "POST", + "path": "/api/runtime/story/continue", + "operation": "runtime.story.continue", + "access": "JWT", + "responseMode": "json", + "summary": "生成下一段故事内容。", + "domainModuleIds": [ + "story", + "ai" + ], + "sourceHint": "/runtime/story/continue", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "runtime.storyInitial", + "group": "runtime-story-generation", + "method": "POST", + "path": "/api/runtime/story/initial", + "operation": "runtime.story.initial", + "access": "JWT", + "responseMode": "json", + "summary": "生成首段故事内容。", + "domainModuleIds": [ + "story", + "ai" + ], + "sourceHint": "/runtime/story/initial", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "storyAction.stateGet", + "group": "story-action", + "method": "GET", + "path": "/api/runtime/story/state/:sessionId", + "operation": "runtime.story.state.get", + "access": "JWT", + "responseMode": "json", + "summary": "读取指定 story session 的运行时状态。", + "domainModuleIds": [ + "story", + "runtime" + ], + "sourceHint": "/state/:sessionId", + "surfaceId": "runtime-story-action", + "sourceFile": "server-node/src/modules/story/storyActionRoutes.ts" + }, + { + "id": "runtime.wsHealth", + "group": "runtime-diagnostics", + "method": "GET", + "path": "/api/ws/health", + "operation": "runtime.ws.health", + "access": "JWT", + "responseMode": "json", + "summary": "保留给未来实时链路的占位健康检查。", + "domainModuleIds": [ + "runtime" + ], + "sourceHint": "/ws/health", + "surfaceId": "runtime-main", + "sourceFile": "server-node/src/routes/runtimeRoutes.ts" + }, + { + "id": "health.check", + "surfaceId": "health", + "group": "health", + "method": "GET", + "path": "/healthz", + "operation": "health.check", + "access": "公开", + "responseMode": "json", + "summary": "返回 Node 后端进程健康状态。", + "domainModuleIds": [], + "sourceFile": "server-node/src/app.ts", + "sourceHint": "/healthz" + } + ], + "maintenanceRules": [ + "新增 `server-node/src/modules/*` 目录时,必须先补充 manifest 里的模块说明,再重新生成产物。", + "新增或下线路由时,先更新 manifest 里的路由清单,再运行生成命令同步 JSON 与文档。", + "如果路由来自兼容路径或中间件派生路径,`sourceHint` 需要指向源代码里的真实表达式,确保生成脚本能做最小校验。" + ] +} diff --git a/server-node/package.json b/server-node/package.json index 4a817b53..d6dcdb60 100644 --- a/server-node/package.json +++ b/server-node/package.json @@ -8,7 +8,8 @@ "build": "node build.mjs", "start": "node dist/server.cjs", "test": "node test.mjs", - "db:migrate": "tsx src/migrate.ts" + "db:migrate": "tsx src/migrate.ts", + "manifest:backend": "tsx scripts/generateBackendCapabilityArtifacts.ts" }, "dependencies": { "@alicloud/dypnsapi20170525": "^2.0.0", diff --git a/server-node/scripts/generateBackendCapabilityArtifacts.ts b/server-node/scripts/generateBackendCapabilityArtifacts.ts new file mode 100644 index 00000000..e534567a --- /dev/null +++ b/server-node/scripts/generateBackendCapabilityArtifacts.ts @@ -0,0 +1,372 @@ +import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { + BACKEND_CAPABILITY_MANIFEST, + type BackendCapabilityManifest, + type BackendDomainModule, + type BackendRouteCapability, + type BackendRouteSurface, +} from '../src/manifest/backendCapabilityManifest.js'; + +type GeneratedSummary = { + surfaceCount: number; + routeCount: number; + moduleCount: number; + publicRouteCount: number; + jwtRouteCount: number; + envSwitchRouteCount: number; + streamRouteCount: number; +}; + +type GeneratedArtifact = { + generatedAt: string; + manifestVersion: string; + generatedCommand: string; + outputTargets: BackendCapabilityManifest['outputTargets']; + summary: GeneratedSummary; + surfaces: Array< + BackendRouteSurface & { + routeCount: number; + routeIds: string[]; + } + >; + modules: Array< + BackendDomainModule & { + routeCount: number; + routeIds: string[]; + } + >; + routes: BackendRouteCapability[]; + maintenanceRules: string[]; +}; + +const currentFilePath = fileURLToPath(import.meta.url); +const scriptDirectory = path.dirname(currentFilePath); +const repoRoot = path.resolve(scriptDirectory, '..', '..'); + +/** + * 统一把 repo 相对路径转成绝对路径,避免不同工作目录下解析不一致。 + */ +function resolveRepoPath(relativePath: string) { + return path.resolve(repoRoot, relativePath); +} + +function sortById(items: T[]) { + return [...items].sort((left, right) => left.id.localeCompare(right.id, 'zh-Hans-CN')); +} + +function sortRoutes(routes: BackendRouteCapability[]) { + return [...routes].sort((left, right) => { + if (left.path === right.path) { + return left.method.localeCompare(right.method, 'en-US'); + } + return left.path.localeCompare(right.path, 'en-US'); + }); +} + +/** + * 用最小约束校验 manifest 的唯一性与引用完整性,确保生成结果可维护。 + */ +function assertUniqueIds(items: Array<{ id: string }>, label: string) { + const seen = new Set(); + const duplicates: string[] = []; + + items.forEach((item) => { + if (seen.has(item.id)) { + duplicates.push(item.id); + return; + } + seen.add(item.id); + }); + + if (duplicates.length > 0) { + throw new Error(`${label} 存在重复 id:${duplicates.join('、')}`); + } +} + +async function assertSourceFileContains(params: { + sourceFile: string; + sourceHint: string; + routeId: string; +}) { + const absolutePath = resolveRepoPath(params.sourceFile); + const content = await readFile(absolutePath, 'utf8'); + if (!content.includes(params.sourceHint)) { + throw new Error( + `路由 ${params.routeId} 的 sourceHint 未命中源码:${params.sourceFile} -> ${params.sourceHint}`, + ); + } +} + +async function validateModuleCoverage(modules: BackendDomainModule[]) { + const modulesRoot = resolveRepoPath('server-node/src/modules'); + const directoryEntries = await readdir(modulesRoot, { withFileTypes: true }); + const actualDirectories = directoryEntries + .filter((entry) => entry.isDirectory()) + .map((entry) => `server-node/src/modules/${entry.name}`) + .sort((left, right) => left.localeCompare(right, 'en-US')); + const manifestDirectories = modules + .map((moduleItem) => moduleItem.directory) + .sort((left, right) => left.localeCompare(right, 'en-US')); + + const missingInManifest = actualDirectories.filter( + (directory) => !manifestDirectories.includes(directory), + ); + const staleInManifest = manifestDirectories.filter( + (directory) => !actualDirectories.includes(directory), + ); + + if (missingInManifest.length > 0) { + throw new Error( + `以下模块目录尚未进入能力 manifest:${missingInManifest.join('、')}`, + ); + } + + if (staleInManifest.length > 0) { + throw new Error( + `manifest 中存在已失效的模块目录:${staleInManifest.join('、')}`, + ); + } +} + +async function validateManifest(manifest: BackendCapabilityManifest) { + assertUniqueIds(manifest.surfaces, '挂载面'); + assertUniqueIds(manifest.modules, '内部模块'); + assertUniqueIds(manifest.routes, '路由'); + + const surfaceIds = new Set(manifest.surfaces.map((surface) => surface.id)); + const moduleIds = new Set(manifest.modules.map((moduleItem) => moduleItem.id)); + + for (const surface of manifest.surfaces) { + for (const relatedModuleId of surface.relatedModuleIds) { + if (!moduleIds.has(relatedModuleId)) { + throw new Error( + `挂载面 ${surface.id} 引用了未定义的模块:${relatedModuleId}`, + ); + } + } + + for (const mount of surface.mounts) { + const absoluteEntryPath = resolveRepoPath(mount.entryFile); + const content = await readFile(absoluteEntryPath, 'utf8'); + if (!content.includes(mount.routeFactory)) { + throw new Error( + `挂载面 ${surface.id} 的入口文件缺少工厂引用:${mount.entryFile} -> ${mount.routeFactory}`, + ); + } + } + } + + for (const moduleItem of manifest.modules) { + for (const surfaceId of moduleItem.exposedBySurfaceIds) { + if (!surfaceIds.has(surfaceId)) { + throw new Error( + `模块 ${moduleItem.id} 引用了未定义的挂载面:${surfaceId}`, + ); + } + } + + const absoluteDirectory = resolveRepoPath(moduleItem.directory); + const statsEntries = await readdir(absoluteDirectory); + if (statsEntries.length === 0) { + throw new Error(`模块目录为空,无法作为能力边界:${moduleItem.directory}`); + } + } + + for (const route of manifest.routes) { + if (!surfaceIds.has(route.surfaceId)) { + throw new Error(`路由 ${route.id} 引用了未定义的挂载面:${route.surfaceId}`); + } + + for (const moduleId of route.domainModuleIds) { + if (!moduleIds.has(moduleId)) { + throw new Error(`路由 ${route.id} 引用了未定义的模块:${moduleId}`); + } + } + + await assertSourceFileContains({ + sourceFile: route.sourceFile, + sourceHint: route.sourceHint, + routeId: route.id, + }); + } + + await validateModuleCoverage(manifest.modules); +} + +function buildSummary(routes: BackendRouteCapability[]): GeneratedSummary { + return { + surfaceCount: BACKEND_CAPABILITY_MANIFEST.surfaces.length, + routeCount: routes.length, + moduleCount: BACKEND_CAPABILITY_MANIFEST.modules.length, + publicRouteCount: routes.filter((route) => route.access === '公开').length, + jwtRouteCount: routes.filter((route) => route.access === 'JWT').length, + envSwitchRouteCount: routes.filter((route) => route.access.startsWith('开关:')).length, + streamRouteCount: routes.filter((route) => route.responseMode === 'stream').length, + }; +} + +function buildArtifact(manifest: BackendCapabilityManifest): GeneratedArtifact { + const routes = sortRoutes(manifest.routes); + const summary = buildSummary(routes); + + const surfaces = sortById(manifest.surfaces).map((surface) => { + const surfaceRoutes = routes.filter((route) => route.surfaceId === surface.id); + return { + ...surface, + routeCount: surfaceRoutes.length, + routeIds: surfaceRoutes.map((route) => route.id), + }; + }); + + const modules = sortById(manifest.modules).map((moduleItem) => { + const moduleRoutes = routes.filter((route) => + route.domainModuleIds.includes(moduleItem.id), + ); + return { + ...moduleItem, + routeCount: moduleRoutes.length, + routeIds: moduleRoutes.map((route) => route.id), + }; + }); + + return { + generatedAt: new Date().toISOString(), + manifestVersion: manifest.version, + generatedCommand: manifest.generatedCommand, + outputTargets: manifest.outputTargets, + summary, + surfaces, + modules, + routes, + maintenanceRules: manifest.maintenanceRules, + }; +} + +function renderMarkdown(artifact: GeneratedArtifact) { + const lines: string[] = []; + lines.push('# Node 后端模块与接口索引'); + lines.push(''); + lines.push('> 该文档由 `server-node/src/manifest/backendCapabilityManifest.ts` 自动生成。'); + lines.push(`> 生成命令:\`${artifact.generatedCommand}\``); + lines.push(`> 生成时间:\`${artifact.generatedAt}\``); + lines.push(''); + lines.push('## 总览'); + lines.push(''); + lines.push(`- 对外挂载面:${artifact.summary.surfaceCount} 个`); + lines.push(`- 已登记路由:${artifact.summary.routeCount} 条`); + lines.push(`- 内部模块目录:${artifact.summary.moduleCount} 个`); + lines.push(`- 公开接口:${artifact.summary.publicRouteCount} 条`); + lines.push(`- JWT 接口:${artifact.summary.jwtRouteCount} 条`); + lines.push(`- 受环境开关控制的接口:${artifact.summary.envSwitchRouteCount} 条`); + lines.push(`- 流式接口:${artifact.summary.streamRouteCount} 条`); + lines.push(''); + lines.push('## 产物'); + lines.push(''); + lines.push(`- JSON 清单:\`${artifact.outputTargets.json}\``); + lines.push(`- Markdown 索引:\`${artifact.outputTargets.markdown}\``); + lines.push(`- Manifest 源:\`server-node/src/manifest/backendCapabilityManifest.ts\``); + lines.push(''); + lines.push('## 对外挂载面'); + lines.push(''); + + artifact.surfaces.forEach((surface) => { + lines.push(`### ${surface.title}`); + lines.push(''); + lines.push(`- 标识:\`${surface.id}\``); + lines.push(`- 路由数:${surface.routeCount}`); + lines.push(`- 入口:${surface.mounts.map((mount) => `\`${mount.entryFile} -> ${mount.mountPath} -> ${mount.routeFactory}\``).join(';')}`); + lines.push(`- 关联模块:${surface.relatedModuleIds.length > 0 ? surface.relatedModuleIds.map((moduleId) => `\`${moduleId}\``).join('、') : '无'}`); + lines.push('- 责任:'); + surface.responsibilities.forEach((item) => { + lines.push(` - ${item}`); + }); + lines.push('- 主要服务边界:'); + surface.primaryServiceBoundaries.forEach((item) => { + lines.push(` - ${item}`); + }); + lines.push(''); + }); + + lines.push('## 接口索引'); + lines.push(''); + lines.push('| 方法 | 路径 | 访问 | 响应 | 挂载面 | 内部模块 | 说明 |'); + lines.push('| --- | --- | --- | --- | --- | --- | --- |'); + artifact.routes.forEach((route) => { + const moduleLabel = + route.domainModuleIds.length > 0 + ? route.domainModuleIds.map((moduleId) => `\`${moduleId}\``).join('、') + : '无'; + lines.push( + `| ${route.method} | \`${route.path}\` | ${route.access} | ${route.responseMode} | \`${route.surfaceId}\` | ${moduleLabel} | ${route.summary} |`, + ); + }); + lines.push(''); + + lines.push('## 内部模块边界'); + lines.push(''); + artifact.modules.forEach((moduleItem) => { + lines.push(`### ${moduleItem.title}`); + lines.push(''); + lines.push(`- 标识:\`${moduleItem.id}\``); + lines.push(`- 目录:\`${moduleItem.directory}\``); + lines.push(`- 对外可见面:${moduleItem.exposedBySurfaceIds.map((surfaceId) => `\`${surfaceId}\``).join('、')}`); + lines.push(`- 关联路由数:${moduleItem.routeCount}`); + lines.push('- 职责:'); + moduleItem.responsibilities.forEach((item) => { + lines.push(` - ${item}`); + }); + lines.push('- 主要服务边界:'); + moduleItem.primaryServiceBoundaries.forEach((item) => { + lines.push(` - ${item}`); + }); + lines.push('- 关键文件:'); + moduleItem.keyFiles.forEach((filePath) => { + lines.push(` - \`${filePath}\``); + }); + lines.push(''); + }); + + lines.push('## 维护规则'); + lines.push(''); + artifact.maintenanceRules.forEach((rule) => { + lines.push(`- ${rule}`); + }); + lines.push(''); + + return `${lines.join('\n')}`; +} + +async function writeArtifactFile(relativePath: string, content: string) { + const absolutePath = resolveRepoPath(relativePath); + await mkdir(path.dirname(absolutePath), { recursive: true }); + await writeFile(absolutePath, content, 'utf8'); +} + +async function main() { + await validateManifest(BACKEND_CAPABILITY_MANIFEST); + + const artifact = buildArtifact(BACKEND_CAPABILITY_MANIFEST); + const jsonContent = `${JSON.stringify(artifact, null, 2)}\n`; + const markdownContent = renderMarkdown(artifact); + + await writeArtifactFile(artifact.outputTargets.json, jsonContent); + await writeArtifactFile(artifact.outputTargets.markdown, markdownContent); + + console.log( + [ + `backend capability artifacts generated`, + `json=${artifact.outputTargets.json}`, + `markdown=${artifact.outputTargets.markdown}`, + `routes=${artifact.summary.routeCount}`, + `modules=${artifact.summary.moduleCount}`, + ].join(' | '), + ); +} + +void main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/server-node/src/manifest/backendCapabilityManifest.ts b/server-node/src/manifest/backendCapabilityManifest.ts new file mode 100644 index 00000000..d71c8898 --- /dev/null +++ b/server-node/src/manifest/backendCapabilityManifest.ts @@ -0,0 +1,1696 @@ +/** + * Node 后端能力清单的单一描述源。 + * + * 设计目标: + * 1. 把当前已经挂载的 HTTP 能力按“挂载面 / 路由 / 内部模块”三层收口。 + * 2. 让 JSON 清单与技术文档都从同一份 manifest 生成,避免重复维护。 + * 3. 当新增 `src/modules/*` 目录或新增对外接口时,生成脚本可以第一时间提示缺口。 + */ + +export type BackendRouteMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; + +export type BackendRouteResponseMode = + | 'json' + | 'stream' + | 'redirect' + | 'proxy'; + +export type BackendRouteMount = { + entryFile: string; + mountPath: string; + routeFactory: string; +}; + +export type BackendRouteSurface = { + id: string; + title: string; + mounts: BackendRouteMount[]; + responsibilities: string[]; + primaryServiceBoundaries: string[]; + relatedModuleIds: string[]; +}; + +export type BackendDomainModule = { + id: string; + title: string; + directory: string; + exposedBySurfaceIds: string[]; + responsibilities: string[]; + primaryServiceBoundaries: string[]; + keyFiles: string[]; +}; + +export type BackendRouteCapability = { + id: string; + surfaceId: string; + group: string; + method: BackendRouteMethod; + path: string; + operation: string; + access: string; + responseMode: BackendRouteResponseMode; + summary: string; + domainModuleIds: string[]; + sourceFile: string; + sourceHint: string; +}; + +export type BackendCapabilityManifest = { + version: string; + generatedCommand: string; + outputTargets: { + json: string; + markdown: string; + }; + maintenanceRules: string[]; + surfaces: BackendRouteSurface[]; + modules: BackendDomainModule[]; + routes: BackendRouteCapability[]; +}; + +const APP_SOURCE_FILE = 'server-node/src/app.ts'; +const AUTH_SOURCE_FILE = 'server-node/src/routes/authRoutes.ts'; +const RUNTIME_SOURCE_FILE = 'server-node/src/routes/runtimeRoutes.ts'; +const CUSTOM_WORLD_AGENT_SOURCE_FILE = + 'server-node/src/routes/customWorldAgent.ts'; +const STORY_ACTION_SOURCE_FILE = + 'server-node/src/modules/story/storyActionRoutes.ts'; +const EDITOR_SOURCE_FILE = 'server-node/src/modules/editor/editorRoutes.ts'; +const CHARACTER_ASSET_SOURCE_FILE = + 'server-node/src/modules/assets/characterAssetRoutes.ts'; +const QWEN_SPRITE_SOURCE_FILE = + 'server-node/src/modules/assets/qwenSpriteRoutes.ts'; + +/** + * 统一生成规范化路径,避免手写时出现重复斜杠。 + */ +function joinRoutePath(prefix: string, pathname: string) { + const normalizedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix; + const normalizedPath = pathname.startsWith('/') ? pathname : `/${pathname}`; + return `${normalizedPrefix}${normalizedPath}`; +} + +/** + * 让路由清单的创建保持扁平,后续生成脚本也能直接消费。 + */ +function defineRoute(route: BackendRouteCapability) { + return route; +} + +function defineAuthRoute( + route: Omit & { + path: string; + }, +) { + return defineRoute({ + ...route, + surfaceId: 'auth', + path: joinRoutePath('/api/auth', route.path), + sourceFile: AUTH_SOURCE_FILE, + }); +} + +function defineEditorRoute( + route: Omit, +) { + return defineRoute({ + ...route, + surfaceId: 'editor', + sourceFile: EDITOR_SOURCE_FILE, + }); +} + +function defineAssetRoute( + route: Omit, +) { + return defineRoute({ + ...route, + surfaceId: 'assets', + sourceFile: CHARACTER_ASSET_SOURCE_FILE, + }); +} + +function defineQwenAssetRoute( + route: Omit, +) { + return defineRoute({ + ...route, + surfaceId: 'assets', + sourceFile: QWEN_SPRITE_SOURCE_FILE, + }); +} + +function defineRuntimeRoute( + route: Omit & { + path: string; + }, +) { + return defineRoute({ + ...route, + surfaceId: 'runtime-main', + path: joinRoutePath('/api', route.path), + sourceFile: RUNTIME_SOURCE_FILE, + }); +} + +function defineRuntimeAgentRoute( + route: Omit & { + path: string; + }, +) { + return defineRoute({ + ...route, + surfaceId: 'runtime-main', + path: joinRoutePath('/api', route.path), + sourceFile: CUSTOM_WORLD_AGENT_SOURCE_FILE, + }); +} + +function defineStoryActionRoute( + route: Omit & { + path: string; + }, +) { + return defineRoute({ + ...route, + surfaceId: 'runtime-story-action', + path: joinRoutePath('/api/runtime/story', route.path), + sourceFile: STORY_ACTION_SOURCE_FILE, + }); +} + +/** + * runtime profile 仍然保留 `/api/profile/*` 与 `/api/runtime/profile/*` + * 两组路径,这里统一展开成两条清单记录,避免文档遗漏兼容入口。 + */ +function defineRuntimeCompatRoutes(params: { + idPrefix: string; + method: BackendRouteMethod; + basePath: string; + operation: string; + group: string; + access: string; + summary: string; + responseMode: BackendRouteResponseMode; + domainModuleIds: string[]; + sourceHint: string; +}) { + return [ + defineRuntimeRoute({ + id: `${params.idPrefix}.primary`, + group: params.group, + method: params.method, + path: params.basePath, + operation: params.operation, + access: params.access, + responseMode: params.responseMode, + summary: params.summary, + domainModuleIds: params.domainModuleIds, + sourceHint: params.sourceHint, + }), + defineRuntimeRoute({ + id: `${params.idPrefix}.compat`, + group: params.group, + method: params.method, + path: `/runtime${params.basePath}`, + operation: `${params.operation}.compat`, + access: params.access, + responseMode: params.responseMode, + summary: `${params.summary}(兼容路径)`, + domainModuleIds: params.domainModuleIds, + sourceHint: params.sourceHint, + }), + ] satisfies BackendRouteCapability[]; +} + +export const BACKEND_ROUTE_SURFACES: BackendRouteSurface[] = [ + { + id: 'health', + title: '基础健康检查', + mounts: [ + { + entryFile: APP_SOURCE_FILE, + mountPath: '/healthz', + routeFactory: 'createApp', + }, + ], + responsibilities: [ + '提供 Node 后端进程级健康探针。', + '给反向代理、部署平台和本地联调提供最小可用状态确认。', + ], + primaryServiceBoundaries: [ + '只返回服务静态信息,不触达数据库、鉴权或外部模型供应商。', + ], + relatedModuleIds: [], + }, + { + id: 'auth', + title: '鉴权与会话面', + mounts: [ + { + entryFile: APP_SOURCE_FILE, + mountPath: '/api/auth', + routeFactory: 'createAuthRoutes', + }, + ], + responsibilities: [ + '承接本地账号、短信验证码与微信登录流程。', + '管理 refresh session、用户信息、会话吊销、审计日志与风险拦截。', + ], + primaryServiceBoundaries: [ + 'HTTP 层只做 schema 校验、请求上下文拼装与 Cookie 管理,核心鉴权逻辑统一收口到 `server-node/src/auth/*`。', + '用户、身份、会话、风控与短信事件等持久化职责全部下沉到 repository 层,避免路由直接碰数据库细节。', + ], + relatedModuleIds: [], + }, + { + id: 'editor', + title: '编辑器工具面', + mounts: [ + { + entryFile: APP_SOURCE_FILE, + mountPath: '/api/editor', + routeFactory: 'createEditorRoutes', + }, + ], + responsibilities: [ + '读取编辑器资源 JSON。', + '回写编辑器覆盖文件。', + '枚举 `public/Icons` 下的物品图标资源。', + ], + primaryServiceBoundaries: [ + '只对工作区文件系统与 `public` 目录负责,不参与运行时数据库存储。', + '统一受 `EDITOR_API_ENABLED` 开关控制,生产环境可按需关闭。', + ], + relatedModuleIds: ['editor'], + }, + { + id: 'assets', + title: '资产生成工具面', + mounts: [ + { + entryFile: APP_SOURCE_FILE, + mountPath: '/api/assets', + routeFactory: 'createCharacterAssetRoutes', + }, + { + entryFile: APP_SOURCE_FILE, + mountPath: '/api/assets/qwen-sprite', + routeFactory: 'createQwenSpriteRoutes', + }, + ], + responsibilities: [ + '生成角色主形象、动作、动作模板与工作流缓存。', + '承接 Qwen 精灵表主图、整表、修帧与保存链路。', + '把产物发布到 `public/generated-*` 目录并落地局部 manifest。', + ], + primaryServiceBoundaries: [ + '负责对接 DashScope、Ark 等外部媒体供应商,但不维护 runtime 快照与业务状态。', + '统一受 `ASSETS_API_ENABLED` 开关控制,产物以文件与 JSON manifest 形式落在仓库工作区。', + ], + relatedModuleIds: ['assets'], + }, + { + id: 'runtime-main', + title: '运行时主能力面', + mounts: [ + { + entryFile: APP_SOURCE_FILE, + mountPath: '/api', + routeFactory: 'createRuntimeRoutes', + }, + { + entryFile: RUNTIME_SOURCE_FILE, + mountPath: '/runtime/custom-world/agent', + routeFactory: 'createCustomWorldAgentRoutes', + }, + ], + responsibilities: [ + '承接运行时资料库、公开画廊、存档、设置与个人档案接口。', + '承接剧情生成、聊天流、任务生成、运行时物品意图与自定义世界链路。', + '承接 Custom World Agent 会话、消息流和操作回放。', + ], + primaryServiceBoundaries: [ + 'HTTP contract 收口在 `runtimeRoutes.ts`,真正的世界生成、剧情、聊天、任务和资源逻辑继续下沉到 `services/*` 与 `src/modules/*`。', + '除公开画廊外,运行时接口统一走 JWT 鉴权,并依赖 `runtimeRepository`、session store 与 LLM client 执行。', + ], + relatedModuleIds: [ + 'ai', + 'custom-world', + 'quest', + 'runtime', + 'runtime-item', + 'story', + ], + }, + { + id: 'runtime-story-action', + title: '运行时 Story Action 面', + mounts: [ + { + entryFile: APP_SOURCE_FILE, + mountPath: '/api/runtime/story', + routeFactory: 'createStoryActionRoutes', + }, + ], + responsibilities: [ + '把前端 story choice 动作解析为新的运行时状态。', + '查询指定 story session 的可恢复状态。', + ], + primaryServiceBoundaries: [ + '路由层只做鉴权与 schema 校验,真正的动作分发与跨模块协作集中在 `storyActionService.ts`。', + 'Story Action 会联动 quest、inventory、runtime-item、npc 等内部模块,但对前端只暴露 story 这一条稳定入口。', + ], + relatedModuleIds: [ + 'story', + 'quest', + 'inventory', + 'runtime-item', + 'npc', + 'progression', + 'combat', + 'runtime', + ], + }, +]; + +export const BACKEND_DOMAIN_MODULES: BackendDomainModule[] = [ + { + id: 'ai', + title: 'AI 编排模块', + directory: 'server-node/src/modules/ai', + exposedBySurfaceIds: ['runtime-main'], + responsibilities: [ + '统一剧情、多轮聊天与自定义世界编排器的 prompt 构造与输出归一化。', + '屏蔽前端对不同 AI 链路的直接拼装细节。', + ], + primaryServiceBoundaries: [ + '专注提示词与编排,不负责持久化与 HTTP 传输。', + '通过 `services/llmClient.ts` 与外部模型交互,由路由与 service 层决定何时调用。', + ], + keyFiles: [ + 'server-node/src/modules/ai/chatOrchestrator.ts', + 'server-node/src/modules/ai/customWorldOrchestrator.ts', + 'server-node/src/modules/ai/storyOrchestrator.ts', + ], + }, + { + id: 'assets', + title: '资产工具模块', + directory: 'server-node/src/modules/assets', + exposedBySurfaceIds: ['assets'], + responsibilities: [ + '承接角色资产与 Qwen 精灵表的生成、查询、发布和保存。', + '维护资产流程需要的缓存、草稿与产物 manifest。', + ], + primaryServiceBoundaries: [ + '以文件系统和外部媒体模型为主要边界,不碰 runtimeRepository。', + '对外暴露稳定 HTTP 路径,对内通过私有 helper 处理媒体编码、任务轮询与写盘。', + ], + keyFiles: [ + 'server-node/src/modules/assets/characterAssetRoutes.ts', + 'server-node/src/modules/assets/qwenSpriteRoutes.ts', + ], + }, + { + id: 'combat', + title: '战斗结算模块', + directory: 'server-node/src/modules/combat', + exposedBySurfaceIds: ['runtime-story-action'], + responsibilities: [ + '提供运行时战斗结算与数值变更能力。', + '为 story action 里的战斗型交互提供纯计算服务。', + ], + primaryServiceBoundaries: [ + '聚焦状态推导与结果计算,不负责 transport 与持久化。', + ], + keyFiles: [ + 'server-node/src/modules/combat/combatResolutionService.ts', + ], + }, + { + id: 'custom-world', + title: '自定义世界运行时模块', + directory: 'server-node/src/modules/custom-world', + exposedBySurfaceIds: ['runtime-main'], + responsibilities: [ + '规范 creator intent、世界运行时类型与 profile compile。', + '把世界创作输入整理成运行时可消费的数据结构。', + ], + primaryServiceBoundaries: [ + '偏纯领域建模与 compile,不直接做 HTTP、数据库查询或模型调用。', + ], + keyFiles: [ + 'server-node/src/modules/custom-world/creatorIntentRuntime.ts', + 'server-node/src/modules/custom-world/runtimeProfile.ts', + 'server-node/src/modules/custom-world/runtimeTypes.ts', + ], + }, + { + id: 'editor', + title: '编辑器资源模块', + directory: 'server-node/src/modules/editor', + exposedBySurfaceIds: ['editor'], + responsibilities: [ + '提供编辑器资源目录枚举与 JSON 读写入口。', + ], + primaryServiceBoundaries: [ + '只负责工作区文件输入输出,不参与运行时业务计算。', + ], + keyFiles: ['server-node/src/modules/editor/editorRoutes.ts'], + }, + { + id: 'inventory', + title: '背包与物品变更模块', + directory: 'server-node/src/modules/inventory', + exposedBySurfaceIds: ['runtime-story-action'], + responsibilities: [ + '维护背包变更、NPC 背包交互与 story action 里的物品副作用。', + ], + primaryServiceBoundaries: [ + '对运行时状态做局部变更,不直接暴露 HTTP 路由。', + ], + keyFiles: [ + 'server-node/src/modules/inventory/inventoryMutationService.ts', + 'server-node/src/modules/inventory/inventoryStoryActionService.ts', + 'server-node/src/modules/inventory/npcInventoryStoryActionService.ts', + ], + }, + { + id: 'npc', + title: 'NPC 交互模块', + directory: 'server-node/src/modules/npc', + exposedBySurfaceIds: ['runtime-story-action', 'runtime-main'], + responsibilities: [ + '维护 NPC 互动规则、任务 primitive 与关系变更逻辑。', + ], + primaryServiceBoundaries: [ + '专注 NPC 侧状态推导,供 story action 与聊天/任务链路复用。', + ], + keyFiles: [ + 'server-node/src/modules/npc/npcInteractionService.ts', + 'server-node/src/modules/npc/npcTask6Primitives.ts', + ], + }, + { + id: 'progression', + title: '成长与关卡进程模块', + directory: 'server-node/src/modules/progression', + exposedBySurfaceIds: ['runtime-story-action', 'runtime-main'], + responsibilities: [ + '提供角色成长、敌对等级、章节推进与 benchmark 逻辑。', + ], + primaryServiceBoundaries: [ + '只做成长数值与章节进度计算,由 runtime hydrate 与 story action 复用。', + ], + keyFiles: [ + 'server-node/src/modules/progression/playerProgressionService.ts', + 'server-node/src/modules/progression/hostileProgressionService.ts', + 'server-node/src/modules/progression/chapterProgressionPlanner.ts', + ], + }, + { + id: 'quest', + title: '任务运行时模块', + directory: 'server-node/src/modules/quest', + exposedBySurfaceIds: ['runtime-main', 'runtime-story-action'], + responsibilities: [ + '生成任务意图、维护任务日志与处理任务进度信号。', + '为运行时 quest 接口与 story action 提供统一任务语义。', + ], + primaryServiceBoundaries: [ + '领域逻辑以 quest module 为中心,AI 生成只是一种输入来源。', + '不直接处理 HTTP 响应,统一由 routes/service 层调用。', + ], + keyFiles: [ + 'server-node/src/modules/quest/runtimeQuestModule.ts', + 'server-node/src/modules/quest/questProgressionService.ts', + 'server-node/src/modules/quest/questStoryActionService.ts', + ], + }, + { + id: 'runtime', + title: '运行时状态基座模块', + directory: 'server-node/src/modules/runtime', + exposedBySurfaceIds: ['runtime-main', 'runtime-story-action'], + responsibilities: [ + '定义运行时状态 primitive、经济与装备规则。', + '负责存档 hydration、兼容迁移与状态归一化。', + ], + primaryServiceBoundaries: [ + '是 runtimeRepository 与 story action 的共同状态基座,不承担 HTTP 入口职责。', + ], + keyFiles: [ + 'server-node/src/modules/runtime/runtimeSnapshotHydration.ts', + 'server-node/src/modules/runtime/runtimeStatePrimitives.ts', + 'server-node/src/modules/runtime/runtimeEquipmentModule.ts', + ], + }, + { + id: 'runtime-item', + title: '运行时物品模块', + directory: 'server-node/src/modules/runtime-item', + exposedBySurfaceIds: ['runtime-main', 'runtime-story-action'], + responsibilities: [ + '生成运行时物品意图、物品奖励与剧情指纹。', + '维护宝藏与物品解析逻辑。', + ], + primaryServiceBoundaries: [ + '聚焦物品领域编译与奖励拼装,由 route/service 选择具体触发时机。', + ], + keyFiles: [ + 'server-node/src/modules/runtime-item/runtimeItemModule.ts', + 'server-node/src/modules/runtime-item/runtimeItemResolutionService.ts', + 'server-node/src/modules/runtime-item/treasureStoryActionService.ts', + ], + }, + { + id: 'story', + title: '故事会话模块', + directory: 'server-node/src/modules/story', + exposedBySurfaceIds: ['runtime-main', 'runtime-story-action'], + responsibilities: [ + '维护运行时故事会话状态与 action 分发。', + '为 story resolve、story state 查询提供统一入口。', + ], + primaryServiceBoundaries: [ + 'story 模块是 runtime 主循环的编排层,必要时再向 quest、inventory、combat 等领域模块分发。', + ], + keyFiles: [ + 'server-node/src/modules/story/runtimeSession.ts', + 'server-node/src/modules/story/storyActionRoutes.ts', + 'server-node/src/modules/story/storyActionService.ts', + ], + }, +]; + +export const BACKEND_ROUTE_CAPABILITIES: BackendRouteCapability[] = [ + defineRoute({ + id: 'health.check', + surfaceId: 'health', + group: 'health', + method: 'GET', + path: '/healthz', + operation: 'health.check', + access: '公开', + responseMode: 'json', + summary: '返回 Node 后端进程健康状态。', + domainModuleIds: [], + sourceFile: APP_SOURCE_FILE, + sourceHint: '/healthz', + }), + + defineAuthRoute({ + id: 'auth.loginOptions', + group: 'auth-entry', + method: 'GET', + path: '/login-options', + operation: 'auth.login_options', + access: '公开', + responseMode: 'json', + summary: '返回当前启用的登录方式与入口配置。', + domainModuleIds: [], + sourceHint: '/login-options', + }), + defineAuthRoute({ + id: 'auth.entry', + group: 'auth-entry', + method: 'POST', + path: '/entry', + operation: 'auth.entry', + access: '公开', + responseMode: 'json', + summary: '用户名密码登录;不存在则创建本地账号。', + domainModuleIds: [], + sourceHint: '/entry', + }), + defineAuthRoute({ + id: 'auth.phoneSendCode', + group: 'auth-phone', + method: 'POST', + path: '/phone/send-code', + operation: 'auth.phone.send_code', + access: '公开', + responseMode: 'json', + summary: '发送手机号登录或绑定验证码。', + domainModuleIds: [], + sourceHint: '/phone/send-code', + }), + defineAuthRoute({ + id: 'auth.phoneChange', + group: 'auth-phone', + method: 'POST', + path: '/phone/change', + operation: 'auth.phone.change', + access: 'JWT', + responseMode: 'json', + summary: '已登录用户更换绑定手机号。', + domainModuleIds: [], + sourceHint: '/phone/change', + }), + defineAuthRoute({ + id: 'auth.phoneLogin', + group: 'auth-phone', + method: 'POST', + path: '/phone/login', + operation: 'auth.phone.login', + access: '公开', + responseMode: 'json', + summary: '手机号验证码登录。', + domainModuleIds: [], + sourceHint: '/phone/login', + }), + defineAuthRoute({ + id: 'auth.wechatStart', + group: 'auth-wechat', + method: 'GET', + path: '/wechat/start', + operation: 'auth.wechat.start', + access: '公开', + responseMode: 'json', + summary: '发起微信登录并返回授权 URL。', + domainModuleIds: [], + sourceHint: '/wechat/start', + }), + defineAuthRoute({ + id: 'auth.wechatCallback', + group: 'auth-wechat', + method: 'GET', + path: '/wechat/callback', + operation: 'auth.wechat.callback', + access: '公开', + responseMode: 'redirect', + summary: '处理微信回调并重定向回前端。', + domainModuleIds: [], + sourceHint: '/wechat/callback', + }), + defineAuthRoute({ + id: 'auth.wechatBindPhone', + group: 'auth-wechat', + method: 'POST', + path: '/wechat/bind-phone', + operation: 'auth.wechat.bind_phone', + access: 'JWT', + responseMode: 'json', + summary: '为已登录微信账号绑定手机号。', + domainModuleIds: [], + sourceHint: '/wechat/bind-phone', + }), + defineAuthRoute({ + id: 'auth.refresh', + group: 'auth-session', + method: 'POST', + path: '/refresh', + operation: 'auth.refresh', + access: '公开', + responseMode: 'json', + summary: '使用 refresh session 刷新 JWT。', + domainModuleIds: [], + sourceHint: '/refresh', + }), + defineAuthRoute({ + id: 'auth.riskBlocks', + group: 'auth-risk', + method: 'GET', + path: '/risk-blocks', + operation: 'auth.risk_blocks', + access: 'JWT', + responseMode: 'json', + summary: '查询当前用户命中的风控封禁。', + domainModuleIds: [], + sourceHint: '/risk-blocks', + }), + defineAuthRoute({ + id: 'auth.riskBlocksLift', + group: 'auth-risk', + method: 'POST', + path: '/risk-blocks/:scopeType/lift', + operation: 'auth.risk_blocks.lift', + access: 'JWT', + responseMode: 'json', + summary: '请求解除指定维度的风控拦截。', + domainModuleIds: [], + sourceHint: '/risk-blocks/:scopeType/lift', + }), + defineAuthRoute({ + id: 'auth.sessions', + group: 'auth-session', + method: 'GET', + path: '/sessions', + operation: 'auth.sessions', + access: 'JWT', + responseMode: 'json', + summary: '列出当前账号的活跃会话。', + domainModuleIds: [], + sourceHint: '/sessions', + }), + defineAuthRoute({ + id: 'auth.sessionRevoke', + group: 'auth-session', + method: 'POST', + path: '/sessions/:sessionId/revoke', + operation: 'auth.sessions.revoke', + access: 'JWT', + responseMode: 'json', + summary: '吊销指定会话。', + domainModuleIds: [], + sourceHint: '/sessions/:sessionId/revoke', + }), + defineAuthRoute({ + id: 'auth.auditLogs', + group: 'auth-audit', + method: 'GET', + path: '/audit-logs', + operation: 'auth.audit_logs', + access: 'JWT', + responseMode: 'json', + summary: '查询当前账号的鉴权审计日志。', + domainModuleIds: [], + sourceHint: '/audit-logs', + }), + defineAuthRoute({ + id: 'auth.me', + group: 'auth-profile', + method: 'GET', + path: '/me', + operation: 'auth.me', + access: 'JWT', + responseMode: 'json', + summary: '读取当前登录用户的鉴权资料。', + domainModuleIds: [], + sourceHint: '/me', + }), + defineAuthRoute({ + id: 'auth.logoutAll', + group: 'auth-session', + method: 'POST', + path: '/logout-all', + operation: 'auth.logout_all', + access: 'JWT', + responseMode: 'json', + summary: '退出当前账号的全部会话。', + domainModuleIds: [], + sourceHint: '/logout-all', + }), + defineAuthRoute({ + id: 'auth.logout', + group: 'auth-session', + method: 'POST', + path: '/logout', + operation: 'auth.logout', + access: 'JWT', + responseMode: 'json', + summary: '退出当前会话并清理 refresh cookie。', + domainModuleIds: [], + sourceHint: '/logout', + }), + + defineEditorRoute({ + id: 'editor.catalogItems', + group: 'editor-catalog', + method: 'GET', + path: '/api/editor/catalog/items', + operation: 'editor.catalog.items.list', + access: '开关: EDITOR_API_ENABLED', + responseMode: 'json', + summary: '列出 `public/Icons` 下的物品图标资源。', + domainModuleIds: ['editor'], + sourceHint: '/api/editor/catalog/items', + }), + defineEditorRoute({ + id: 'editor.resourceRead', + group: 'editor-json', + method: 'GET', + path: '/api/editor/json/:resourceId', + operation: 'editor.resource.read', + access: '开关: EDITOR_API_ENABLED', + responseMode: 'json', + summary: '读取指定编辑器资源 JSON。', + domainModuleIds: ['editor'], + sourceHint: '/api/editor/json/:resourceId', + }), + defineEditorRoute({ + id: 'editor.resourceWrite', + group: 'editor-json', + method: 'POST', + path: '/api/editor/json/:resourceId', + operation: 'editor.resource.write', + access: '开关: EDITOR_API_ENABLED', + responseMode: 'json', + summary: '回写指定编辑器资源 JSON。', + domainModuleIds: ['editor'], + sourceHint: '/api/editor/json/:resourceId', + }), + + defineAssetRoute({ + id: 'assets.characterWorkflowCacheSave', + group: 'assets-character-cache', + method: 'POST', + path: '/api/assets/character-workflow-cache', + operation: 'assets.character.workflowCache.save', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '保存角色资产工作流缓存。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.workflowCache.save', + }), + defineAssetRoute({ + id: 'assets.characterWorkflowCacheGet', + group: 'assets-character-cache', + method: 'GET', + path: '/api/assets/character-workflow-cache/:characterId', + operation: 'assets.character.workflowCache.get', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '按角色读取角色资产工作流缓存。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.workflowCache.get', + }), + defineAssetRoute({ + id: 'assets.characterVisualGenerate', + group: 'assets-character-visual', + method: 'POST', + path: '/api/assets/character-visual/generate', + operation: 'assets.character.visual.generate', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '生成角色主形象候选图。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.visual.generate', + }), + defineAssetRoute({ + id: 'assets.characterVisualPublish', + group: 'assets-character-visual', + method: 'POST', + path: '/api/assets/character-visual/publish', + operation: 'assets.character.visual.publish', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '发布选中的角色主形象到 public 目录。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.visual.publish', + }), + defineAssetRoute({ + id: 'assets.characterVisualJobGet', + group: 'assets-character-visual', + method: 'GET', + path: '/api/assets/character-visual/jobs/:taskId', + operation: 'assets.character.visual.job.get', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '查询角色主形象生成任务状态。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.visual.job.get', + }), + defineAssetRoute({ + id: 'assets.characterAnimationGenerate', + group: 'assets-character-animation', + method: 'POST', + path: '/api/assets/character-animation/generate', + operation: 'assets.character.animation.generate', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '生成角色动作草稿。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.animation.generate', + }), + defineAssetRoute({ + id: 'assets.characterAnimationPublish', + group: 'assets-character-animation', + method: 'POST', + path: '/api/assets/character-animation/publish', + operation: 'assets.character.animation.publish', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '发布角色动作帧集到 public 目录。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.animation.publish', + }), + defineAssetRoute({ + id: 'assets.characterAnimationJobGet', + group: 'assets-character-animation', + method: 'GET', + path: '/api/assets/character-animation/jobs/:taskId', + operation: 'assets.character.animation.job.get', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '查询角色动作生成任务状态。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.animation.job.get', + }), + defineAssetRoute({ + id: 'assets.characterAnimationImportVideo', + group: 'assets-character-animation', + method: 'POST', + path: '/api/assets/character-animation/import-video', + operation: 'assets.character.animation.importVideo', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '导入动作参考视频并转为可消费素材。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.animation.importVideo', + }), + defineAssetRoute({ + id: 'assets.characterAnimationTemplatesList', + group: 'assets-character-animation', + method: 'GET', + path: '/api/assets/character-animation/templates', + operation: 'assets.character.animation.templates.list', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '列出内置角色动作模板。', + domainModuleIds: ['assets'], + sourceHint: 'assets.character.animation.templates.list', + }), + defineQwenAssetRoute({ + id: 'assets.qwenSpriteMasterGenerate', + group: 'assets-qwen', + method: 'POST', + path: '/api/assets/qwen-sprite/master', + operation: 'assets.qwenSprite.master.generate', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '生成 Qwen 精灵主图。', + domainModuleIds: ['assets'], + sourceHint: 'assets.qwenSprite.master.generate', + }), + defineQwenAssetRoute({ + id: 'assets.qwenSpriteSheetGenerate', + group: 'assets-qwen', + method: 'POST', + path: '/api/assets/qwen-sprite/sheet', + operation: 'assets.qwenSprite.sheet.generate', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '生成 Qwen 精灵表。', + domainModuleIds: ['assets'], + sourceHint: 'assets.qwenSprite.sheet.generate', + }), + defineQwenAssetRoute({ + id: 'assets.qwenSpriteFrameRepairGenerate', + group: 'assets-qwen', + method: 'POST', + path: '/api/assets/qwen-sprite/frame-repair', + operation: 'assets.qwenSprite.frameRepair.generate', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '对单帧做 Qwen 修复。', + domainModuleIds: ['assets'], + sourceHint: 'assets.qwenSprite.frameRepair.generate', + }), + defineQwenAssetRoute({ + id: 'assets.qwenSpriteAssetSave', + group: 'assets-qwen', + method: 'POST', + path: '/api/assets/qwen-sprite/save', + operation: 'assets.qwenSprite.asset.save', + access: '开关: ASSETS_API_ENABLED', + responseMode: 'json', + summary: '保存 Qwen 精灵资产到 public 目录。', + domainModuleIds: ['assets'], + sourceHint: 'assets.qwenSprite.asset.save', + }), + + defineStoryActionRoute({ + id: 'storyAction.resolve', + group: 'story-action', + method: 'POST', + path: '/actions/resolve', + operation: 'runtime.story.actions.resolve', + access: 'JWT', + responseMode: 'json', + summary: '解析前端 story choice 动作为新的运行时结果。', + domainModuleIds: [ + 'story', + 'quest', + 'inventory', + 'runtime-item', + 'npc', + 'progression', + 'combat', + 'runtime', + ], + sourceHint: '/actions/resolve', + }), + defineStoryActionRoute({ + id: 'storyAction.stateGet', + group: 'story-action', + method: 'GET', + path: '/state/:sessionId', + operation: 'runtime.story.state.get', + access: 'JWT', + responseMode: 'json', + summary: '读取指定 story session 的运行时状态。', + domainModuleIds: ['story', 'runtime'], + sourceHint: '/state/:sessionId', + }), + + defineRuntimeRoute({ + id: 'runtime.customWorldGalleryList', + group: 'runtime-gallery', + method: 'GET', + path: '/runtime/custom-world-gallery', + operation: 'runtime.customWorldGallery.list', + access: '公开', + responseMode: 'json', + summary: '列出公开的自定义世界画廊。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-gallery', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldGalleryDetail', + group: 'runtime-gallery', + method: 'GET', + path: '/runtime/custom-world-gallery/:ownerUserId/:profileId', + operation: 'runtime.customWorldGallery.detail', + access: '公开', + responseMode: 'json', + summary: '读取指定公开世界作品详情。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-gallery/:ownerUserId/:profileId', + }), + + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentCreateSession', + group: 'runtime-custom-world-agent', + method: 'POST', + path: '/runtime/custom-world/agent/sessions', + operation: 'runtime.customWorldAgent.createSession', + access: 'JWT', + responseMode: 'json', + summary: '创建 Custom World Agent 会话。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/sessions', + }), + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentGetSession', + group: 'runtime-custom-world-agent', + method: 'GET', + path: '/runtime/custom-world/agent/sessions/:sessionId', + operation: 'runtime.customWorldAgent.getSession', + access: 'JWT', + responseMode: 'json', + summary: '读取 Agent 会话快照。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/sessions/:sessionId', + }), + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentSendMessage', + group: 'runtime-custom-world-agent', + method: 'POST', + path: '/runtime/custom-world/agent/sessions/:sessionId/messages', + operation: 'runtime.customWorldAgent.sendMessage', + access: 'JWT', + responseMode: 'json', + summary: '向 Agent 会话提交一条创作消息。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/sessions/:sessionId/messages', + }), + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentStreamMessage', + group: 'runtime-custom-world-agent', + method: 'POST', + path: '/runtime/custom-world/agent/sessions/:sessionId/messages/stream', + operation: 'runtime.customWorldAgent.streamMessage', + access: 'JWT', + responseMode: 'stream', + summary: '流式提交 Agent 消息并实时接收回执。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/sessions/:sessionId/messages/stream', + }), + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentExecuteAction', + group: 'runtime-custom-world-agent', + method: 'POST', + path: '/runtime/custom-world/agent/sessions/:sessionId/actions', + operation: 'runtime.customWorldAgent.executeAction', + access: 'JWT', + responseMode: 'json', + summary: '执行 Agent 卡片生成、资产同步或发布动作。', + domainModuleIds: ['custom-world', 'ai', 'assets'], + sourceHint: '/sessions/:sessionId/actions', + }), + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentGetOperation', + group: 'runtime-custom-world-agent', + method: 'GET', + path: '/runtime/custom-world/agent/sessions/:sessionId/operations/:operationId', + operation: 'runtime.customWorldAgent.getOperation', + access: 'JWT', + responseMode: 'json', + summary: '查询 Agent 后台操作状态。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/sessions/:sessionId/operations/:operationId', + }), + defineRuntimeAgentRoute({ + id: 'runtime.customWorldAgentGetCardDetail', + group: 'runtime-custom-world-agent', + method: 'GET', + path: '/runtime/custom-world/agent/sessions/:sessionId/cards/:cardId', + operation: 'runtime.customWorldAgent.getCardDetail', + access: 'JWT', + responseMode: 'json', + summary: '读取 Agent 卡片详情。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/sessions/:sessionId/cards/:cardId', + }), + + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profileDashboard', + method: 'GET', + basePath: '/profile/dashboard', + operation: 'profile.dashboard.get', + group: 'runtime-profile', + access: 'JWT', + responseMode: 'json', + summary: '读取运行时个人主页汇总。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/dashboard')", + }), + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profileWalletLedger', + method: 'GET', + basePath: '/profile/wallet-ledger', + operation: 'profile.walletLedger.list', + group: 'runtime-profile', + access: 'JWT', + responseMode: 'json', + summary: '列出个人资产流水。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/wallet-ledger')", + }), + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profilePlayStats', + method: 'GET', + basePath: '/profile/play-stats', + operation: 'profile.playStats.get', + group: 'runtime-profile', + access: 'JWT', + responseMode: 'json', + summary: '读取个人游玩统计。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/play-stats')", + }), + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profileBrowseHistoryGet', + method: 'GET', + basePath: '/profile/browse-history', + operation: 'profile.browseHistory.list', + group: 'runtime-profile', + access: 'JWT', + responseMode: 'json', + summary: '读取平台浏览历史。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/browse-history')", + }), + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profileBrowseHistoryPost', + method: 'POST', + basePath: '/profile/browse-history', + operation: 'profile.browseHistory.upsert', + group: 'runtime-profile', + access: 'JWT', + responseMode: 'json', + summary: '写入或批量同步平台浏览历史。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/browse-history')", + }), + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profileBrowseHistoryDelete', + method: 'DELETE', + basePath: '/profile/browse-history', + operation: 'profile.browseHistory.clear', + group: 'runtime-profile', + access: 'JWT', + responseMode: 'json', + summary: '清空平台浏览历史。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/browse-history')", + }), + ...defineRuntimeCompatRoutes({ + idPrefix: 'runtime.profileSaveArchivesList', + method: 'GET', + basePath: '/profile/save-archives', + operation: 'profile.saveArchives.list', + group: 'runtime-save', + access: 'JWT', + responseMode: 'json', + summary: '列出个人存档摘要。', + domainModuleIds: ['runtime'], + sourceHint: "routeCompatPaths('/profile/save-archives')", + }), + defineRuntimeRoute({ + id: 'runtime.profileSaveArchivesResume.primary', + group: 'runtime-save', + method: 'POST', + path: '/profile/save-archives/:worldKey', + operation: 'profile.saveArchives.resume', + access: 'JWT', + responseMode: 'json', + summary: '恢复指定世界的最近存档。', + domainModuleIds: ['runtime'], + sourceHint: "'/profile/save-archives/:worldKey'", + }), + defineRuntimeRoute({ + id: 'runtime.profileSaveArchivesResume.compat', + group: 'runtime-save', + method: 'POST', + path: '/runtime/profile/save-archives/:worldKey', + operation: 'profile.saveArchives.resume.compat', + access: 'JWT', + responseMode: 'json', + summary: '恢复指定世界的最近存档(兼容路径)。', + domainModuleIds: ['runtime'], + sourceHint: "'/runtime/profile/save-archives/:worldKey'", + }), + + defineRuntimeRoute({ + id: 'runtime.llmChatCompletionsProxy', + group: 'runtime-proxy', + method: 'POST', + path: '/llm/chat/completions', + operation: 'runtime.llm.chatCompletionsProxy', + access: 'JWT', + responseMode: 'proxy', + summary: '把聊天补全请求透传到上游模型。', + domainModuleIds: ['ai'], + sourceHint: '/llm/chat/completions', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldCoverImage', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/custom-world/cover-image', + operation: 'runtime.customWorld.coverImage', + access: 'JWT', + responseMode: 'json', + summary: '生成自定义世界封面图。', + domainModuleIds: ['custom-world', 'assets'], + sourceHint: '/custom-world/cover-image', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldCoverUpload', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/custom-world/cover-upload', + operation: 'runtime.customWorld.coverUpload', + access: 'JWT', + responseMode: 'json', + summary: '上传并落地自定义世界封面图。', + domainModuleIds: ['custom-world', 'assets'], + sourceHint: '/custom-world/cover-upload', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldSceneImage', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/custom-world/scene-image', + operation: 'runtime.customWorld.sceneImage', + access: 'JWT', + responseMode: 'json', + summary: '生成自定义世界场景图。', + domainModuleIds: ['custom-world', 'assets'], + sourceHint: '/custom-world/scene-image', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldEntity.primary', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/custom-world/entity', + operation: 'runtime.customWorld.entity', + access: 'JWT', + responseMode: 'json', + summary: '按世界 profile 生成单个角色或地标实体。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/custom-world/entity', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldEntity.compat', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/runtime/custom-world/entity', + operation: 'runtime.customWorld.entity.compat', + access: 'JWT', + responseMode: 'json', + summary: '按世界 profile 生成单个角色或地标实体(兼容路径)。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/runtime/custom-world/entity', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldSceneNpc.primary', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/custom-world/scene-npc', + operation: 'runtime.customWorld.sceneNpc', + access: 'JWT', + responseMode: 'json', + summary: '按地标生成场景 NPC。', + domainModuleIds: ['custom-world', 'ai', 'npc'], + sourceHint: '/custom-world/scene-npc', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldSceneNpc.compat', + group: 'runtime-custom-world-assets', + method: 'POST', + path: '/runtime/custom-world/scene-npc', + operation: 'runtime.customWorld.sceneNpc.compat', + access: 'JWT', + responseMode: 'json', + summary: '按地标生成场景 NPC(兼容路径)。', + domainModuleIds: ['custom-world', 'ai', 'npc'], + sourceHint: '/runtime/custom-world/scene-npc', + }), + + defineRuntimeRoute({ + id: 'runtime.snapshotGet', + group: 'runtime-save', + method: 'GET', + path: '/runtime/save/snapshot', + operation: 'runtime.snapshot.get', + access: 'JWT', + responseMode: 'json', + summary: '读取当前用户的运行时存档。', + domainModuleIds: ['runtime', 'progression', 'quest'], + sourceHint: '/runtime/save/snapshot', + }), + defineRuntimeRoute({ + id: 'runtime.snapshotPut', + group: 'runtime-save', + method: 'PUT', + path: '/runtime/save/snapshot', + operation: 'runtime.snapshot.put', + access: 'JWT', + responseMode: 'json', + summary: '保存并归一化当前运行时存档。', + domainModuleIds: ['runtime', 'progression', 'quest'], + sourceHint: '/runtime/save/snapshot', + }), + defineRuntimeRoute({ + id: 'runtime.snapshotDelete', + group: 'runtime-save', + method: 'DELETE', + path: '/runtime/save/snapshot', + operation: 'runtime.snapshot.delete', + access: 'JWT', + responseMode: 'json', + summary: '删除当前用户的运行时存档。', + domainModuleIds: ['runtime'], + sourceHint: '/runtime/save/snapshot', + }), + defineRuntimeRoute({ + id: 'runtime.settingsGet', + group: 'runtime-settings', + method: 'GET', + path: '/runtime/settings', + operation: 'runtime.settings.get', + access: 'JWT', + responseMode: 'json', + summary: '读取运行时设置。', + domainModuleIds: ['runtime'], + sourceHint: '/runtime/settings', + }), + defineRuntimeRoute({ + id: 'runtime.settingsPut', + group: 'runtime-settings', + method: 'PUT', + path: '/runtime/settings', + operation: 'runtime.settings.put', + access: 'JWT', + responseMode: 'json', + summary: '更新运行时设置。', + domainModuleIds: ['runtime'], + sourceHint: '/runtime/settings', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldWorksList', + group: 'runtime-custom-world-library', + method: 'GET', + path: '/runtime/custom-world/works', + operation: 'runtime.customWorldWorks.list', + access: 'JWT', + responseMode: 'json', + summary: '列出当前账号的自定义世界作品汇总。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world/works', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldLibraryList', + group: 'runtime-custom-world-library', + method: 'GET', + path: '/runtime/custom-world-library', + operation: 'runtime.customWorldLibrary.list', + access: 'JWT', + responseMode: 'json', + summary: '列出当前账号的自定义世界资料库。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-library', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldLibraryUpsert', + group: 'runtime-custom-world-library', + method: 'PUT', + path: '/runtime/custom-world-library/:profileId', + operation: 'runtime.customWorldLibrary.upsert', + access: 'JWT', + responseMode: 'json', + summary: '写入或更新指定自定义世界 profile。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-library/:profileId', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldLibraryDelete', + group: 'runtime-custom-world-library', + method: 'DELETE', + path: '/runtime/custom-world-library/:profileId', + operation: 'runtime.customWorldLibrary.delete', + access: 'JWT', + responseMode: 'json', + summary: '删除指定自定义世界 profile。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-library/:profileId', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldLibraryPublish', + group: 'runtime-custom-world-library', + method: 'POST', + path: '/runtime/custom-world-library/:profileId/publish', + operation: 'runtime.customWorldLibrary.publish', + access: 'JWT', + responseMode: 'json', + summary: '发布指定世界到公开画廊。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-library/:profileId/publish', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldLibraryUnpublish', + group: 'runtime-custom-world-library', + method: 'POST', + path: '/runtime/custom-world-library/:profileId/unpublish', + operation: 'runtime.customWorldLibrary.unpublish', + access: 'JWT', + responseMode: 'json', + summary: '撤回指定世界的公开发布状态。', + domainModuleIds: ['custom-world', 'runtime'], + sourceHint: '/runtime/custom-world-library/:profileId/unpublish', + }), + + defineRuntimeRoute({ + id: 'runtime.storyInitial', + group: 'runtime-story-generation', + method: 'POST', + path: '/runtime/story/initial', + operation: 'runtime.story.initial', + access: 'JWT', + responseMode: 'json', + summary: '生成首段故事内容。', + domainModuleIds: ['story', 'ai'], + sourceHint: '/runtime/story/initial', + }), + defineRuntimeRoute({ + id: 'runtime.storyContinue', + group: 'runtime-story-generation', + method: 'POST', + path: '/runtime/story/continue', + operation: 'runtime.story.continue', + access: 'JWT', + responseMode: 'json', + summary: '生成下一段故事内容。', + domainModuleIds: ['story', 'ai'], + sourceHint: '/runtime/story/continue', + }), + defineRuntimeRoute({ + id: 'runtime.characterSuggestions', + group: 'runtime-chat', + method: 'POST', + path: '/runtime/chat/character/suggestions', + operation: 'runtime.chat.character.suggestions', + access: 'JWT', + responseMode: 'json', + summary: '生成角色聊天建议语。', + domainModuleIds: ['ai', 'story'], + sourceHint: '/runtime/chat/character/suggestions', + }), + defineRuntimeRoute({ + id: 'runtime.characterSummary', + group: 'runtime-chat', + method: 'POST', + path: '/runtime/chat/character/summary', + operation: 'runtime.chat.character.summary', + access: 'JWT', + responseMode: 'json', + summary: '生成角色聊天摘要。', + domainModuleIds: ['ai', 'story'], + sourceHint: '/runtime/chat/character/summary', + }), + defineRuntimeRoute({ + id: 'runtime.characterReplyStream', + group: 'runtime-chat', + method: 'POST', + path: '/runtime/chat/character/reply/stream', + operation: 'runtime.chat.character.replyStream', + access: 'JWT', + responseMode: 'stream', + summary: '流式生成角色回复。', + domainModuleIds: ['ai', 'story'], + sourceHint: '/runtime/chat/character/reply/stream', + }), + defineRuntimeRoute({ + id: 'runtime.npcDialogueStream', + group: 'runtime-chat', + method: 'POST', + path: '/runtime/chat/npc/dialogue/stream', + operation: 'runtime.chat.npc.dialogueStream', + access: 'JWT', + responseMode: 'stream', + summary: '流式生成 NPC 对话。', + domainModuleIds: ['ai', 'npc', 'story'], + sourceHint: '/runtime/chat/npc/dialogue/stream', + }), + defineRuntimeRoute({ + id: 'runtime.npcTurnStream', + group: 'runtime-chat', + method: 'POST', + path: '/runtime/chat/npc/turn/stream', + operation: 'runtime.chat.npc.turnStream', + access: 'JWT', + responseMode: 'stream', + summary: '流式生成 NPC 单回合发言。', + domainModuleIds: ['ai', 'npc', 'story'], + sourceHint: '/runtime/chat/npc/turn/stream', + }), + defineRuntimeRoute({ + id: 'runtime.npcRecruitStream', + group: 'runtime-chat', + method: 'POST', + path: '/runtime/chat/npc/recruit/stream', + operation: 'runtime.chat.npc.recruitStream', + access: 'JWT', + responseMode: 'stream', + summary: '流式生成招募 NPC 对话。', + domainModuleIds: ['ai', 'npc', 'story'], + sourceHint: '/runtime/chat/npc/recruit/stream', + }), + + defineRuntimeRoute({ + id: 'runtime.customWorldSessionCreate', + group: 'runtime-custom-world-session', + method: 'POST', + path: '/runtime/custom-world/sessions', + operation: 'runtime.customWorldSession.create', + access: 'JWT', + responseMode: 'json', + summary: '创建传统自定义世界问答会话。', + domainModuleIds: ['custom-world'], + sourceHint: '/runtime/custom-world/sessions', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldSessionGet', + group: 'runtime-custom-world-session', + method: 'GET', + path: '/runtime/custom-world/sessions/:sessionId', + operation: 'runtime.customWorldSession.get', + access: 'JWT', + responseMode: 'json', + summary: '读取传统自定义世界问答会话。', + domainModuleIds: ['custom-world'], + sourceHint: '/runtime/custom-world/sessions/:sessionId', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldSessionAnswer', + group: 'runtime-custom-world-session', + method: 'POST', + path: '/runtime/custom-world/sessions/:sessionId/answers', + operation: 'runtime.customWorldSession.answer', + access: 'JWT', + responseMode: 'json', + summary: '回答传统自定义世界问答题目。', + domainModuleIds: ['custom-world'], + sourceHint: '/runtime/custom-world/sessions/:sessionId/answers', + }), + defineRuntimeRoute({ + id: 'runtime.customWorldSessionGenerateStream', + group: 'runtime-custom-world-session', + method: 'GET', + path: '/runtime/custom-world/sessions/:sessionId/generate/stream', + operation: 'runtime.customWorldSession.generateStream', + access: 'JWT', + responseMode: 'stream', + summary: '流式编译传统自定义世界 profile。', + domainModuleIds: ['custom-world', 'ai'], + sourceHint: '/runtime/custom-world/sessions/:sessionId/generate/stream', + }), + + defineRuntimeRoute({ + id: 'runtime.itemsIntent', + group: 'runtime-loot', + method: 'POST', + path: '/runtime/items/runtime-intent', + operation: 'runtime.items.intent', + access: 'JWT', + responseMode: 'json', + summary: '生成运行时物品意图。', + domainModuleIds: ['runtime-item', 'ai'], + sourceHint: '/runtime/items/runtime-intent', + }), + defineRuntimeRoute({ + id: 'runtime.questsGenerate', + group: 'runtime-quest', + method: 'POST', + path: '/runtime/quests/generate', + operation: 'runtime.quests.generate', + access: 'JWT', + responseMode: 'json', + summary: '按当前遭遇生成任务候选。', + domainModuleIds: ['quest', 'ai'], + sourceHint: '/runtime/quests/generate', + }), + defineRuntimeRoute({ + id: 'runtime.wsHealth', + group: 'runtime-diagnostics', + method: 'GET', + path: '/ws/health', + operation: 'runtime.ws.health', + access: 'JWT', + responseMode: 'json', + summary: '保留给未来实时链路的占位健康检查。', + domainModuleIds: ['runtime'], + sourceHint: '/ws/health', + }), +]; + +export const BACKEND_CAPABILITY_MANIFEST: BackendCapabilityManifest = { + version: '2026-04-20', + generatedCommand: 'npm run server-node:manifest:backend', + outputTargets: { + json: 'server-node/manifests/backend-capability-index.json', + markdown: 'docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md', + }, + maintenanceRules: [ + '新增 `server-node/src/modules/*` 目录时,必须先补充 manifest 里的模块说明,再重新生成产物。', + '新增或下线路由时,先更新 manifest 里的路由清单,再运行生成命令同步 JSON 与文档。', + '如果路由来自兼容路径或中间件派生路径,`sourceHint` 需要指向源代码里的真实表达式,确保生成脚本能做最小校验。', + ], + surfaces: BACKEND_ROUTE_SURFACES, + modules: BACKEND_DOMAIN_MODULES, + routes: BACKEND_ROUTE_CAPABILITIES, +}; diff --git a/server-node/src/modules/assets/characterAssetRoutes.ts b/server-node/src/modules/assets/characterAssetRoutes.ts index 996bd7c1..994120e8 100644 --- a/server-node/src/modules/assets/characterAssetRoutes.ts +++ b/server-node/src/modules/assets/characterAssetRoutes.ts @@ -19,6 +19,7 @@ import { PNG } from 'pngjs'; import { removeBackgroundFromRgba } from '../../../../packages/shared/src/assets/chromaKey.js'; import { parseApiErrorMessage } from '../../../../packages/shared/src/http.js'; import type { AppConfig } from '../../config.js'; +import { routeMeta } from '../../middleware/routeMeta.js'; import { buildArkCharacterAnimationPrompt, buildFallbackModerationSafeAnimationPrompt, @@ -30,14 +31,18 @@ import { import type { UpstreamLlmClient } from '../../services/llmClient.js'; const CHARACTER_WORKFLOW_CACHE_PATH = '/api/assets/character-workflow-cache'; +const CHARACTER_WORKFLOW_CACHE_DETAIL_PATH = + '/api/assets/character-workflow-cache/:characterId'; const CHARACTER_VISUAL_GENERATE_PATH = '/api/assets/character-visual/generate'; const CHARACTER_VISUAL_PUBLISH_PATH = '/api/assets/character-visual/publish'; -const CHARACTER_VISUAL_JOBS_PATH = '/api/assets/character-visual/jobs/'; +const CHARACTER_VISUAL_JOB_DETAIL_PATH = + '/api/assets/character-visual/jobs/:taskId'; const CHARACTER_ANIMATION_GENERATE_PATH = '/api/assets/character-animation/generate'; const CHARACTER_ANIMATION_PUBLISH_PATH = '/api/assets/character-animation/publish'; -const CHARACTER_ANIMATION_JOBS_PATH = '/api/assets/character-animation/jobs/'; +const CHARACTER_ANIMATION_JOB_DETAIL_PATH = + '/api/assets/character-animation/jobs/:taskId'; const CHARACTER_ANIMATION_IMPORT_VIDEO_PATH = '/api/assets/character-animation/import-video'; const CHARACTER_ANIMATION_TEMPLATES_PATH = @@ -2990,29 +2995,37 @@ export function createCharacterAssetRoutes( next(); }); - router.use( + router.post( CHARACTER_WORKFLOW_CACHE_PATH, + routeMeta({ operation: 'assets.character.workflowCache.save' }), toExpressHandler((request, response) => { - if (request.method === 'GET') { - return handleGetCharacterWorkflowCache(config, request, response); - } return handleSaveCharacterWorkflowCache(config, request, response); }), ); - router.use( + router.get( + CHARACTER_WORKFLOW_CACHE_DETAIL_PATH, + routeMeta({ operation: 'assets.character.workflowCache.get' }), + toExpressHandler((request, response) => + handleGetCharacterWorkflowCache(config, request, response), + ), + ); + router.post( CHARACTER_VISUAL_GENERATE_PATH, + routeMeta({ operation: 'assets.character.visual.generate' }), toExpressHandler((request, response) => handleGenerateCharacterVisuals(config, request, response), ), ); - router.use( + router.post( CHARACTER_VISUAL_PUBLISH_PATH, + routeMeta({ operation: 'assets.character.visual.publish' }), toExpressHandler((request, response) => handlePublishCharacterVisual(config, request, response), ), ); - router.use( - CHARACTER_VISUAL_JOBS_PATH, + router.get( + CHARACTER_VISUAL_JOB_DETAIL_PATH, + routeMeta({ operation: 'assets.character.visual.job.get' }), toExpressHandler((request, response) => handleReadCharacterJobStatus( config.projectRoot, @@ -3022,20 +3035,23 @@ export function createCharacterAssetRoutes( ), ), ); - router.use( + router.post( CHARACTER_ANIMATION_GENERATE_PATH, + routeMeta({ operation: 'assets.character.animation.generate' }), toExpressHandler((request, response) => handleGenerateCharacterAnimation(config, request, response), ), ); - router.use( + router.post( CHARACTER_ANIMATION_PUBLISH_PATH, + routeMeta({ operation: 'assets.character.animation.publish' }), toExpressHandler((request, response) => handlePublishCharacterAnimation(config, request, response), ), ); - router.use( - CHARACTER_ANIMATION_JOBS_PATH, + router.get( + CHARACTER_ANIMATION_JOB_DETAIL_PATH, + routeMeta({ operation: 'assets.character.animation.job.get' }), toExpressHandler((request, response) => handleReadCharacterJobStatus( config.projectRoot, @@ -3045,8 +3061,9 @@ export function createCharacterAssetRoutes( ), ), ); - router.use( + router.post( CHARACTER_ANIMATION_IMPORT_VIDEO_PATH, + routeMeta({ operation: 'assets.character.animation.importVideo' }), toExpressHandler((request, response) => handleImportCharacterAnimationVideo( config.projectRoot, @@ -3055,8 +3072,9 @@ export function createCharacterAssetRoutes( ), ), ); - router.use( + router.get( CHARACTER_ANIMATION_TEMPLATES_PATH, + routeMeta({ operation: 'assets.character.animation.templates.list' }), toExpressHandler((request, response) => handleListAnimationTemplates(config, request, response), ), diff --git a/server-node/src/modules/assets/qwenSpriteRoutes.ts b/server-node/src/modules/assets/qwenSpriteRoutes.ts index 4053e78d..01c960b0 100644 --- a/server-node/src/modules/assets/qwenSpriteRoutes.ts +++ b/server-node/src/modules/assets/qwenSpriteRoutes.ts @@ -9,6 +9,7 @@ import path from 'node:path'; import { Router, type NextFunction, type Request, type Response } from 'express'; import type { AppConfig } from '../../config.js'; +import { routeMeta } from '../../middleware/routeMeta.js'; const QWEN_SPRITE_MASTER_GENERATE_PATH = '/api/assets/qwen-sprite/master'; const QWEN_SPRITE_SHEET_GENERATE_PATH = '/api/assets/qwen-sprite/sheet'; @@ -878,26 +879,30 @@ export function createQwenSpriteRoutes(config: AppConfig) { next(); }); - router.use( + router.post( QWEN_SPRITE_MASTER_GENERATE_PATH, + routeMeta({ operation: 'assets.qwenSprite.master.generate' }), toExpressHandler((request, response) => handleGenerateMaster(config, request, response), ), ); - router.use( + router.post( QWEN_SPRITE_SHEET_GENERATE_PATH, + routeMeta({ operation: 'assets.qwenSprite.sheet.generate' }), toExpressHandler((request, response) => handleGenerateSheet(config, request, response), ), ); - router.use( + router.post( QWEN_SPRITE_FRAME_REPAIR_PATH, + routeMeta({ operation: 'assets.qwenSprite.frameRepair.generate' }), toExpressHandler((request, response) => handleRepairFrame(config, request, response), ), ); - router.use( + router.post( QWEN_SPRITE_SAVE_PATH, + routeMeta({ operation: 'assets.qwenSprite.asset.save' }), toExpressHandler((request, response) => handleSaveAsset(config.projectRoot, request, response), ), diff --git a/server-node/src/modules/editor/editorRoutes.ts b/server-node/src/modules/editor/editorRoutes.ts index c1a4ccab..7108258f 100644 --- a/server-node/src/modules/editor/editorRoutes.ts +++ b/server-node/src/modules/editor/editorRoutes.ts @@ -6,6 +6,7 @@ import { Router } from 'express'; import type { AppConfig } from '../../config.js'; import { badRequest, notFound } from '../../errors.js'; import { asyncHandler } from '../../http.js'; +import { routeMeta } from '../../middleware/routeMeta.js'; const EDITOR_JSON_RESOURCE_FILES = { 'item-overrides': 'src/data/itemOverrides.json', @@ -102,6 +103,7 @@ export function createEditorRoutes(config: AppConfig) { router.get( '/api/editor/catalog/items', + routeMeta({ operation: 'editor.catalog.items.list' }), asyncHandler(async (_request, response) => { response.json({ assetPaths: await collectPngAssetPaths( @@ -113,6 +115,7 @@ export function createEditorRoutes(config: AppConfig) { router.get( '/api/editor/json/:resourceId', + routeMeta({ operation: 'editor.resource.read' }), asyncHandler(async (request, response) => { const filePath = resolveEditorJsonFile(config, request.params.resourceId); response.json(await readEditorJsonFile(filePath)); @@ -121,6 +124,7 @@ export function createEditorRoutes(config: AppConfig) { router.post( '/api/editor/json/:resourceId', + routeMeta({ operation: 'editor.resource.write' }), asyncHandler(async (request, response) => { if (!isEditorJsonPayload(request.body)) { throw badRequest('编辑器保存请求必须是 JSON 对象。');