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,367 @@
1
+ /** 将 ConsolePageSource 贡献组装为 manifest;逐个隔离异常、校验声明,并解析面板调用、流与资源。 */
2
+
3
+ import { readFileSync } from 'node:fs';
4
+ import { basename, join } from 'node:path';
5
+ import type { Logger } from '../core/types.ts';
6
+ import { extensionAssetUrl, type ExtensionConsoleAsset } from '../extensions/manifest.ts';
7
+ import type { Language } from '../core/language.ts';
8
+ import {
9
+ CONSOLE_PROTOCOL_VERSION,
10
+ assetKeyForPage,
11
+ isSafeAssetUrl,
12
+ sanitizeLamps,
13
+ toPageManifest,
14
+ validateContributions,
15
+ type ConsoleAssetEntry,
16
+ type ConsoleAssetManifest,
17
+ type ConsoleLamp,
18
+ type ConsoleManifest,
19
+ type ConsolePageContribution,
20
+ type ConsoleStream,
21
+ } from './shared/console-protocol.ts';
22
+
23
+ /**
24
+ * 一页的来源。装配层为每个 World / 每个 bot 的控制面造一个。
25
+ *
26
+ * `contribute()` 每次组装 manifest 都会被调用(`badges` 是活数据),所以它应当
27
+ * 便宜。返回 null = 这一轮不露面(比如 World 临时不可用)。
28
+ */
29
+ export interface ConsolePageSource {
30
+ /** 仅用于出错时说清"是谁炸了";正式 id 以 `contribute()` 返回的为准 */
31
+ id: string;
32
+ /**
33
+ * `language` 是发起这次请求的浏览器的界面语言:贡献里的文案、`invoke` 与 `stream`
34
+ * 闭包回给操作员的话都按它。页的 id、面板 id 与能力不随语言变。
35
+ */
36
+ contribute(language: Language):
37
+ | ConsolePageContribution
38
+ | null
39
+ | Promise<ConsolePageContribution | null>;
40
+ }
41
+
42
+ /** 资源来自 dist 清单或扩展预构建产物,同 key 时 dist 优先;URL 均经 isSafeAssetUrl 校验,扩展 URL 由服务端分配。 */
43
+ export class ConsoleAssets {
44
+ private readonly distDir: string;
45
+ private readonly log: Logger;
46
+ private manifest: ConsoleAssetManifest = {
47
+ protocolVersion: CONSOLE_PROTOCOL_VERSION,
48
+ core: null,
49
+ providers: {},
50
+ };
51
+ /** 扩展产物表。加载完就定死,`reload()` 只重读 dist。 */
52
+ private readonly extensionPages: Record<string, ConsoleAssetEntry>;
53
+
54
+ constructor(distDir: string, log: Logger, extensionAssets: readonly ExtensionConsoleAsset[] = []) {
55
+ this.distDir = distDir;
56
+ this.log = log;
57
+ this.extensionPages = this.indexExtensionAssets(extensionAssets);
58
+ this.reload();
59
+ }
60
+
61
+ private indexExtensionAssets(assets: readonly ExtensionConsoleAsset[]): Record<string, ConsoleAssetEntry> {
62
+ const out: Record<string, ConsoleAssetEntry> = Object.create(null) as Record<string, ConsoleAssetEntry>;
63
+ for (const a of assets) {
64
+ const js = extensionAssetUrl(a.packageName, a.version, basename(a.jsFile));
65
+ if (!isSafeAssetUrl(js)) {
66
+ this.log.error(`扩展产物被拒: ${a.packageName} 的 js 分配不出合法的 /assets/ 路径`, { value: js });
67
+ continue;
68
+ }
69
+ const entry: ConsoleAssetEntry = { js };
70
+ if (a.cssFile !== undefined) {
71
+ const css = extensionAssetUrl(a.packageName, a.version, basename(a.cssFile));
72
+ if (isSafeAssetUrl(css)) {
73
+ entry.css = css;
74
+ } else {
75
+ this.log.error(`扩展产物的 css 被拒: ${a.packageName}`, { value: css });
76
+ }
77
+ }
78
+ out[assetKeyForPage(a.pageId)] = entry;
79
+ }
80
+ return out;
81
+ }
82
+
83
+ /** 重读构建产物。没构建过 = 空表,不是错误(控制台照常起,只是没有扩展)。 */
84
+ reload(): void {
85
+ const file = join(this.distDir, 'asset-manifest.json');
86
+ let raw: string;
87
+ try {
88
+ raw = readFileSync(file, 'utf8');
89
+ } catch {
90
+ this.manifest = { protocolVersion: CONSOLE_PROTOCOL_VERSION, core: null, providers: {} };
91
+ return;
92
+ }
93
+ let parsed: ConsoleAssetManifest;
94
+ try {
95
+ parsed = JSON.parse(raw) as ConsoleAssetManifest;
96
+ } catch (err) {
97
+ this.log.error('asset-manifest.json 解析失败,浏览器扩展全部不可用', { error: String(err) });
98
+ this.manifest = { protocolVersion: CONSOLE_PROTOCOL_VERSION, core: null, providers: {} };
99
+ return;
100
+ }
101
+
102
+ // 拒绝不匹配的产物协议版本。
103
+ if (parsed.protocolVersion !== CONSOLE_PROTOCOL_VERSION) {
104
+ this.log.error(
105
+ `asset-manifest.json 的协议版本是 ${String(parsed.protocolVersion)},`
106
+ + `本进程要求 ${CONSOLE_PROTOCOL_VERSION},所有扩展产物已拒绝加载。请先停止 bot,再运行 pnpm build:web。`,
107
+ );
108
+ this.manifest = { protocolVersion: CONSOLE_PROTOCOL_VERSION, core: null, providers: {} };
109
+ return;
110
+ }
111
+
112
+ const pages: Record<string, ConsoleAssetEntry> = Object.create(null) as Record<string, ConsoleAssetEntry>;
113
+ for (const [key, entry] of Object.entries(parsed.providers ?? {})) {
114
+ if (!isSafeAssetUrl(entry?.js)) {
115
+ this.log.error(`asset 条目被拒: ${key} 的 js 不是合法的 /assets/ 路径`, {
116
+ value: String(entry?.js),
117
+ });
118
+ continue;
119
+ }
120
+ const safe: ConsoleAssetEntry = { js: entry.js };
121
+ if (entry.css !== undefined) {
122
+ if (isSafeAssetUrl(entry.css)) {
123
+ safe.css = entry.css;
124
+ } else {
125
+ this.log.error(`asset 条目的 css 被拒: ${key}`, { value: String(entry.css) });
126
+ }
127
+ }
128
+ pages[key] = safe;
129
+ }
130
+ const core = isSafeAssetUrl(parsed.core) ? parsed.core : null;
131
+ if (parsed.core && !core) {
132
+ this.log.error('asset-manifest.json 的 core 不是合法的 /assets/ 路径,已忽略', {
133
+ value: String(parsed.core),
134
+ });
135
+ }
136
+ this.manifest = { protocolVersion: CONSOLE_PROTOCOL_VERSION, core, providers: pages };
137
+ }
138
+
139
+ /** 内核入口 URL;尚未构建时 null。 */
140
+ core(): string | null {
141
+ return this.manifest.core;
142
+ }
143
+
144
+ /** 按 key 依次查询 dist 与扩展资源表,缺失时返回 undefined。 */
145
+ forPage(pageId: string): ConsoleAssetEntry | undefined {
146
+ const key = assetKeyForPage(pageId);
147
+ return this.manifest.providers[key] ?? this.extensionPages[key];
148
+ }
149
+ }
150
+
151
+ export interface ConsolePageRegistryDeps {
152
+ /** 当前全部页来源。每次取 manifest 都重新问一遍(World 会挂载/卸载)。 */
153
+ sources(): ConsolePageSource[];
154
+ /** 框架级表面的挂载情况,原样进 manifest.framework.capabilities */
155
+ capabilities(): Record<string, boolean>;
156
+ assets: ConsoleAssets;
157
+ log: Logger;
158
+ }
159
+
160
+ /**
161
+ * 面板解析失败的原因。`invoke` 与 `stream` 共用这三态:两条通道的解析路径
162
+ * 一模一样(找那一页 → 查 panel 声明 → 看那个面在不在),失败语义自然也一样。
163
+ * 服务端据此选 404/503,或选 WS 的关闭 code。
164
+ */
165
+ export type InvokeFailure =
166
+ | { kind: 'no-provider'; message: string }
167
+ | { kind: 'no-panel'; message: string }
168
+ | { kind: 'no-surface'; message: string }
169
+ | { kind: 'method-not-allowed'; message: string };
170
+
171
+ export class ConsolePageRegistry {
172
+ private readonly deps: ConsolePageRegistryDeps;
173
+
174
+ constructor(deps: ConsolePageRegistryDeps) {
175
+ this.deps = deps;
176
+ }
177
+
178
+ /**
179
+ * 逐个 source 取贡献。**隔离在这里**:一个 source 抛错只丢它自己,
180
+ * 其余照常进 manifest。
181
+ */
182
+ private async collect(language: Language): Promise<ConsolePageContribution[]> {
183
+ const sources = this.safeSources();
184
+ const out: ConsolePageContribution[] = [];
185
+ for (const src of sources) {
186
+ try {
187
+ const c = await src.contribute(language);
188
+ if (c) out.push(c);
189
+ } catch (err) {
190
+ this.deps.log.error(`provider 贡献失败,已跳过: ${src.id}`, { error: String(err) });
191
+ }
192
+ }
193
+ return out;
194
+ }
195
+
196
+ private safeSources(): ConsolePageSource[] {
197
+ try {
198
+ return this.deps.sources() ?? [];
199
+ } catch (err) {
200
+ this.deps.log.error('provider 来源枚举失败', { error: String(err) });
201
+ return [];
202
+ }
203
+ }
204
+
205
+ /**
206
+ * 组装 manifest。校验不通过的页**整个丢掉**并记一条日志——
207
+ * 半页坏的上线,前端会以一种很难查的方式坏掉。
208
+ */
209
+ async manifest(language: Language = 'zh'): Promise<ConsoleManifest> {
210
+ const collected = await this.collect(language);
211
+ const problems = validateContributions(collected);
212
+ const rejected = new Set<string>();
213
+ for (const p of problems) {
214
+ this.deps.log.error(`provider 声明不合法,已丢弃: ${p.message}`, { provider: p.pageId });
215
+ rejected.add(p.pageId);
216
+ }
217
+
218
+ const pages = collected
219
+ .filter((c) => !rejected.has(c.id))
220
+ .map((c) => {
221
+ const entry = toPageManifest(c, this.deps.assets.forPage(c.id));
222
+ // 记录被过滤的链接,保留其余合法成员。
223
+ const dropped = (c.links?.length ?? 0) - (entry.links?.length ?? 0);
224
+ if (dropped > 0) {
225
+ this.deps.log.error(`provider ${c.id} 有 ${dropped} 条链接的 href 不安全,已丢弃`, {
226
+ hrefs: (c.links ?? []).map((l) => String(l?.href)),
227
+ });
228
+ }
229
+ // 面板同理:`builtin` 名写歪的那一块会被整块丢掉,不说一声就是"页上少了一格"。
230
+ const droppedPanels = (c.panels?.length ?? 0) - (entry.panels?.length ?? 0);
231
+ if (droppedPanels > 0) {
232
+ this.deps.log.error(`provider ${c.id} 有 ${droppedPanels} 块面板的 builtin 名不合法,已丢弃`, {
233
+ builtins: (c.panels ?? []).map((p) => String(p?.builtin)),
234
+ });
235
+ }
236
+ return entry;
237
+ });
238
+
239
+ let capabilities: Record<string, boolean>;
240
+ try {
241
+ capabilities = this.deps.capabilities() ?? {};
242
+ } catch (err) {
243
+ this.deps.log.error('capabilities 计算失败', { error: String(err) });
244
+ capabilities = {};
245
+ }
246
+
247
+ return {
248
+ protocolVersion: CONSOLE_PROTOCOL_VERSION,
249
+ providers: pages,
250
+ framework: { capabilities },
251
+ };
252
+ }
253
+
254
+ /** 获取状态灯,不执行 manifest 的声明校验、资源解析或面板序列化。 */
255
+ async lamps(language: Language = 'zh'): Promise<Record<string, ConsoleLamp[]>> {
256
+ const out: Record<string, ConsoleLamp[]> = {};
257
+ for (const c of await this.collect(language)) {
258
+ const lamps = sanitizeLamps(c.lamps);
259
+ if (lamps.length) out[c.id] = lamps;
260
+ }
261
+ return out;
262
+ }
263
+
264
+ /** 按 id 找当前的贡献。找不到返回 null。 */
265
+ private async find(pageId: string, language: Language): Promise<ConsolePageContribution | null> {
266
+ for (const src of this.safeSources()) {
267
+ let c: ConsolePageContribution | null;
268
+ try {
269
+ c = await src.contribute(language);
270
+ } catch (err) {
271
+ this.deps.log.error(`provider 贡献失败: ${src.id}`, { error: String(err) });
272
+ continue;
273
+ }
274
+ if (c && c.id === pageId) return c;
275
+ }
276
+ return null;
277
+ }
278
+
279
+ /**
280
+ * 定位一个面板:那一页在不在、这个 panel 声明过没有。到"那个面在不在"为止的
281
+ * 判断两条通道共享;面本身由调用方各自取(`invoke` / `stream`)。
282
+ */
283
+ private async resolvePanel(
284
+ pageId: string,
285
+ panelId: string,
286
+ language: Language,
287
+ ): Promise<
288
+ | { ok: true; contribution: ConsolePageContribution; panel: NonNullable<ConsolePageContribution['panels']>[number] }
289
+ | { ok: false; failure: InvokeFailure }
290
+ > {
291
+ const c = await this.find(pageId, language);
292
+ if (!c) {
293
+ return { ok: false, failure: { kind: 'no-provider', message: `没有这个 provider: ${pageId}` } };
294
+ }
295
+ const panel = (c.panels ?? []).find((p) => p.id === panelId);
296
+ if (!panel) {
297
+ return {
298
+ ok: false,
299
+ failure: { kind: 'no-panel', message: `provider ${pageId} 没有声明面板: ${panelId}` },
300
+ };
301
+ }
302
+ return { ok: true, contribution: c, panel };
303
+ }
304
+
305
+ /**
306
+ * 一次面板调用。语义、参数校验、结果形状全归那一页——这里只做解析与转交。
307
+ *
308
+ * 抛出的错误由调用方(server.ts)翻成 500;解析失败走返回值里的
309
+ * `InvokeFailure`,因为那不是那一页的错。
310
+ */
311
+ async invoke(
312
+ pageId: string,
313
+ panelId: string,
314
+ method: string,
315
+ args: unknown[],
316
+ transport: 'get' | 'post' = 'post',
317
+ language: Language = 'zh',
318
+ ): Promise<{ ok: true; value: unknown } | { ok: false; failure: InvokeFailure }> {
319
+ const found = await this.resolvePanel(pageId, panelId, language);
320
+ if (!found.ok) return found;
321
+ // GET 只允许 getMethods 声明的方法。
322
+ if (transport === 'get' && !(found.panel.getMethods ?? []).includes(method)) {
323
+ return {
324
+ ok: false,
325
+ failure: { kind: 'method-not-allowed', message: `面板 ${panelId} 的方法 ${method} 只允许 POST` },
326
+ };
327
+ }
328
+ const c = found.contribution;
329
+ if (!c.invoke) {
330
+ return {
331
+ ok: false,
332
+ failure: { kind: 'no-surface', message: `provider ${pageId} 没有面板数据面` },
333
+ };
334
+ }
335
+ return { ok: true, value: await c.invoke(panelId, method, args) };
336
+ }
337
+
338
+ /**
339
+ * 解析一条流式通道。与 `invoke` 同构:这里只负责"找到谁来接这条连接",
340
+ * 接上之后的一切(帧格式、心跳、清理)全归那一页。
341
+ *
342
+ * 返回的是**还没调用**的 `open`——真 socket 的包装归服务端,注册表不认识 ws。
343
+ * `open` 由那一页的代码组成,抛错要由调用方接住并只关这一条连接。
344
+ */
345
+ async resolveStream(
346
+ pageId: string,
347
+ panelId: string,
348
+ language: Language = 'zh',
349
+ ): Promise<
350
+ | { ok: true; open: (socket: ConsoleStream) => void }
351
+ | { ok: false; failure: InvokeFailure }
352
+ > {
353
+ // 通道名与面板 id 同一个命名空间,但不要求声明过同名面板:没有面板的页也能有流。
354
+ const c = await this.find(pageId, language);
355
+ if (!c) {
356
+ return { ok: false, failure: { kind: 'no-provider', message: `没有这个 provider: ${pageId}` } };
357
+ }
358
+ const stream = c.stream;
359
+ if (!stream) {
360
+ return {
361
+ ok: false,
362
+ failure: { kind: 'no-surface', message: `provider ${pageId} 没有流式面` },
363
+ };
364
+ }
365
+ return { ok: true, open: (socket) => stream.call(c, panelId, socket) };
366
+ }
367
+ }
@@ -0,0 +1,107 @@
1
+ import { closeSync, existsSync, fstatSync, openSync, readFileSync, readSync } from 'node:fs';
2
+ import { LOG_LEVEL_RANK, type LogLevel, type LogRecord } from '../core/types.ts';
3
+
4
+ const TAIL_CHUNK = 512 * 1024;
5
+ /** 过滤查询从文件尾部向前最多扫这么多字节 */
6
+ const SCAN_MAX_BYTES = 32 * 1024 * 1024;
7
+
8
+ /**
9
+ * 从文件尾部向前按块扫,收满 limit 条通过 pred 的记录即停;最多扫 SCAN_MAX_BYTES。
10
+ * 返回按文件顺序(升序)。文件不存在→[]。
11
+ */
12
+ export function readTailRecordsWhere<T>(file: string, limit: number, pred: (record: T) => boolean): T[] {
13
+ let descriptor: number;
14
+ try {
15
+ descriptor = openSync(file, 'r');
16
+ } catch {
17
+ return [];
18
+ }
19
+ const out: T[] = [];
20
+ try {
21
+ const size = fstatSync(descriptor).size;
22
+ let end = size;
23
+ let carry = '';
24
+ let scanned = 0;
25
+ while (end > 0 && out.length < limit && scanned < SCAN_MAX_BYTES) {
26
+ const start = Math.max(0, end - TAIL_CHUNK);
27
+ const buffer = Buffer.alloc(end - start);
28
+ readSync(descriptor, buffer, 0, end - start, start);
29
+ scanned += end - start;
30
+ const text = buffer.toString('utf8') + carry;
31
+ const lines = text.split(/\r?\n/);
32
+ // 块首的半行留给下一块拼
33
+ carry = start > 0 ? lines.shift() ?? '' : '';
34
+ for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
35
+ const line = lines[i].trim();
36
+ if (!line) continue;
37
+ let record: T;
38
+ try { record = JSON.parse(line) as T; } catch { continue; }
39
+ if (pred(record)) out.push(record);
40
+ }
41
+ end = start;
42
+ }
43
+ } finally {
44
+ closeSync(descriptor);
45
+ }
46
+ return out.reverse();
47
+ }
48
+
49
+ export interface LogQuery {
50
+ /** 最低级别 */
51
+ level?: string;
52
+ /** 区域前缀(worlds.x 命中 worlds.x 与 worlds.x.*) */
53
+ area?: string;
54
+ event?: string;
55
+ /** 正则(不区分大小写),匹配 msg 与 data */
56
+ grep?: string;
57
+ /** ISO 前缀比较:ts >= since */
58
+ since?: string;
59
+ round?: number;
60
+ call?: string;
61
+ }
62
+
63
+ export function logPredicate(q: LogQuery): (record: LogRecord) => boolean {
64
+ const min = q.level && q.level in LOG_LEVEL_RANK ? LOG_LEVEL_RANK[q.level as LogLevel] : 0;
65
+ let re: RegExp | null = null;
66
+ if (q.grep) {
67
+ try { re = new RegExp(q.grep, 'i'); } catch { re = new RegExp(q.grep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); }
68
+ }
69
+ const area = q.area?.replace(/\.?\*$/, '');
70
+ return (r) => {
71
+ if (!r || typeof r !== 'object' || typeof r.level !== 'string') return false;
72
+ if ((LOG_LEVEL_RANK[r.level] ?? 0) < min) return false;
73
+ if (area && r.area !== area && !r.area.startsWith(`${area}.`)) return false;
74
+ if (q.event && r.event !== q.event) return false;
75
+ if (q.since && r.ts < q.since) return false;
76
+ if (q.round !== undefined && r.round !== q.round) return false;
77
+ if (q.call && r.call !== q.call) return false;
78
+ if (re && !re.test(r.msg) && !(r.data !== undefined && re.test(JSON.stringify(r.data)))) return false;
79
+ return true;
80
+ };
81
+ }
82
+
83
+ export interface RunIndexRow {
84
+ run: string;
85
+ startedAt?: string;
86
+ endedAt?: string;
87
+ bot?: string;
88
+ gitSha?: string | null;
89
+ previousRun?: string | null;
90
+ lastCursor?: number;
91
+ complete?: boolean | null;
92
+ reason?: string;
93
+ }
94
+
95
+ /** runs/index.jsonl 按 run 合并开机行与关机行,升序。 */
96
+ export function readRunsIndex(file: string): RunIndexRow[] {
97
+ if (!existsSync(file)) return [];
98
+ const byRun = new Map<string, RunIndexRow>();
99
+ for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
100
+ if (!line.trim()) continue;
101
+ let row: RunIndexRow;
102
+ try { row = JSON.parse(line) as RunIndexRow; } catch { continue; }
103
+ if (typeof row.run !== 'string') continue;
104
+ byRun.set(row.run, { ...byRun.get(row.run), ...row });
105
+ }
106
+ return [...byRun.values()].sort((a, b) => a.run.localeCompare(b.run));
107
+ }