cortico 0.1.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.
Files changed (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,236 @@
1
+ /**
2
+ * 严格解析失败时按固定规则规范化格式,再次运行解析及注册表校验。
3
+ * 实际矩阵决定尺寸,缺格用 structure_void 补齐;不裁剪已有内容,不改方块名、属性值或越界索引。
4
+ */
5
+
6
+ import {
7
+ paletteEntryToState,
8
+ type NormalizedBlueprint,
9
+ type SizeXYZ,
10
+ } from './blueprint.ts';
11
+ import { BLUEPRINT_MC_VERSION, completeBlockStateDefaults } from './blueprint-registry.ts';
12
+
13
+ const REPAIR_POLICY_VERSION = 'blueprint-repair-v1' as const;
14
+
15
+ interface RepairAction {
16
+ code:
17
+ | 'visible-json-recovery'
18
+ | 'axis-order-normalization'
19
+ | 'declared-size-normalization'
20
+ | 'dense-grid-fit'
21
+ | 'default-state-completion';
22
+ message: string;
23
+ count: number;
24
+ samples: string[];
25
+ }
26
+
27
+ export interface RepairReport {
28
+ policyVersion: typeof REPAIR_POLICY_VERSION;
29
+ strictValid: boolean;
30
+ strictError: string | null;
31
+ applied: boolean;
32
+ actions: RepairAction[];
33
+ }
34
+
35
+ export function createRepairReport(): RepairReport {
36
+ return {
37
+ policyVersion: REPAIR_POLICY_VERSION,
38
+ strictValid: false,
39
+ strictError: null,
40
+ applied: false,
41
+ actions: [],
42
+ };
43
+ }
44
+
45
+ function isRecord(value: unknown): value is Record<string, unknown> {
46
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
47
+ }
48
+
49
+ function parseJsonObject(text: string): Record<string, unknown> | null {
50
+ try {
51
+ const value = JSON.parse(text.trim()) as unknown;
52
+ return isRecord(value) ? value : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function unwrapSubmission(
59
+ value: Record<string, unknown>,
60
+ expectedToolName: string,
61
+ ): Record<string, unknown> | null {
62
+ // 裸蓝图提交以 size_xyz 识别;工具调用包装以 arguments/parameters 识别。
63
+ // name 也是裸蓝图的合法字段,不能用作包装判据。
64
+ if (Array.isArray(value.size_xyz)) return value;
65
+ if (value.arguments !== undefined || value.parameters !== undefined) {
66
+ if (value.name !== expectedToolName) return null;
67
+ const wrapped = value.arguments ?? value.parameters;
68
+ const args = typeof wrapped === 'string' ? parseJsonObject(wrapped) : wrapped;
69
+ return isRecord(args) ? args : null;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ /** 仅提取正文中唯一一份完整蓝图提交。 */
75
+ export function recoverVisibleSubmission(
76
+ visibleContent: string,
77
+ expectedToolName: string,
78
+ ): Record<string, unknown> | null {
79
+ const candidates: string[] = [];
80
+ for (const match of visibleContent.matchAll(/```[^\r\n]*\r?\n?([\s\S]*?)```/g)) {
81
+ candidates.push(match[1]);
82
+ }
83
+ if (candidates.length === 0) candidates.push(visibleContent);
84
+
85
+ const recovered = new Map<string, Record<string, unknown>>();
86
+ for (const candidate of candidates) {
87
+ const parsed = parseJsonObject(candidate);
88
+ if (!parsed) continue;
89
+ const submission = unwrapSubmission(parsed, expectedToolName);
90
+ if (submission) recovered.set(JSON.stringify(submission), submission);
91
+ }
92
+ return recovered.size === 1 ? [...recovered.values()][0] : null;
93
+ }
94
+
95
+ /** 查找可规范化为 minecraft:structure_void 的 Palette 索引。 */
96
+ function preserveIndex(submission: Record<string, unknown>): number | undefined {
97
+ if (!Array.isArray(submission.palette)) return undefined;
98
+ for (let index = 0; index < submission.palette.length; index++) {
99
+ try {
100
+ if (paletteEntryToState(submission.palette[index]) === 'minecraft:structure_void') return index;
101
+ } catch {
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+
107
+ /** 补齐轴序并统一大小写;按矩阵计算尺寸,向东、向南用 structure_void 补齐参差行。 */
108
+ export function repairSubmissionShape(
109
+ value: Record<string, unknown>,
110
+ ): { submission: Record<string, unknown>; actions: RepairAction[] } {
111
+ const submission = structuredClone(value);
112
+ const actions: RepairAction[] = [];
113
+
114
+ if (submission.axis_order === undefined
115
+ || (typeof submission.axis_order === 'string'
116
+ && submission.axis_order.toUpperCase() === 'YZX')) {
117
+ if (submission.axis_order !== 'YZX') {
118
+ submission.axis_order = 'YZX';
119
+ actions.push({
120
+ code: 'axis-order-normalization',
121
+ message: '轴序缺失或大小写不一致,按固定顺序解释。',
122
+ count: 1,
123
+ samples: ['按高度、南北、东西读取三维矩阵'],
124
+ });
125
+ }
126
+ }
127
+
128
+ if (!Array.isArray(submission.layers)) return { submission, actions };
129
+ let filler = preserveIndex(submission);
130
+ const layers = submission.layers as unknown[];
131
+ if (layers.length === 0 || layers.some((layer) => !Array.isArray(layer))) {
132
+ return { submission, actions };
133
+ }
134
+ const sizeY = layers.length;
135
+ const sizeZ = Math.max(...layers.map((layer) => (layer as unknown[]).length));
136
+ const rows = layers.flatMap((layer) => layer as unknown[]);
137
+ if (sizeZ === 0 || rows.some((row) => !Array.isArray(row))) return { submission, actions };
138
+ const sizeX = Math.max(...rows.map((row) => (row as unknown[]).length));
139
+ if (sizeX === 0) return { submission, actions };
140
+
141
+ const inferred: SizeXYZ = [sizeX, sizeY, sizeZ];
142
+ const declared = Array.isArray(submission.size_xyz) ? submission.size_xyz.join('×') : '未声明';
143
+ if (declared !== inferred.join('×')) {
144
+ submission.size_xyz = inferred;
145
+ actions.push({
146
+ code: 'declared-size-normalization',
147
+ message: '声明尺寸按实际画出的矩阵登记。',
148
+ count: 1,
149
+ samples: [`${declared} → ${inferred.join('×')}`],
150
+ });
151
+ }
152
+
153
+ const ragged = layers.some((layer) => (layer as unknown[]).length !== sizeZ)
154
+ || rows.some((row) => (row as unknown[]).length !== sizeX);
155
+ if (ragged && filler === undefined && Array.isArray(submission.palette)) {
156
+ filler = submission.palette.length;
157
+ submission.palette.push('minecraft:structure_void');
158
+ }
159
+
160
+ let padded = 0;
161
+ const samples: string[] = [];
162
+ for (let y = 0; y < layers.length; y++) {
163
+ const layer = layers[y] as unknown[];
164
+ const zDelta = layer.length - sizeZ;
165
+ if (zDelta < 0 && filler !== undefined) {
166
+ for (let count = 0; count < -zDelta; count++) {
167
+ layer.push(Array.from({ length: sizeX }, () => filler));
168
+ padded += sizeX;
169
+ }
170
+ samples.push(`第 ${y + 1} 层南侧补保留格 ${-zDelta} 行`);
171
+ }
172
+ for (let z = 0; z < layer.length; z++) {
173
+ const row = layer[z] as unknown[];
174
+ const xDelta = row.length - sizeX;
175
+ if (xDelta < 0 && filler !== undefined) {
176
+ row.push(...Array.from({ length: -xDelta }, () => filler));
177
+ padded += -xDelta;
178
+ samples.push(`第 ${y + 1} 层第 ${z + 1} 行东侧补保留格 ${-xDelta} 格`);
179
+ }
180
+ }
181
+ }
182
+
183
+ if (padded > 0) {
184
+ actions.push({
185
+ code: 'dense-grid-fit',
186
+ message: '参差矩阵扩成实际内容所需的最小长方体,缺格解释为不属于工地。',
187
+ count: padded,
188
+ samples: [...samples.slice(0, 4), `共补 ${padded} 个保留格;没有裁掉任何内容`],
189
+ });
190
+ }
191
+ return { submission, actions };
192
+ }
193
+
194
+ /** 整份蓝图逐格补默认属性;一条都没补时 action 是 null */
195
+ export function completeBlueprintStateDefaults(
196
+ blueprint: NormalizedBlueprint,
197
+ ): { blueprint: NormalizedBlueprint; action: RepairAction | null } {
198
+ const cache = new Map<string, string>();
199
+ const samples = new Set<string>();
200
+ let cells = 0;
201
+
202
+ const complete = (state: string): string => {
203
+ const cached = cache.get(state);
204
+ if (cached !== undefined) {
205
+ if (cached !== state) cells++;
206
+ return cached;
207
+ }
208
+ let completion: ReturnType<typeof completeBlockStateDefaults>;
209
+ try {
210
+ completion = completeBlockStateDefaults(state);
211
+ } catch {
212
+ cache.set(state, state);
213
+ return state;
214
+ }
215
+ const added = Object.entries(completion.added);
216
+ if (added.length > 0) {
217
+ cells++;
218
+ if (samples.size < 8) {
219
+ samples.add(`${state.split('[', 1)[0]}:${added.map(([k, v]) => `${k}=${v}`).join(',')}`);
220
+ }
221
+ }
222
+ cache.set(state, completion.state);
223
+ return completion.state;
224
+ };
225
+
226
+ const layers = blueprint.layers.map((layer) => layer.map((row) => row.map(complete)));
227
+ return {
228
+ blueprint: { size_xyz: [...blueprint.size_xyz], site_mode: blueprint.site_mode, layers },
229
+ action: cells === 0 ? null : {
230
+ code: 'default-state-completion',
231
+ message: `漏写的方块属性按 Minecraft ${BLUEPRINT_MC_VERSION} 官方默认状态补齐。`,
232
+ count: cells,
233
+ samples: [...samples],
234
+ },
235
+ };
236
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * 蓝图版本身份与剩余材料保留账。
3
+ *
4
+ * 版本身份绑定规范化内容;材料账只接受已经装载的可执行版本与其剩余 IR,
5
+ * 不把流式草稿算成工程需求。
6
+ */
7
+ import { createHash, randomUUID } from 'node:crypto';
8
+ import type { NormalizedBlueprint } from './blueprint.ts';
9
+ import { billForSteps, type BlueprintStep } from './blueprint-plan.ts';
10
+
11
+ export interface BlueprintVersionIdentity {
12
+ versionId: string;
13
+ contentHash: string;
14
+ }
15
+
16
+ export function createBlueprintVersionId(): string {
17
+ return `bpv_${randomUUID()}`;
18
+ }
19
+
20
+ export function createBlueprintJobId(): string {
21
+ return `bpj_${randomUUID()}`;
22
+ }
23
+
24
+ /** 规范化矩阵的稳定摘要;标签、保存时间与施工进度不属于设计内容。 */
25
+ export function blueprintContentHash(blueprint: NormalizedBlueprint): string {
26
+ const canonical = JSON.stringify({
27
+ site_mode: blueprint.site_mode,
28
+ size_xyz: blueprint.size_xyz,
29
+ axis_order: 'YZX',
30
+ layers: blueprint.layers,
31
+ });
32
+ return createHash('sha256').update(canonical).digest('hex');
33
+ }
34
+
35
+ /** 新保存版本使用不可复用的 id;内容相同仍是两次独立保存。 */
36
+ export function createBlueprintVersion(blueprint: NormalizedBlueprint): BlueprintVersionIdentity {
37
+ return {
38
+ versionId: createBlueprintVersionId(),
39
+ contentHash: blueprintContentHash(blueprint),
40
+ };
41
+ }
42
+
43
+ interface BlueprintReserveProject {
44
+ key: string;
45
+ versionId: string;
46
+ remaining: readonly BlueprintStep[];
47
+ }
48
+
49
+ interface BlueprintResourceOverride {
50
+ id: string;
51
+ reason: string;
52
+ maxBlocks: number;
53
+ spent: number;
54
+ startedAt: number;
55
+ expiresAt: number;
56
+ }
57
+
58
+ interface BlueprintRestockMarker {
59
+ overrideId: string;
60
+ reason: string;
61
+ maxBlocks: number;
62
+ borrowed: Readonly<Record<string, number>>;
63
+ at: number;
64
+ }
65
+
66
+ type BlueprintPlacementDecision =
67
+ | { ok: true; source: 'unreserved' | 'surplus' | 'override' }
68
+ | { ok: false; reason: string };
69
+
70
+ /** 每次计算时读取名单和容器库存。 */
71
+ interface BlueprintLedgerFacts {
72
+ /** 当下生效的垫脚名单(World 侧 = `policy.scaffold ?? cfg.scaffoldBlocks`) */
73
+ scaffold?: () => readonly string[];
74
+ /** 当下生效的照明名单(World 侧 = `policy.light ?? 默认 ['torch']`) */
75
+ light?: () => readonly string[];
76
+ /** 箱子账本里本世界的存货(与随身分账;口径是「上次看见」) */
77
+ stored?: () => Readonly<Record<string, number>>;
78
+ }
79
+
80
+ /**
81
+ * 保护已接受蓝图剩余施工所需的材料,允许使用随身盈余。
82
+ * 当前垫脚/照明名单和容器存货从预留量扣除;普通放置受逐块许可约束,临时借用按实际消耗扣预算。
83
+ */
84
+ export class BlueprintResourceLedger {
85
+ private projects = new Map<string, { versionId: string; bill: Record<string, number> }>();
86
+ private override: BlueprintResourceOverride | null = null;
87
+ private restock: BlueprintRestockMarker[] = [];
88
+ private observedStock: Record<string, number> = {};
89
+
90
+ constructor(private readonly facts: BlueprintLedgerFacts = {}) {}
91
+
92
+ sync(projects: readonly BlueprintReserveProject[]): boolean {
93
+ const before = this.projectSignature();
94
+ const next = new Map<string, { versionId: string; bill: Record<string, number> }>();
95
+ for (const project of projects) {
96
+ const bill = Object.fromEntries(
97
+ billForSteps(project.remaining).lines
98
+ .filter((line) => line.need > 0)
99
+ .map((line) => [line.item, line.need]),
100
+ );
101
+ next.set(project.key, { versionId: project.versionId, bill });
102
+ }
103
+ this.projects = next;
104
+ return before !== this.projectSignature();
105
+ }
106
+
107
+ /**
108
+ * 当前垫脚与照明名单中的物品不进 reserve,即使它们也是蓝图主料。
109
+ * 逐块硬否决仍由 placementDecision 处理,reserve_override 仍按预算生效。
110
+ */
111
+ private scaffoldExempt(): ReadonlySet<string> {
112
+ return new Set([...this.facts.scaffold?.() ?? [], ...this.facts.light?.() ?? []]);
113
+ }
114
+
115
+ /** 预留量为剩余需求扣除豁免物品与容器存货。施工材料统计使用随身与容器数量,放置要求随身持有。 */
116
+ reserve(): Record<string, number> {
117
+ const exempt = this.scaffoldExempt();
118
+ const stored = this.facts.stored?.() ?? {};
119
+ const total: Record<string, number> = {};
120
+ for (const project of this.projects.values()) {
121
+ for (const [item, count] of Object.entries(project.bill)) {
122
+ if (exempt.has(item)) continue;
123
+ total[item] = (total[item] ?? 0) + count;
124
+ }
125
+ }
126
+ for (const [item, need] of Object.entries(total)) {
127
+ const left = need - Math.max(0, Math.floor(stored[item] ?? 0));
128
+ if (left > 0) total[item] = left;
129
+ else delete total[item];
130
+ }
131
+ return total;
132
+ }
133
+
134
+ protectedItems(): string[] {
135
+ return Object.keys(this.reserve()).sort();
136
+ }
137
+
138
+ /** 签名仅包含原始工程需求;名单和容器库存由各自更新路径触发重算。 */
139
+ private projectSignature(): string {
140
+ return JSON.stringify([...this.projects]
141
+ .sort(([left], [right]) => left.localeCompare(right))
142
+ .map(([key, project]) => [
143
+ key,
144
+ project.versionId,
145
+ Object.entries(project.bill).sort(([left], [right]) => left.localeCompare(right)),
146
+ ]));
147
+ }
148
+
149
+ observe(stock: Readonly<Record<string, number>>, now = Date.now()): {
150
+ retune: boolean;
151
+ borrowed: Readonly<Record<string, number>>;
152
+ } {
153
+ const reserve = this.reserve();
154
+ const before = this.observedStock;
155
+ const borrowed: Record<string, number> = {};
156
+ let retune = false;
157
+ for (const [item, need] of Object.entries(reserve)) {
158
+ const was = Math.max(0, Math.floor(before[item] ?? 0));
159
+ const has = Math.max(0, Math.floor(stock[item] ?? 0));
160
+ if ((was > need) !== (has > need)) retune = true;
161
+ const enteredReserve = Math.max(0, need - has) - Math.max(0, need - was);
162
+ if (enteredReserve <= 0) continue;
163
+ const verdict = this.borrow(item, enteredReserve, now);
164
+ if (verdict.ok) borrowed[item] = enteredReserve;
165
+ else retune = true;
166
+ }
167
+ this.observedStock = Object.fromEntries(
168
+ Object.entries(stock).map(([item, count]) => [item, Math.max(0, Math.floor(count))]),
169
+ );
170
+ if (!this.activeOverride(now)) retune = retune || Object.keys(borrowed).length > 0;
171
+ return { retune, borrowed };
172
+ }
173
+
174
+ /** 返回存在预留量、随身数量不高于预留量且无临时覆盖的物品。 */
175
+ collapsedItems(
176
+ stock: Readonly<Record<string, number>> = this.observedStock,
177
+ now = Date.now(),
178
+ ): string[] {
179
+ const active = this.activeOverride(now);
180
+ if (active && active.spent < active.maxBlocks) return [];
181
+ return Object.entries(this.reserve())
182
+ .filter(([item, need]) => (stock[item] ?? 0) <= need)
183
+ .map(([item]) => item)
184
+ .sort();
185
+ }
186
+
187
+ /**
188
+ * 将预留收口的垫脚候选排到末位,不删除候选;硬否决由 placementDecision 处理。
189
+ * 垫脚名单由 scaffoldExempt 豁免,寻路可能消耗同名蓝图主料。
190
+ */
191
+ orderScaffoldCandidates(
192
+ candidates: readonly string[],
193
+ stock: Readonly<Record<string, number>> = this.observedStock,
194
+ now = Date.now(),
195
+ ): string[] {
196
+ const held = new Set(this.collapsedItems(stock, now));
197
+ /** 稳定排序保留同组候选的原顺序。 */
198
+ return [...candidates].sort((left, right) => Number(held.has(left)) - Number(held.has(right)));
199
+ }
200
+
201
+ /** 单块放置许可;实际消耗仍由放置后的库存观察逐块结算。 */
202
+ placementDecision(
203
+ item: string,
204
+ stock: Readonly<Record<string, number>> = this.observedStock,
205
+ now = Date.now(),
206
+ ): BlueprintPlacementDecision {
207
+ const need = this.reserve()[item];
208
+ if (need === undefined) return { ok: true, source: 'unreserved' };
209
+ const has = Math.max(0, Math.floor(stock[item] ?? 0));
210
+ if (has > need) return { ok: true, source: 'surplus' };
211
+ const active = this.activeOverride(now);
212
+ if (active && active.spent < active.maxBlocks) return { ok: true, source: 'override' };
213
+ return {
214
+ ok: false,
215
+ reason: `${item} 的蓝图 reserve 是 ${need},随身只有 ${has};要动它先用 mc_blueprint 的 reserve_override`,
216
+ };
217
+ }
218
+
219
+ openOverride(
220
+ spec: { reason: string; ttlMs: number; maxBlocks: number },
221
+ now = Date.now(),
222
+ ): BlueprintResourceOverride {
223
+ const override: BlueprintResourceOverride = {
224
+ id: `bpo_${randomUUID()}`,
225
+ reason: spec.reason,
226
+ maxBlocks: spec.maxBlocks,
227
+ spent: 0,
228
+ startedAt: now,
229
+ expiresAt: now + spec.ttlMs,
230
+ };
231
+ this.override = override;
232
+ return { ...override };
233
+ }
234
+
235
+ activeOverride(now = Date.now()): BlueprintResourceOverride | null {
236
+ if (!this.override) return null;
237
+ if (now >= this.override.expiresAt || this.override.spent >= this.override.maxBlocks) {
238
+ this.override = null;
239
+ return null;
240
+ }
241
+ return { ...this.override };
242
+ }
243
+
244
+ /** 库存观察确认实际动用保留料后扣预算;超预算或过期时拒绝记账。 */
245
+ borrow(item: string, count: number, now = Date.now()): { ok: true; left: number } | { ok: false; reason: string } {
246
+ const active = this.activeOverride(now);
247
+ if (!active) return { ok: false, reason: '没有有效的蓝图材料临时覆盖' };
248
+ if (!Number.isSafeInteger(count) || count <= 0) return { ok: false, reason: '借料数量必须是正整数' };
249
+ const left = active.maxBlocks - active.spent;
250
+ if (count > left) return { ok: false, reason: `临时覆盖只剩 ${left} 块预算` };
251
+ this.override!.spent += count;
252
+ const marker = this.restock.find((entry) => entry.overrideId === active.id);
253
+ if (marker) {
254
+ const borrowed = { ...marker.borrowed, [item]: (marker.borrowed[item] ?? 0) + count };
255
+ this.restock = this.restock.map((entry) => entry === marker ? { ...entry, borrowed } : entry);
256
+ } else {
257
+ this.restock.push({
258
+ overrideId: active.id,
259
+ reason: active.reason,
260
+ maxBlocks: active.maxBlocks,
261
+ borrowed: { [item]: count },
262
+ at: now,
263
+ });
264
+ if (this.restock.length > 8) this.restock.splice(0, this.restock.length - 8);
265
+ }
266
+ return { ok: true, left: left - count };
267
+ }
268
+
269
+ restockMarkers(): readonly BlueprintRestockMarker[] {
270
+ return this.restock.map((entry) => ({ ...entry, borrowed: { ...entry.borrowed } }));
271
+ }
272
+ }