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,5 @@
1
+ 你在QQ上。
2
+
3
+ 下面是你正在参与的会话:
4
+ {{qq.conversations | (还没有配置任何监听的群或私聊。)}}
5
+ {{qq.identity | (与QQ的连接尚未建立,群名和你的账号信息暂时未知。)}}
@@ -0,0 +1,108 @@
1
+ import type { ConfigGroup } from '../../core/types.ts';
2
+ import type { VisionConfig } from './vision.ts';
3
+
4
+ /** 关闭的 roster 条目仍持久化并显示,仅从监听集合中排除。 */
5
+ export interface QQRosterEntry {
6
+ id: number;
7
+ enabled: boolean;
8
+ }
9
+
10
+ export const QQ_DEFAULTS = {
11
+ enabled: false,
12
+ /** NapCat 正向 WS 地址 */
13
+ wsUrl: 'ws://127.0.0.1:3001',
14
+ groups: [] as QQRosterEntry[],
15
+ privates: [] as QQRosterEntry[],
16
+ token: '',
17
+ } as const;
18
+
19
+ /**
20
+ * 本 World 需要的密钥的**环境变量名**。辅助视觉用的是 OpenRouter——
21
+ * core 不认识这个名字,装配层照 World 声明的名字取值。
22
+ */
23
+ export const QQ_SECRETS = { vision: 'OPENROUTER_API_KEY' } as const;
24
+
25
+ /**
26
+ * 本 World 声明的可调项。只声明**World 自己**的参数:连接与监听名单有
27
+ * 专用面板(要卡片式增删、要重启),这里放的是自带视觉的行为旋钮。
28
+ */
29
+ export const QQ_CONFIG_GROUP: ConfigGroup = {
30
+ id: 'world:qq',
31
+ owner: 'world:qq',
32
+ schema: {
33
+ type: 'object',
34
+ title: 'QQ · 辅助视觉',
35
+ description: '图片描述写入事件库。',
36
+ properties: {
37
+ 'worlds.qq.vision.enabled': {
38
+ type: 'boolean',
39
+ title: '开启辅助视觉',
40
+ 'x-hot': false,
41
+ description: '关掉后图片只留可读占位。需要重启生效(装配时才创建视觉服务),且需要配好 OPENROUTER_API_KEY。',
42
+ },
43
+ 'worlds.qq.vision.model': {
44
+ type: 'string',
45
+ title: 'VLM 模型',
46
+ 'x-hot': false,
47
+ description: 'OpenRouter 上的模型名。重启生效。',
48
+ },
49
+ 'worlds.qq.vision.concurrency': {
50
+ type: 'integer',
51
+ title: '被动识图并发',
52
+ minimum: 1,
53
+ maximum: 16,
54
+ multipleOf: 1,
55
+ 'x-suffix': '路',
56
+ 'x-hot': true,
57
+ description: '同时最多跑几张图的识别。',
58
+ },
59
+ 'worlds.qq.vision.dedupPrecheckMs': {
60
+ type: 'integer',
61
+ title: '去重预判等待上限',
62
+ minimum: 0,
63
+ maximum: 30_000,
64
+ multipleOf: 100,
65
+ 'x-suffix': 'ms',
66
+ 'x-hot': true,
67
+ description: '消息渲染前检查图片是否重复的最长等待时间。命中时引用已有图片,下载失败时标记失败;'
68
+ + '新图片或超时先显示占位,识图结果另行投递。',
69
+ },
70
+ 'worlds.qq.vision.maxImageBytes': {
71
+ type: 'integer',
72
+ title: '图片下载上限',
73
+ minimum: 65_536,
74
+ maximum: 104_857_600,
75
+ multipleOf: 65_536,
76
+ 'x-scale': 1_048_576,
77
+ 'x-suffix': 'MB',
78
+ 'x-hot': true,
79
+ description: '超过这个大小的图不下载、不识别。',
80
+ },
81
+ 'worlds.qq.vision.timeoutMs': {
82
+ type: 'integer',
83
+ title: 'VLM 请求超时',
84
+ minimum: 1000,
85
+ maximum: 300_000,
86
+ multipleOf: 1000,
87
+ 'x-scale': 1000,
88
+ 'x-suffix': 's',
89
+ 'x-hot': false,
90
+ description: '单次识别请求的超时。重启生效(客户端构造时固定)。',
91
+ },
92
+ },
93
+ },
94
+ };
95
+
96
+ /** config.json 的 `worlds.qq` 节。 */
97
+ export interface QQConfigSection {
98
+ enabled: boolean;
99
+ /** NapCat 正向 WS 地址 */
100
+ wsUrl: string;
101
+ /** 群 roster,逐项热更新;关闭的条目仍持久化并显示,但不参与监听。 */
102
+ groups: QQRosterEntry[];
103
+ /** 监听的私聊 QQ 号 roster,语义同 groups */
104
+ privates: QQRosterEntry[];
105
+ /** OneBot access token,可空 */
106
+ token: string;
107
+ vision: VisionConfig;
108
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * QQ World 的浏览器扩展 —— 接入门 / 监听名单 / 事件三个面板。
3
+ *
4
+ * 这个文件只做两件事:**装配**(把三个面板接到局部 id 上)与**共享 helper**
5
+ * (三个面板都要的取数—渲染骨架、类型、错误措辞)。面板本体各在自己的文件里。
6
+ *
7
+ * 与外界的依赖只有一条:`client-panel.ts` 里的**类型**。没有 import 控制台内部
8
+ * 模块,没有 `fetch`,没有 `document.body`,没有 `window.__*`——数据面一律走
9
+ * `ctx.invoke`,DOM 一律用 `ctx.ui` 的原语,定时器一律走 `ctx.interval`。
10
+ *
11
+ * `style.css` 是本 provider 自己的样式:只放 `ctx.ui` 没有对应原语的那几块布局
12
+ * (名单卡片的网格、字段标签),用 `qq-` 前缀避开控制台的通用 class,
13
+ * 变量取自控制台的主题变量,所以浅深色两套皮都跟着走。
14
+ */
15
+
16
+ import type {
17
+ ConsoleClientBundle,
18
+ ConsolePanelContext,
19
+ } from '../../../web/shared/client-panel.ts';
20
+ import './style.css';
21
+ import { gatePanel } from './gate.ts';
22
+ import { rosterPanel } from './roster.ts';
23
+ import { eventsPanel } from './events.ts';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // 共享类型:服务端 `QQWorld.invokePanel` 各方法的返回形状
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /** `gate.state` */
30
+ export interface QQGateState {
31
+ enabled: boolean;
32
+ wsUrl: string;
33
+ tokenSet: boolean;
34
+ connected: boolean;
35
+ selfId: number | null;
36
+ nickname: string;
37
+ groups: QQGroupName[];
38
+ privates: QQPrivateName[];
39
+ }
40
+
41
+ interface QQGroupName {
42
+ id: number;
43
+ name: string;
44
+ card: string;
45
+ }
46
+
47
+ interface QQPrivateName {
48
+ id: number;
49
+ name: string;
50
+ }
51
+
52
+ /** `roster.names` / `events.names`:号码 → 人看得懂的名字 */
53
+ export interface QQConvNames {
54
+ groups: QQGroupName[];
55
+ privates: QQPrivateName[];
56
+ }
57
+
58
+ /** `roster.get` 的一条。关掉的条目仍留在名单里,只是不进监听集合。 */
59
+ export interface QQRosterEntry {
60
+ id: number;
61
+ enabled: boolean;
62
+ }
63
+
64
+ export interface QQRoster {
65
+ groups: QQRosterEntry[];
66
+ privates: QQRosterEntry[];
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // 共享 helper
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /** 错误 → 一句人话。`ConsoleInvokeError` 带的就是服务端的中文措辞。 */
74
+ export function errText(err: unknown): string {
75
+ return err instanceof Error ? err.message : String(err);
76
+ }
77
+
78
+ export interface AutoloadOptions<T> {
79
+ /** 取数期间铺的空态措辞 */
80
+ loading: string;
81
+ /** 取数失败时空态的前半句,后面接错误原文 */
82
+ failed: string;
83
+ load(): Promise<T>;
84
+ /**
85
+ * 画出来。`reload` 重跑一遍取数与渲染——面板改完自己的状态之后调它,
86
+ * 而不是去重挂面板(那会连同定时器与滚动位置一起重来)。
87
+ */
88
+ render(data: T, reload: () => void): Node[];
89
+ }
90
+
91
+ /**
92
+ * 三个面板共用的取数—渲染骨架:空态 → 取数 → 渲染或错误空态。
93
+ * 请求代号防止晚到的旧响应覆盖新响应;unmount 后到达的异步拒绝不再写 DOM。
94
+ */
95
+ export function autoload<T>(ctx: ConsolePanelContext, opts: AutoloadOptions<T>): void {
96
+ const { ui, root } = ctx;
97
+ let generation = 0;
98
+ const run = (): void => {
99
+ const gen = ++generation;
100
+ root.replaceChildren(ui.placeholder(opts.loading));
101
+ void opts.load().then(
102
+ (data) => {
103
+ if (gen !== generation || ctx.signal.aborted) return;
104
+ root.replaceChildren(...opts.render(data, run));
105
+ },
106
+ (err: unknown) => {
107
+ if (gen !== generation || ctx.signal.aborted) return;
108
+ root.replaceChildren(ui.placeholder(`${opts.failed}: ${errText(err)}`));
109
+ },
110
+ );
111
+ };
112
+ run();
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+
117
+ const bundle: ConsoleClientBundle = {
118
+ // 键是**局部** panel id,与服务端 `console().panels[].id` 一一对应。
119
+ panels: {
120
+ gate: gatePanel,
121
+ roster: rosterPanel,
122
+ events: eventsPanel,
123
+ },
124
+ };
125
+
126
+ export default bundle;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * 面板 `events` —— 事件库里 `source=qq` 的历史,按会话翻。
3
+ *
4
+ * 只读面板:选会话、翻记录、改条数上限、刷新。离线(NapCat 没连上)也能看,
5
+ * 因为它读的是事件库而不是协议端。
6
+ *
7
+ * 选中的会话与条数上限记在 `ctx.memo` 里(按 `provider:panel` 命名空间隔离),
8
+ * 所以离开面板再回来还在原处。
9
+ */
10
+
11
+ import type {
12
+ ConsolePanelContext,
13
+ ConsolePanel,
14
+ } from '../../../web/shared/client-panel.ts';
15
+ import { autoload, type QQConvNames } from './client.ts';
16
+
17
+ /** 事件 type → 列表里那颗药丸上的字。不认识的 type 原样显示。 */
18
+ const TYPE_LABEL: Record<string, string> = {
19
+ 'qq.message': '消息',
20
+ 'qq.self': '我',
21
+ 'qq.recall': '撤回',
22
+ 'qq.member': '成员',
23
+ 'qq.emoji': '表情',
24
+ 'qq.poke': '戳一戳',
25
+ 'qq.vision': '识图',
26
+ 'qq.watch': '监听',
27
+ 'qq.forward': '转发',
28
+ 'qq.reply.uncaptured': '引用',
29
+ };
30
+
31
+ /** 服务端把它夹在 1..2000 之间,这里只给几个常用挡位。 */
32
+ const LIMITS = ['100', '300', '1000', '2000'] as const;
33
+ const DEFAULT_LIMIT = '300';
34
+
35
+ interface QQConvSummary {
36
+ kind: 'group' | 'private';
37
+ id: number;
38
+ count: number;
39
+ lastTs: string;
40
+ }
41
+
42
+ interface QQLeanEvent {
43
+ cursor: number;
44
+ type: string;
45
+ ts: string;
46
+ text: string;
47
+ senderKey: string;
48
+ senderName: string;
49
+ conv: { kind?: string; id?: number } | null;
50
+ }
51
+
52
+ interface QQEventList {
53
+ conversations: QQConvSummary[];
54
+ events: QQLeanEvent[];
55
+ total: number;
56
+ }
57
+
58
+ interface EventsView {
59
+ list: QQEventList;
60
+ names: QQConvNames;
61
+ }
62
+
63
+ const NO_NAMES: QQConvNames = { groups: [], privates: [] };
64
+
65
+ export const eventsPanel: ConsolePanel = {
66
+ mount(ctx: ConsolePanelContext) {
67
+ // `''` = 全部会话
68
+ let conv = ctx.memo.get<string>('conv', '');
69
+ let limit = ctx.memo.get<string>('limit', DEFAULT_LIMIT);
70
+
71
+ autoload<EventsView>(ctx, {
72
+ loading: '加载 QQ 事件…',
73
+ failed: 'QQ 事件不可用',
74
+ load: async () => ({
75
+ list: await ctx.invoke<QQEventList>('list', [{ conv, limit: Number(limit) }]),
76
+ names: await ctx.invoke<QQConvNames>('names').catch(() => NO_NAMES),
77
+ }),
78
+ render: (view, reload) => [
79
+ sheet(ctx, view, {
80
+ conv,
81
+ limit,
82
+ pick: (next) => { conv = next; ctx.memo.set('conv', next); reload(); },
83
+ setLimit: (next) => { limit = next; ctx.memo.set('limit', next); reload(); },
84
+ reload,
85
+ }),
86
+ ],
87
+ });
88
+ },
89
+ };
90
+
91
+ interface Controls {
92
+ conv: string;
93
+ limit: string;
94
+ pick(conv: string): void;
95
+ setLimit(limit: string): void;
96
+ reload(): void;
97
+ }
98
+
99
+ function sheet(ctx: ConsolePanelContext, view: EventsView, ctl: Controls): HTMLElement {
100
+ const { ui } = ctx;
101
+ const card = ui.sheet({
102
+ title: 'QQ 事件',
103
+ en: '分群 / 私聊',
104
+ desc: `事件库里 source=qq 的历史,按会话查看(离线也能翻)。共 ${view.list.total} 条。`,
105
+ });
106
+
107
+ // 会话切换:与控制台画面板页签同一套做法(一行按钮,选中的那颗上主色)
108
+ const tabs = ui.rowbar();
109
+ const tab = (label: string, value: string): HTMLButtonElement =>
110
+ ui.button(label, {
111
+ size: 'sm',
112
+ variant: ctl.conv === value ? 'primary' : 'plain',
113
+ onClick: () => { if (ctl.conv !== value) ctl.pick(value); },
114
+ });
115
+ tabs.appendChild(tab('全部', ''));
116
+ for (const c of view.list.conversations) {
117
+ tabs.appendChild(tab(`${convName(view.names, c)} (${c.count})`, `${c.kind}:${c.id}`));
118
+ }
119
+ card.body.appendChild(tabs);
120
+
121
+ const bar = ui.rowbar();
122
+ bar.append(
123
+ ui.h('span', 'grow'),
124
+ ui.chip(`本页 ${view.list.events.length}`),
125
+ ui.select({
126
+ value: ctl.limit,
127
+ options: LIMITS.map((n) => ({ value: n, label: `最近 ${n} 条` })),
128
+ onInput: (v) => ctl.setLimit(v),
129
+ }),
130
+ ui.button('↻ 刷新', { size: 'sm', onClick: () => ctl.reload() }),
131
+ );
132
+ card.body.appendChild(bar);
133
+
134
+ const table = ui.table({ head: ['#', '类型', '正文'], maxHeight: 'calc(100vh - 360px)' });
135
+ if (!view.list.events.length) table.clear('(无事件)');
136
+ for (const e of view.list.events) {
137
+ table.addRow([
138
+ { text: `#${e.cursor}`, cls: 'mono' },
139
+ { el: ui.pill(TYPE_LABEL[e.type] ?? e.type, 'plain') },
140
+ { text: e.text || '', cls: 'txt' },
141
+ ]);
142
+ }
143
+ card.body.appendChild(table.el);
144
+ return card.el;
145
+ }
146
+
147
+ /** 会话在页签上的名字:能查到就用群名/称呼,否则退回号码。 */
148
+ function convName(names: QQConvNames, c: QQConvSummary): string {
149
+ if (c.kind === 'group') {
150
+ const g = names.groups.find((x) => x.id === c.id);
151
+ return `群 ${g?.name || c.id}`;
152
+ }
153
+ const p = names.privates.find((x) => x.id === c.id);
154
+ return `私聊 ${p?.name || c.id}`;
155
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * 面板 `gate` —— 接入门:接入开关 + 连接状态 + NapCat 连接。
3
+ *
4
+ * 三张卡靠同一份 `gate.state`,一次取数。改开关或改连接都会**真重启进程**,
5
+ * 所以这里还带着重启期间的等待与回来后的自动刷新(`restartWatch`)。
6
+ */
7
+
8
+ import type {
9
+ ConsolePanelContext,
10
+ ConsolePanel,
11
+ } from '../../../web/shared/client-panel.ts';
12
+ import { autoload, errText, type QQGateState } from './client.ts';
13
+
14
+ /** WS 地址的形状。与服务端 `invokeGate` 那条校验同一个判据,先在本地拦一次。 */
15
+ const WS_URL_RE = /^wss?:\/\/.+/i;
16
+
17
+ export const gatePanel: ConsolePanel = {
18
+ mount(ctx: ConsolePanelContext) {
19
+ autoload<QQGateState>(ctx, {
20
+ loading: '加载 QQ 接入状态…',
21
+ failed: 'QQ 接入状态不可用',
22
+ load: () => ctx.invoke<QQGateState>('state'),
23
+ render: (st) => [enableSheet(ctx, st), connSheet(ctx, st), napcatSheet(ctx, st)],
24
+ });
25
+ },
26
+ };
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // 接入开关
30
+ // ---------------------------------------------------------------------------
31
+
32
+ function enableSheet(ctx: ConsolePanelContext, st: QQGateState): HTMLElement {
33
+ const { ui } = ctx;
34
+ const card = ui.sheet({
35
+ title: '接入开关',
36
+ en: 'enabled',
37
+ desc: '开启或关闭 QQ 接入会重启进程。',
38
+ });
39
+
40
+ const status = ui.rowbar();
41
+ const btn = ui.button(st.enabled ? '关闭 QQ 接入' : '开启 QQ 接入', {
42
+ variant: st.enabled ? 'danger' : 'primary',
43
+ onClick: () => { void toggleEnabled(ctx, st, btn); },
44
+ });
45
+ status.append(
46
+ st.enabled
47
+ ? ui.pill(st.connected ? '已启用 · 已连接' : '已启用 · 未连接', st.connected ? 'on' : 'off')
48
+ : ui.pill('未启用', 'plain'),
49
+ ui.h('span', 'grow'),
50
+ );
51
+ card.body.appendChild(status);
52
+ if (st.enabled && !st.connected) {
53
+ card.body.appendChild(ui.msgline('NapCat 未连接。', true));
54
+ }
55
+ const actions = ui.actions();
56
+ actions.append(ui.h('span', 'grow'), btn);
57
+ card.body.appendChild(actions);
58
+ return card.el;
59
+ }
60
+
61
+ async function toggleEnabled(
62
+ ctx: ConsolePanelContext,
63
+ st: QQGateState,
64
+ btn: HTMLButtonElement,
65
+ ): Promise<void> {
66
+ const next = !st.enabled;
67
+ const verb = next ? '开启' : '关闭';
68
+ const ok = await ctx.ui.confirm({
69
+ title: `${verb} QQ 接入?`,
70
+ body: '此操作会重启进程。',
71
+ danger: true,
72
+ });
73
+ if (!ok) return;
74
+ btn.disabled = true;
75
+ try {
76
+ await ctx.invoke('setEnabled', [next]);
77
+ restartWatch(ctx, verb);
78
+ } catch (err) {
79
+ ctx.ui.toast(`操作失败: ${errText(err)}`, 'bad');
80
+ btn.disabled = false;
81
+ }
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // 连接状态
86
+ // ---------------------------------------------------------------------------
87
+
88
+ function connSheet(ctx: ConsolePanelContext, st: QQGateState): HTMLElement {
89
+ const { ui } = ctx;
90
+ const card = ui.sheet({ title: '连接状态', en: 'NapCat ⇄ core' });
91
+
92
+ const conn = !st.enabled
93
+ ? '未启用'
94
+ : st.connected
95
+ ? '已连接'
96
+ : '未连接';
97
+ const self = st.selfId != null
98
+ ? `${st.selfId}${st.nickname ? ` · ${st.nickname}` : ''}`
99
+ : '—';
100
+ const groups = st.groups.length
101
+ ? st.groups
102
+ .map((g) => `${g.name}(${g.id})${g.card ? ` · 我的群昵称「${g.card}」` : ''}`)
103
+ .join('\n')
104
+ : '(未配置)';
105
+ const privates = st.privates.length
106
+ ? st.privates.map((p) => (p.name ? `${p.name}(${p.id})` : String(p.id))).join('、')
107
+ : '(未配置)';
108
+
109
+ const table = ui.table();
110
+ table.addRow(['连接', { el: ui.pill(conn, !st.enabled ? 'plain' : st.connected ? 'on' : 'off') }]);
111
+ table.addRow(['自身 QQ', { text: self, cls: 'txt' }]);
112
+ table.addRow(['监听群', { text: groups, cls: 'txt' }]);
113
+ table.addRow(['监听私聊', { text: privates, cls: 'txt' }]);
114
+ card.body.appendChild(table.el);
115
+ card.body.appendChild(ui.msgline('监听名单在「监听名单」面板里热改(无需重启)。'));
116
+ return card.el;
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // NapCat 连接
121
+ // ---------------------------------------------------------------------------
122
+
123
+ function napcatSheet(ctx: ConsolePanelContext, st: QQGateState): HTMLElement {
124
+ const { ui } = ctx;
125
+ const card = ui.sheet({
126
+ title: 'NapCat 连接',
127
+ en: '正向 WS + token',
128
+ desc: 'NapCat 需保持运行并登录 QQ,通过正向 WebSocket 连接。保存连接配置后重启进程。',
129
+ });
130
+
131
+ const ws = ui.input({ value: st.wsUrl || '', placeholder: 'ws://127.0.0.1:3001' });
132
+ const token = ui.input({
133
+ type: 'password',
134
+ placeholder: st.tokenSet ? '已配置(留空=不改动)' : '(未配置)',
135
+ });
136
+ const msg = ui.msgline('');
137
+ const save = ui.button('保存并重启', {
138
+ variant: 'primary',
139
+ onClick: () => { void saveConnection(ctx, ws, token, msg, save); },
140
+ });
141
+
142
+ const bar = ui.actions();
143
+ bar.append(msg, ui.h('span', 'grow'), save);
144
+ card.body.append(
145
+ ui.field('WS 地址', ws),
146
+ ui.field('Access Token(可空)', token),
147
+ bar,
148
+ );
149
+ return card.el;
150
+ }
151
+
152
+ async function saveConnection(
153
+ ctx: ConsolePanelContext,
154
+ ws: HTMLInputElement,
155
+ token: HTMLInputElement,
156
+ msg: HTMLDivElement,
157
+ save: HTMLButtonElement,
158
+ ): Promise<void> {
159
+ const fail = (text: string): void => {
160
+ msg.textContent = text;
161
+ msg.classList.add('bad');
162
+ };
163
+ const wsUrl = ws.value.trim();
164
+ if (!WS_URL_RE.test(wsUrl)) {
165
+ fail('WS 地址必须以 ws:// 或 wss:// 开头');
166
+ return;
167
+ }
168
+ msg.textContent = '';
169
+ msg.classList.remove('bad');
170
+ const ok = await ctx.ui.confirm({
171
+ title: '保存 NapCat 连接并重启?',
172
+ body: '保存连接配置后重启进程。',
173
+ });
174
+ if (!ok) return;
175
+ save.disabled = true;
176
+ try {
177
+ await ctx.invoke('setConnection', [wsUrl, token.value]);
178
+ restartWatch(ctx, '保存连接');
179
+ } catch (err) {
180
+ fail(`保存失败: ${errText(err)}`);
181
+ save.disabled = false;
182
+ }
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // 重启等待
187
+ // ---------------------------------------------------------------------------
188
+
189
+ /** 探活前先让旧进程走完退出窗口(约 350ms),这几拍不问。 */
190
+ const SETTLE_TICKS = 3;
191
+ /** 探活总拍数。超了就换一句"要不要手动看看"的措辞,不再空转。 */
192
+ const MAX_TICKS = 90;
193
+ const TICK_MS = 1000;
194
+
195
+ /**
196
+ * 重启期间显示等待状态,以 ctx.interval 轮询 ctx.invoke("state"),确认本面板服务恢复后整页重载。轮询和请求均随 ctx.signal 取消,离开面板后停止。
197
+ */
198
+ function restartWatch(ctx: ConsolePanelContext, verb: string): void {
199
+ const { ui } = ctx;
200
+ const view = ctx.root.ownerDocument?.defaultView ?? null;
201
+ let notice = ui.drawer(
202
+ `${verb}中`,
203
+ '正在重启,完成后自动刷新页面。',
204
+ );
205
+
206
+ let ticks = 0;
207
+ let probing = false;
208
+ const timer = ctx.interval(() => {
209
+ ticks++;
210
+ if (ticks <= SETTLE_TICKS) return;
211
+ if (ticks > MAX_TICKS) {
212
+ timer.dispose();
213
+ notice.dispose();
214
+ notice = ui.drawer(
215
+ '重启耗时偏长',
216
+ '请手动刷新页面,或检查启动器是否还在运行'
217
+ + '(没用启动器起的进程,退出后不会有人把它拉起来)。',
218
+ );
219
+ return;
220
+ }
221
+ if (probing) return; // 上一拍的探活还没回来,不叠着发
222
+ probing = true;
223
+ void ctx.invoke('state').then(
224
+ () => {
225
+ timer.dispose();
226
+ notice.dispose();
227
+ view?.location.reload();
228
+ },
229
+ () => { probing = false; }, // 还没回来,下一拍再问
230
+ );
231
+ }, TICK_MS);
232
+ }