prts-terrarchive 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GAME_ASSETS.md +26 -0
- package/LICENSE +21 -0
- package/README.en.md +274 -0
- package/README.md +423 -0
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/bin/bake-rhine-details.mjs +34 -0
- package/bin/install.js +273 -0
- package/bin/pack-map-assets.mjs +110 -0
- package/bin/preview-rhine.mjs +71 -0
- package/bin/rhine-detail-baker.ts +84 -0
- package/contracts/cloud_tools_v1.schema.json +243 -0
- package/contracts/corpus_i18n_v1.schema.json +179 -0
- package/contracts/corpus_tools_v1.schema.json +1801 -0
- package/cordis.patch.yml +32 -0
- package/docs/endfield-official-i18n.md +103 -0
- package/lib/client.js +3912 -0
- package/lib/endfield-map/map.js.br +0 -0
- package/lib/endfield-map/map.js.gz +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.br +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.gz +0 -0
- package/lib/endfield-map/resources/map-01616f534aaaeca1.png +0 -0
- package/lib/endfield-map/resources/map-1562e0dff4be3d23.png +0 -0
- package/lib/endfield-map/resources/map-160fe040f080f6a3.png +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.br +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.gz +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.br +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.gz +0 -0
- package/lib/endfield-map/resources/map-1d30baaaef3e29cc.png +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.br +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.gz +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.br +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.gz +0 -0
- package/lib/endfield-map/resources/map-26caf8d71247abb7.png +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.br +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.gz +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.br +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.br +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.br +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.gz +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.br +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.gz +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.br +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.gz +0 -0
- package/lib/endfield-map/resources/map-3d486e887ae121ac.png +0 -0
- package/lib/endfield-map/resources/map-418dc4865608fce5.png +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.br +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.gz +0 -0
- package/lib/endfield-map/resources/map-4371259d3ab7aac7.png +0 -0
- package/lib/endfield-map/resources/map-48dcc0fd55e13f7f.png +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.br +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.gz +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.br +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.gz +0 -0
- package/lib/endfield-map/resources/map-5177e9d1d46f47e2.png +0 -0
- package/lib/endfield-map/resources/map-51baeb59b7f8c28d.png +0 -0
- package/lib/endfield-map/resources/map-549561b46e621874.png +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.br +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.gz +0 -0
- package/lib/endfield-map/resources/map-5a6963909423b674.png +0 -0
- package/lib/endfield-map/resources/map-5a9c533c0552d7b7.png +0 -0
- package/lib/endfield-map/resources/map-5d20ae9ffa9a02b0.png +0 -0
- package/lib/endfield-map/resources/map-626c8847393f60bf.png +0 -0
- package/lib/endfield-map/resources/map-63d43cbec3bed3b8.png +0 -0
- package/lib/endfield-map/resources/map-661b6ec56e722574.png +0 -0
- package/lib/endfield-map/resources/map-6ddd384eca190828.png +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.br +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.gz +0 -0
- package/lib/endfield-map/resources/map-738ebaa002c33cfc.png +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.br +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.gz +0 -0
- package/lib/endfield-map/resources/map-78929f26d6844509.png +0 -0
- package/lib/endfield-map/resources/map-7f09c142950a0f74.png +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.br +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.gz +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.br +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.gz +0 -0
- package/lib/endfield-map/resources/map-8d1ade3f01d952b0.png +0 -0
- package/lib/endfield-map/resources/map-8fef3495bb7ed60f.png +0 -0
- package/lib/endfield-map/resources/map-907a7ecf8a0ce352.png +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.br +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.gz +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.br +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-99722e61da5f3e53.png +0 -0
- package/lib/endfield-map/resources/map-9be9596cc059abe3.png +0 -0
- package/lib/endfield-map/resources/map-a4db9a4763e47fbf.png +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.br +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.gz +0 -0
- package/lib/endfield-map/resources/map-ab35d06875ee418f.png +0 -0
- package/lib/endfield-map/resources/map-ac51bb3e7623cd8b.png +0 -0
- package/lib/endfield-map/resources/map-ac830a66296bc83a.png +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.br +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8164f1af7c5d895.png +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.br +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.br +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.gz +0 -0
- package/lib/endfield-map/resources/map-bfc58277d5ba649d.png +0 -0
- package/lib/endfield-map/resources/map-c013f82048c6891a.png +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.br +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.gz +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.br +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.gz +0 -0
- package/lib/endfield-map/resources/map-c607f8b4c8ad8967.png +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.br +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.gz +0 -0
- package/lib/endfield-map/resources/map-d51adf5952af1e8e.png +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.br +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.gz +0 -0
- package/lib/endfield-map/resources/map-db11d394456a7465.png +0 -0
- package/lib/endfield-map/resources/map-dcc4d218581e3dc6.png +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.br +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-defe5db7dabf748a.png +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.br +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.gz +0 -0
- package/lib/endfield-map/resources/map-e25ee9af6e493500.png +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.br +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.gz +0 -0
- package/lib/endfield-map/resources/map-e694a91e630b1bd6.png +0 -0
- package/lib/endfield-map/resources/map-eaa93af79d12a87d.png +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.br +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.gz +0 -0
- package/lib/endfield-map/resources/map-ede421a329dc78a4.png +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.br +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.gz +0 -0
- package/lib/endfield-map/resources/map-fb5f4a59a181452c.png +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.br +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.gz +0 -0
- package/lib/rhine/assets/archive-assembly.glb +0 -0
- package/lib/rhine/assets/archive-cassette.glb +0 -0
- package/lib/rhine/fonts/MiSans-Bold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Demibold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Light.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Regular.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-license.pdf +0 -0
- package/lib/rhine/fonts/NOTICE.txt +6 -0
- package/lib/rhine/licenses/RhineLabUI-MIT.txt +21 -0
- package/lib/rhine/licenses/entities-BSD-2-Clause.txt +11 -0
- package/lib/rhine/licenses/linkify-it-MIT.txt +22 -0
- package/lib/rhine/licenses/markdown-it-MIT.txt +22 -0
- package/lib/rhine/licenses/mdurl-MIT.txt +45 -0
- package/lib/rhine/licenses/punycode.js-MIT.txt +20 -0
- package/lib/rhine/licenses/rolling-number-MIT.txt +21 -0
- package/lib/rhine/licenses/three-MIT.txt +21 -0
- package/lib/rhine/licenses/uc.micro-MIT.txt +20 -0
- package/lib/rhine/rhine.css +1 -0
- package/lib/rhine/rhine.js +5106 -0
- package/lib/skins/common.css +31 -0
- package/lib/skins/endfield-aic.css +141 -0
- package/lib/skins/prts-agent.css +136 -0
- package/lib/skins/rhine-lab.css +70 -0
- package/package.json +112 -0
- package/presets/definition.js +26 -0
- package/presets/prts/agent.cordis.yml +25 -0
- package/presets/prts/preset.yml +3 -0
- package/presets/register.js +86 -0
- package/resources/entity-routing.json +21 -0
- package/skills/prts-investigation/SKILL.md +121 -0
- package/skills/prts-retrieval/SKILL.md +35 -0
- package/skills/prts-retrieval/references/cloud-tools.md +13 -0
- package/skills/prts-retrieval/references/diagnostics.md +15 -0
- package/skills/prts-retrieval/references/local-tools.md +24 -0
- package/skills/prts-retrieval/references/module-arknights.md +23 -0
- package/skills/prts-retrieval/references/module-dual.md +15 -0
- package/skills/prts-retrieval/references/module-endfield.md +21 -0
- package/skills/prts-retrieval/references/retrieval-process.md +44 -0
- package/skills/prts-retrieval/references/retrieval-recipes-dual.md +34 -0
- package/skills/prts-retrieval/references/retrieval-recipes-endfield.md +37 -0
- package/skills/prts-retrieval/references/retrieval-recipes.md +39 -0
- package/skills/prts-retrieval/references/tools-runtime.md +48 -0
- package/skills/prts-retrieval/references/wiki-schema.md +38 -0
- package/src/archive-presentation.js +287 -0
- package/src/cloud-projection.js +124 -0
- package/src/cloud.js +498 -0
- package/src/entity-recognizer.js +311 -0
- package/src/entity-routing.js +82 -0
- package/src/evidence-state.js +332 -0
- package/src/i18n.js +365 -0
- package/src/index.js +1261 -0
- package/src/installer.js +1362 -0
- package/src/investigation-review.js +87 -0
- package/src/investigation-store.js +579 -0
- package/src/investigation-tools.js +113 -0
- package/src/localization-format.js +23 -0
- package/src/manual-search.js +103 -0
- package/src/read.js +1097 -0
- package/src/release-compatibility.js +73 -0
- package/src/release-watch.js +40 -0
- package/src/search-projection.js +139 -0
- package/src/search.js +1522 -0
- package/src/skill.js +98 -0
- package/src/source-map.js +348 -0
- package/src/state.js +314 -0
- package/src/store.js +1569 -0
- package/src/timeline.js +386 -0
- package/src/ui.js +1353 -0
- package/src/wiki.js +111 -0
- package/tsconfig.rhine.json +15 -0
- package/ui/rhine/archive-navigation-limit.ts +31 -0
- package/ui/rhine/archive-rack.ts +83 -0
- package/ui/rhine/assets/evidence-observatory.webp +0 -0
- package/ui/rhine/board-plane-transform.ts +24 -0
- package/ui/rhine/catalogue.ts +72 -0
- package/ui/rhine/deferred-preparation.ts +146 -0
- package/ui/rhine/evidence-board-controls.css +22 -0
- package/ui/rhine/evidence-board-history.ts +73 -0
- package/ui/rhine/evidence-board-model.ts +189 -0
- package/ui/rhine/evidence-board-panel.ts +762 -0
- package/ui/rhine/evidence-board-scene.ts +548 -0
- package/ui/rhine/evidence-board.css +191 -0
- package/ui/rhine/evidence-card-art.ts +470 -0
- package/ui/rhine/evidence-card-style.ts +17 -0
- package/ui/rhine/evidence-inbox-scene.ts +43 -0
- package/ui/rhine/evidence-inbox.css +29 -0
- package/ui/rhine/evidence-inbox.ts +104 -0
- package/ui/rhine/index.ts +12 -0
- package/ui/rhine/interaction.css +20 -0
- package/ui/rhine/investigation-board-plane.ts +5 -0
- package/ui/rhine/investigation-board.css +34 -0
- package/ui/rhine/investigation-board.ts +348 -0
- package/ui/rhine/investigation-edits.ts +46 -0
- package/ui/rhine/investigation-layout.ts +29 -0
- package/ui/rhine/label-prefetch.ts +34 -0
- package/ui/rhine/looping-title.ts +40 -0
- package/ui/rhine/manual-search.css +41 -0
- package/ui/rhine/manual-search.ts +89 -0
- package/ui/rhine/original/appearance.ts +180 -0
- package/ui/rhine/original/archive-label.ts +84 -0
- package/ui/rhine/original/archive-lighting.ts +39 -0
- package/ui/rhine/original/archive-loop.ts +82 -0
- package/ui/rhine/original/archive-refill.ts +64 -0
- package/ui/rhine/original/array-detail-data.ts +2 -0
- package/ui/rhine/original/array-detail.ts +41 -0
- package/ui/rhine/original/array-interior.ts +411 -0
- package/ui/rhine/original/array-occlusion.ts +166 -0
- package/ui/rhine/original/array-shell.ts +38 -0
- package/ui/rhine/original/array-visibility.ts +309 -0
- package/ui/rhine/original/asset-url.ts +4 -0
- package/ui/rhine/original/brand.ts +12 -0
- package/ui/rhine/original/cassette-lod.ts +81 -0
- package/ui/rhine/original/data.ts +21 -0
- package/ui/rhine/original/decryption.ts +185 -0
- package/ui/rhine/original/detail-signature.ts +18 -0
- package/ui/rhine/original/glass-reveal.ts +45 -0
- package/ui/rhine/original/internal-optics.ts +70 -0
- package/ui/rhine/original/label-renderer.ts +119 -0
- package/ui/rhine/original/label-textures.ts +117 -0
- package/ui/rhine/original/model-viewer.ts +658 -0
- package/ui/rhine/original/motion.ts +116 -0
- package/ui/rhine/original/opaque-backfaces.ts +64 -0
- package/ui/rhine/original/program-preparation.ts +111 -0
- package/ui/rhine/original/quality-renderer.ts +97 -0
- package/ui/rhine/original/quality-settings.ts +111 -0
- package/ui/rhine/original/render-quality.ts +163 -0
- package/ui/rhine/original/scene.ts +2051 -0
- package/ui/rhine/original/shelf-interior.ts +142 -0
- package/ui/rhine/original/ui-transitions.ts +111 -0
- package/ui/rhine/original/viewer-camera.ts +92 -0
- package/ui/rhine/original/viewport-layout.ts +41 -0
- package/ui/rhine/original-ui.css +2334 -0
- package/ui/rhine/readability.css +46 -0
- package/ui/rhine/reading-object.ts +96 -0
- package/ui/rhine/reading-origin.ts +28 -0
- package/ui/rhine/reading-room.css +55 -0
- package/ui/rhine/report-markdown.ts +98 -0
- package/ui/rhine/scene-edge-navigation.ts +84 -0
- package/ui/rhine/scene.ts +1726 -0
- package/ui/rhine/shelf-layout.ts +38 -0
- package/ui/rhine/snapshot-update-gate.ts +22 -0
- package/ui/rhine/source-reading.ts +15 -0
- package/ui/rhine/source-selection.css +164 -0
- package/ui/rhine/temporary-performance-observers.ts +163 -0
- package/ui/rhine/temporary-performance-panel.ts +124 -0
- package/ui/rhine/temporary-performance.css +19 -0
- package/ui/rhine/temporary-performance.ts +552 -0
- package/ui/rhine/temporary-render-diagnostics.ts +105 -0
- package/ui/rhine/tool-activity.ts +69 -0
- package/ui/rhine/types.ts +219 -0
- package/ui/rhine/visible-scene-hit.ts +13 -0
- package/ui/rhine/workbench.css +818 -0
- package/ui/rhine/workbench.ts +2245 -0
- package/ui/rhine/workspace-travel.ts +32 -0
- package/vite.rhine.config.ts +19 -0
package/src/state.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件共享状态与运行时配置。
|
|
3
|
+
*
|
|
4
|
+
* 配置分三层(与 DSH settings 的解析顺序同构,但不依赖 schemastery,
|
|
5
|
+
* 保持零 npm 依赖):
|
|
6
|
+
* 默认值 ← cordis.patch.yml 行内 config(base 层)← 用户文件($DSH_HOME/prts-corpus.json)
|
|
7
|
+
* 界面(settings tab)改配置 = 写用户层文件并立即生效:
|
|
8
|
+
* 显式下载的字节回退源走 getter 每次调用现读;可信元数据固定来自
|
|
9
|
+
* https://prts.chat;cloud 工具由
|
|
10
|
+
* index.js 的 rebuildCloud() 在配置变化时 dispose + 重注册。
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, rename, writeFile, mkdir, unlink } from 'node:fs/promises'
|
|
13
|
+
import { randomBytes } from 'node:crypto'
|
|
14
|
+
import { dirname } from 'node:path'
|
|
15
|
+
import { DEFAULT_SITE_BASE_URL } from './installer.js'
|
|
16
|
+
import { watchFileChanges } from './release-watch.js'
|
|
17
|
+
|
|
18
|
+
/** 运行时可改配置的默认值。 */
|
|
19
|
+
export const CONFIG_DEFAULTS = Object.freeze({
|
|
20
|
+
uiSkin: 'harness',
|
|
21
|
+
cloudEnabled: false,
|
|
22
|
+
cloudBaseUrl: DEFAULT_SITE_BASE_URL,
|
|
23
|
+
cloudToken: '',
|
|
24
|
+
cloudGame: 'all',
|
|
25
|
+
enabledGames: Object.freeze(['arknights', 'endfield']),
|
|
26
|
+
cloudUserId: '',
|
|
27
|
+
downloadSiteBaseUrl: DEFAULT_SITE_BASE_URL,
|
|
28
|
+
downloadOrder: Object.freeze(['modelscope', 'site']),
|
|
29
|
+
cloudTimeoutMs: 90_000,
|
|
30
|
+
cloudMaxResponseBytes: 32 * 1024 * 1024,
|
|
31
|
+
cacheShards: 24,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 云端/字节回退站点地址校验:必须是 https,或仅指向环回主机的 http(本地开发用)。
|
|
36
|
+
* 明文 http 只允许 localhost / 127.0.0.1 / [::1],避免把 token 或资料
|
|
37
|
+
* 下载暴露给局域网中间人。
|
|
38
|
+
*/
|
|
39
|
+
const isServiceBaseUrl = (value) => {
|
|
40
|
+
if (typeof value !== 'string') return false
|
|
41
|
+
const raw = value.trim()
|
|
42
|
+
if (!raw || raw.length >= 512) return false
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(raw)
|
|
45
|
+
if (url.username || url.password || url.search || url.hash) return false
|
|
46
|
+
if (url.protocol === 'https:') return true
|
|
47
|
+
return url.protocol === 'http:'
|
|
48
|
+
&& (url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]')
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const serviceOrigin = (value) => new URL(value).origin
|
|
55
|
+
|
|
56
|
+
/** 界面可写的键(白名单 + 类型校验器)。 */
|
|
57
|
+
const WRITABLE = {
|
|
58
|
+
uiSkin: (v) => v === 'harness' || v === 'prts-agent' || v === 'endfield-aic' || v === 'rhine-lab',
|
|
59
|
+
cloudEnabled: (v) => typeof v === 'boolean',
|
|
60
|
+
cloudBaseUrl: isServiceBaseUrl,
|
|
61
|
+
cloudToken: (v) => typeof v === 'string' && v.length < 4096,
|
|
62
|
+
cloudGame: (v) => v === 'all' || v === 'arknights' || v === 'endfield',
|
|
63
|
+
enabledGames: (v) => Array.isArray(v) && v.length > 0 && v.length <= 2
|
|
64
|
+
&& new Set(v).size === v.length
|
|
65
|
+
&& v.every((entry) => entry === 'arknights' || entry === 'endfield'),
|
|
66
|
+
cloudUserId: (v) => typeof v === 'string' && v.length < 128,
|
|
67
|
+
downloadSiteBaseUrl: isServiceBaseUrl,
|
|
68
|
+
downloadOrder: (v) => Array.isArray(v) && v.length > 0 && v.length <= 2
|
|
69
|
+
&& new Set(v).size === v.length && v.every((entry) => entry === 'modelscope' || entry === 'site'),
|
|
70
|
+
cloudTimeoutMs: (v) => Number.isInteger(v) && v >= 1000 && v <= 10 * 60_000,
|
|
71
|
+
cloudMaxResponseBytes: (v) => Number.isInteger(v) && v >= 1024 && v <= 64 * 1024 * 1024,
|
|
72
|
+
cacheShards: (v) => Number.isInteger(v) && v >= 1 && v <= 128,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 从 cordis.patch.yml 行内 config 提取 base 层。 */
|
|
76
|
+
function baseLayer(patchConfig = {}) {
|
|
77
|
+
const base = {}
|
|
78
|
+
if (patchConfig.uiSkin) base.uiSkin = String(patchConfig.uiSkin)
|
|
79
|
+
const cloud = patchConfig.cloud
|
|
80
|
+
if (cloud?.baseUrl) {
|
|
81
|
+
base.cloudEnabled = true
|
|
82
|
+
base.cloudBaseUrl = String(cloud.baseUrl)
|
|
83
|
+
if (cloud.token) base.cloudToken = String(cloud.token)
|
|
84
|
+
if (cloud.game) base.cloudGame = cloud.game
|
|
85
|
+
if (cloud.userId) base.cloudUserId = String(cloud.userId)
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(patchConfig.enabledGames)) base.enabledGames = [...patchConfig.enabledGames]
|
|
88
|
+
const download = patchConfig.download
|
|
89
|
+
if (download?.siteBaseUrl) base.downloadSiteBaseUrl = String(download.siteBaseUrl)
|
|
90
|
+
if (Array.isArray(download?.order)) base.downloadOrder = [...download.order]
|
|
91
|
+
if (Number.isInteger(cloud?.timeoutMs)) base.cloudTimeoutMs = cloud.timeoutMs
|
|
92
|
+
if (Number.isInteger(cloud?.maxResponseBytes)) base.cloudMaxResponseBytes = cloud.maxResponseBytes
|
|
93
|
+
if (Number.isInteger(patchConfig.cacheShards)) base.cacheShards = patchConfig.cacheShards
|
|
94
|
+
return validateUserLayer(base)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const cancelledConfigWrite = () => Object.assign(new Error('PRTS 配置写入已取消'), {
|
|
98
|
+
name: 'AbortError', code: 'CANCELLED',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const assertConfigWriteActive = (signal) => {
|
|
102
|
+
if (signal?.aborted) throw cancelledConfigWrite()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const atomicWriteJson = async (path, value, { signal } = {}) => {
|
|
106
|
+
assertConfigWriteActive(signal)
|
|
107
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 })
|
|
108
|
+
assertConfigWriteActive(signal)
|
|
109
|
+
const temp = `${path}.${randomBytes(6).toString('hex')}.tmp`
|
|
110
|
+
try {
|
|
111
|
+
await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600, signal })
|
|
112
|
+
// rename 是不可取消的原子提交点;进入它之前最后检查一次。进入后即使
|
|
113
|
+
// transport 同时断开,也必须让内存态跟已经替换的磁盘文件保持一致。
|
|
114
|
+
assertConfigWriteActive(signal)
|
|
115
|
+
await rename(temp, path)
|
|
116
|
+
} catch (error) {
|
|
117
|
+
await unlink(temp).catch(() => {})
|
|
118
|
+
if (signal?.aborted || error?.name === 'AbortError' || error?.code === 'ABORT_ERR') {
|
|
119
|
+
throw cancelledConfigWrite()
|
|
120
|
+
}
|
|
121
|
+
throw error
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateUserLayer(value, { persisted = false } = {}) {
|
|
126
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
127
|
+
throw Object.assign(new Error('PRTS 配置文件必须是对象'), { code: 'INVALID_CONFIG' })
|
|
128
|
+
}
|
|
129
|
+
const result = {}
|
|
130
|
+
const deprecated = new Set([
|
|
131
|
+
'approvalMode', 'softIntentCharacters', 'hardIntentCharacters', 'hardIntentRecords', 'autoDownload',
|
|
132
|
+
// 远程下载已改为必须由 PRTS.chat current 选版;旧 pin 键只做无害迁移。
|
|
133
|
+
'downloadReleaseId',
|
|
134
|
+
])
|
|
135
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
136
|
+
// 旧版用户配置可以无痛升级:预算/审批已经完全交给 DSH,这些键不再生效。
|
|
137
|
+
if (deprecated.has(key)) continue
|
|
138
|
+
// 令牌所属源只由 saveConfig 在显式提交令牌时记录,不能由 UI 补丁改绑。
|
|
139
|
+
if (persisted && key === 'cloudTokenOrigin') {
|
|
140
|
+
if (!isServiceBaseUrl(entry) || serviceOrigin(entry) !== entry) {
|
|
141
|
+
throw Object.assign(new Error('配置项 cloudTokenOrigin 的值不合法'), { code: 'INVALID_CONFIG' })
|
|
142
|
+
}
|
|
143
|
+
result.cloudTokenOrigin = entry
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
const check = Object.hasOwn(WRITABLE, key) ? WRITABLE[key] : null
|
|
147
|
+
if (!check) throw Object.assign(new Error(`不可识别或只读的配置项:${key}`), { code: 'INVALID_CONFIG' })
|
|
148
|
+
if (!check(entry)) throw Object.assign(new Error(`配置项 ${key} 的值不合法`), { code: 'INVALID_CONFIG' })
|
|
149
|
+
if (key === 'cloudBaseUrl' || key === 'downloadSiteBaseUrl') result[key] = entry.trim()
|
|
150
|
+
else if (key === 'downloadOrder') result[key] = [...entry]
|
|
151
|
+
else result[key] = entry
|
|
152
|
+
}
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 创建共享状态单例(每插件实例一个,ui.js 与 index.js 共用)。
|
|
158
|
+
* @param {{ patchConfig?: object, configPath: string, releasesDir: string }} options
|
|
159
|
+
*/
|
|
160
|
+
export function createSharedState({ patchConfig, configPath, releasesDir }) {
|
|
161
|
+
const base = baseLayer(patchConfig)
|
|
162
|
+
let user = {}
|
|
163
|
+
let writeChain = Promise.resolve()
|
|
164
|
+
const listeners = new Set()
|
|
165
|
+
const runtimeListeners = new Set()
|
|
166
|
+
|
|
167
|
+
const commitUser = (next) => {
|
|
168
|
+
const previous = JSON.stringify(user)
|
|
169
|
+
user = next
|
|
170
|
+
if (JSON.stringify(next) === previous) return
|
|
171
|
+
for (const listener of listeners) listener(state.effective())
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const state = {
|
|
175
|
+
configPath,
|
|
176
|
+
releasesDir,
|
|
177
|
+
/** @type {import('./store.js').CorpusStore | null} */
|
|
178
|
+
store: null,
|
|
179
|
+
/** 下载任务状态(供设置页轮询) */
|
|
180
|
+
download: {
|
|
181
|
+
active: false, phase: 'idle', source: null, releaseId: null,
|
|
182
|
+
filesDone: 0, filesTotal: null, bytesDone: 0, error: null, finishedAt: null,
|
|
183
|
+
},
|
|
184
|
+
/** 载入用户层配置文件;缺失视为空,存在但无效则失败。 */
|
|
185
|
+
async loadConfig() {
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(await readFile(configPath, 'utf8'))
|
|
188
|
+
commitUser(validateUserLayer(parsed, { persisted: true }))
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error?.code !== 'ENOENT') throw error
|
|
191
|
+
commitUser({})
|
|
192
|
+
}
|
|
193
|
+
return state.effective()
|
|
194
|
+
},
|
|
195
|
+
/** 三层合并后的生效配置 */
|
|
196
|
+
effective() {
|
|
197
|
+
const { cloudTokenOrigin: userTokenOrigin, ...publicUser } = user
|
|
198
|
+
const effective = { ...CONFIG_DEFAULTS, ...base, ...publicUser }
|
|
199
|
+
// 用户令牌必须有保存时记录的源;不能在启动时按可能已改动的 base
|
|
200
|
+
// 猜测旧令牌所属源。旧版无 origin 的值仍保留,重新保存令牌后恢复。
|
|
201
|
+
// base 层令牌与同一份声明中的 baseUrl 绑定,避免用户 URL 覆盖带走它。
|
|
202
|
+
const tokenOrigin = Object.hasOwn(user, 'cloudToken')
|
|
203
|
+
? userTokenOrigin : base.cloudToken ? serviceOrigin(base.cloudBaseUrl) : null
|
|
204
|
+
if (effective.cloudToken && tokenOrigin !== serviceOrigin(effective.cloudBaseUrl)) {
|
|
205
|
+
effective.cloudToken = ''
|
|
206
|
+
}
|
|
207
|
+
// 旧配置只有 cloudGame。首次打开新版设置页之前仍沿用旧选择,避免
|
|
208
|
+
// 升级后悄悄把单游戏范围放宽为双游戏。
|
|
209
|
+
const explicitlyEnabled = user.enabledGames ?? base.enabledGames
|
|
210
|
+
const enabledGames = explicitlyEnabled
|
|
211
|
+
? [...explicitlyEnabled]
|
|
212
|
+
: effective.cloudGame === 'all'
|
|
213
|
+
? ['arknights', 'endfield'] : [effective.cloudGame]
|
|
214
|
+
return { ...effective, enabledGames, downloadOrder: [...effective.downloadOrder] }
|
|
215
|
+
},
|
|
216
|
+
/** 当前用户层(配置界面的编辑起点) */
|
|
217
|
+
userLayer() {
|
|
218
|
+
const { cloudTokenOrigin: _tokenOrigin, ...publicUser } = user
|
|
219
|
+
return { ...publicUser,
|
|
220
|
+
...(user.enabledGames ? { enabledGames: [...user.enabledGames] } : {}),
|
|
221
|
+
...(user.downloadOrder ? { downloadOrder: [...user.downloadOrder] } : {}) }
|
|
222
|
+
},
|
|
223
|
+
/** 订阅已验证配置的变化。 */
|
|
224
|
+
subscribe(listener) {
|
|
225
|
+
listeners.add(listener)
|
|
226
|
+
return () => { listeners.delete(listener) }
|
|
227
|
+
},
|
|
228
|
+
/** 订阅下载、激活和索引准备等非配置状态变化。 */
|
|
229
|
+
subscribeRuntime(listener) {
|
|
230
|
+
runtimeListeners.add(listener)
|
|
231
|
+
return () => { runtimeListeners.delete(listener) }
|
|
232
|
+
},
|
|
233
|
+
/** 通知只读取运行时状态的宿主能力重新求值。 */
|
|
234
|
+
notifyRuntime() {
|
|
235
|
+
for (const listener of runtimeListeners) listener()
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* 监听其他插件实例或人工编辑写入的配置文件。运行期无效内容保留最后一个
|
|
239
|
+
* 可用快照并记录警告,不让一次手误卸载已经工作的工具。
|
|
240
|
+
*/
|
|
241
|
+
async watchConfig(logger) {
|
|
242
|
+
await mkdir(dirname(configPath), { recursive: true, mode: 0o700 })
|
|
243
|
+
let timer = null
|
|
244
|
+
let closed = false
|
|
245
|
+
const reload = () => {
|
|
246
|
+
if (closed) return
|
|
247
|
+
if (timer) clearTimeout(timer)
|
|
248
|
+
timer = setTimeout(() => {
|
|
249
|
+
timer = null
|
|
250
|
+
state.loadConfig().catch((error) => {
|
|
251
|
+
logger?.warn?.(`prts-corpus: 忽略无效配置更新:${error?.message ?? error}`)
|
|
252
|
+
})
|
|
253
|
+
}, 100)
|
|
254
|
+
}
|
|
255
|
+
const watcher = watchFileChanges(configPath, reload, {
|
|
256
|
+
onFallback(error) {
|
|
257
|
+
logger?.warn?.(`prts-corpus: 原生配置监听资源不足(${error.code}),改为每秒检查配置`)
|
|
258
|
+
},
|
|
259
|
+
onError(error) {
|
|
260
|
+
logger?.warn?.(`prts-corpus: 配置监听停止:${error?.message ?? error}`)
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
return () => {
|
|
264
|
+
closed = true
|
|
265
|
+
if (timer) clearTimeout(timer)
|
|
266
|
+
watcher.close()
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
/**
|
|
270
|
+
* 写入用户层补丁并持久化(原子写)。非法键/类型拒绝,不落盘。
|
|
271
|
+
* @returns {Promise<object>} 新的生效配置
|
|
272
|
+
*/
|
|
273
|
+
async saveConfig(patch, { signal } = {}) {
|
|
274
|
+
assertConfigWriteActive(signal)
|
|
275
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
|
|
276
|
+
throw Object.assign(new Error('配置补丁必须是对象'), { code: 'INVALID_CONFIG' })
|
|
277
|
+
}
|
|
278
|
+
validateUserLayer(patch)
|
|
279
|
+
const operation = writeChain.then(async () => {
|
|
280
|
+
// 被取消的排队写不能在较新的选择之后苏醒并覆盖配置。
|
|
281
|
+
assertConfigWriteActive(signal)
|
|
282
|
+
const previousOrigin = serviceOrigin(state.effective().cloudBaseUrl)
|
|
283
|
+
const next = validateUserLayer({ ...user, ...patch }, { persisted: true })
|
|
284
|
+
const nextOrigin = serviceOrigin({ ...CONFIG_DEFAULTS, ...base, ...next }.cloudBaseUrl)
|
|
285
|
+
// 静态令牌只属于签发它的源。设置页把云端服务切到另一个 origin 时,
|
|
286
|
+
// 未同时提交的新令牌不得被带到新服务;空字符串也能遮住 patch 层令牌。
|
|
287
|
+
if (nextOrigin !== previousOrigin && !Object.hasOwn(patch, 'cloudToken')) {
|
|
288
|
+
next.cloudToken = ''
|
|
289
|
+
delete next.cloudTokenOrigin
|
|
290
|
+
}
|
|
291
|
+
if (Object.hasOwn(patch, 'cloudToken')) {
|
|
292
|
+
if (next.cloudToken) next.cloudTokenOrigin = nextOrigin
|
|
293
|
+
else delete next.cloudTokenOrigin
|
|
294
|
+
}
|
|
295
|
+
await atomicWriteJson(configPath, next, { signal })
|
|
296
|
+
commitUser(next)
|
|
297
|
+
return state.effective()
|
|
298
|
+
})
|
|
299
|
+
writeChain = operation.catch(() => {})
|
|
300
|
+
return operation
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
return state
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** 配置快照(token 只暴露"是否已设置",不回传明文)。 */
|
|
307
|
+
export function redactConfig(effective, userLayer = {}) {
|
|
308
|
+
return {
|
|
309
|
+
...effective,
|
|
310
|
+
cloudToken: undefined,
|
|
311
|
+
hasCloudToken: Boolean(effective.cloudToken),
|
|
312
|
+
cloudTokenUserSet: Boolean(userLayer.cloudToken),
|
|
313
|
+
}
|
|
314
|
+
}
|