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.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 持久化最近一次记录死亡的世界、死亡计数与近期死因。
|
|
3
|
+
* 每日计数以部署时区 06:00 为日界;记录不同世界的死亡时重置并覆盖原账本。
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname } from 'node:path';
|
|
7
|
+
import { nowIso } from '../../core/util.ts';
|
|
8
|
+
|
|
9
|
+
/** 死亡时刻、服务端死因原文与坐标;未获取死因时为 null。 */
|
|
10
|
+
interface DeathFact {
|
|
11
|
+
at: string;
|
|
12
|
+
cause: string | null;
|
|
13
|
+
/** `维度:x,y,z`(floor 口径);读不到死亡点为 null */
|
|
14
|
+
cell: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface DeathToll {
|
|
18
|
+
/** 账本属于哪个世界;换世界时整本重置 */
|
|
19
|
+
world: string;
|
|
20
|
+
/** `today` 所属的那一"天"(部署时区,YYYY-MM-DD);06:00 换日,见 dayKey */
|
|
21
|
+
day: string;
|
|
22
|
+
today: number;
|
|
23
|
+
total: number;
|
|
24
|
+
/** 近期死亡记录,用于计算同日同格死亡次数。 */
|
|
25
|
+
lastCauses: DeathFact[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const LAST_CAUSES_CAP = 20;
|
|
30
|
+
|
|
31
|
+
const EMPTY: DeathToll = { world: '', day: '', today: 0, total: 0, lastCauses: [] };
|
|
32
|
+
|
|
33
|
+
const DAY_START_HOUR = 6;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* "今天"的键:部署时区 06:00 换日,凌晨那几个小时算前一天。
|
|
37
|
+
* 日期取自把时刻整体往回拨 6 小时之后的那一天。
|
|
38
|
+
*/
|
|
39
|
+
export function dayKey(timezone: string, at: Date = new Date()): string {
|
|
40
|
+
return nowIso(timezone, new Date(at.getTime() - DAY_START_HOUR * 3_600_000)).slice(0, 10);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 文件不存在、为空或格式错误时使用空记录。 */
|
|
44
|
+
export function loadDeaths(file: string | null): DeathToll {
|
|
45
|
+
if (!file || !existsSync(file)) return { ...EMPTY };
|
|
46
|
+
let data: Partial<DeathToll>;
|
|
47
|
+
try {
|
|
48
|
+
data = JSON.parse(readFileSync(file, 'utf8')) as Partial<DeathToll>;
|
|
49
|
+
} catch {
|
|
50
|
+
return { ...EMPTY };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
world: typeof data.world === 'string' ? data.world : '',
|
|
54
|
+
day: typeof data.day === 'string' ? data.day : '',
|
|
55
|
+
today: typeof data.today === 'number' && data.today > 0 ? Math.floor(data.today) : 0,
|
|
56
|
+
total: typeof data.total === 'number' && data.total > 0 ? Math.floor(data.total) : 0,
|
|
57
|
+
lastCauses: Array.isArray(data.lastCauses)
|
|
58
|
+
? data.lastCauses
|
|
59
|
+
.filter((f): f is DeathFact => !!f && typeof (f as DeathFact).at === 'string')
|
|
60
|
+
.map((f) => ({
|
|
61
|
+
at: f.at,
|
|
62
|
+
cause: typeof f.cause === 'string' ? f.cause : null,
|
|
63
|
+
cell: typeof f.cell === 'string' ? f.cell : null,
|
|
64
|
+
}))
|
|
65
|
+
.slice(-LAST_CAUSES_CAP)
|
|
66
|
+
: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 读数视图:跨过换日点的旧账本今天是 0,不必等下一次死亡才滚。 */
|
|
71
|
+
export function tollOn(toll: DeathToll, day: string): { today: number; total: number } {
|
|
72
|
+
return { today: toll.day === day ? toll.today : 0, total: toll.total };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class DeathBook {
|
|
76
|
+
private toll: DeathToll;
|
|
77
|
+
|
|
78
|
+
constructor(private readonly file: string | null, private readonly timezone: string) {
|
|
79
|
+
this.toll = loadDeaths(file);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 记录死亡后返回计数;世界改变时重置,跨日时 today 归零。
|
|
84
|
+
* hereToday 包含本次死亡;缺少死亡坐标时为 0。
|
|
85
|
+
*/
|
|
86
|
+
record(
|
|
87
|
+
world: string,
|
|
88
|
+
at: Date = new Date(),
|
|
89
|
+
fact?: { cause?: string | null; cell?: string | null },
|
|
90
|
+
): { today: number; total: number; hereToday: number } {
|
|
91
|
+
const day = dayKey(this.timezone, at);
|
|
92
|
+
if (this.toll.world !== world) this.toll = { world, day, today: 0, total: 0, lastCauses: [] };
|
|
93
|
+
if (this.toll.day !== day) this.toll = { ...this.toll, day, today: 0 };
|
|
94
|
+
const cell = fact?.cell ?? null;
|
|
95
|
+
const entry: DeathFact = { at: nowIso(this.timezone, at), cause: fact?.cause ?? null, cell };
|
|
96
|
+
const lastCauses = [...this.toll.lastCauses, entry].slice(-LAST_CAUSES_CAP);
|
|
97
|
+
this.toll = { ...this.toll, today: this.toll.today + 1, total: this.toll.total + 1, lastCauses };
|
|
98
|
+
this.save();
|
|
99
|
+
const hereToday = cell === null
|
|
100
|
+
? 0
|
|
101
|
+
: lastCauses.filter((f) => f.cell === cell && dayKey(this.timezone, new Date(f.at)) === day).length;
|
|
102
|
+
return { today: this.toll.today, total: this.toll.total, hereToday };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 迟到的服务端死因仅补入 15 秒内尚无死因的最近一次死亡。 */
|
|
106
|
+
noteCause(cause: string, at: Date = new Date()): void {
|
|
107
|
+
const last = this.toll.lastCauses[this.toll.lastCauses.length - 1];
|
|
108
|
+
if (!last || last.cause !== null) return;
|
|
109
|
+
if (Math.abs(at.getTime() - new Date(last.at).getTime()) > 15_000) return;
|
|
110
|
+
last.cause = cause;
|
|
111
|
+
this.save();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
count(at: Date = new Date()): { today: number; total: number } {
|
|
115
|
+
return tollOn(this.toll, dayKey(this.timezone, at));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
stat(): string {
|
|
119
|
+
const { today, total } = this.count();
|
|
120
|
+
return total === 0 ? '(没死过)' : `今天 ${today} 次 / 累计 ${total} 次`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
clear(): string {
|
|
124
|
+
const { total } = this.count();
|
|
125
|
+
this.toll = { ...EMPTY, world: this.toll.world };
|
|
126
|
+
this.save();
|
|
127
|
+
return total === 0 ? '死亡账本本来就是空的' : `死亡账本已清空(原有 ${total} 次)`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private save(): void {
|
|
131
|
+
if (!this.file) return;
|
|
132
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
133
|
+
writeFileSync(this.file, `${JSON.stringify(this.toll)}\n`, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import type { WorldDefinition } from '../../world.ts';
|
|
3
|
+
import { MINECRAFT_DEFAULTS, type MinecraftConfigSection } from './config.ts';
|
|
4
|
+
import { MinecraftWorldProxy } from './proxy.ts';
|
|
5
|
+
|
|
6
|
+
export const MINECRAFT: WorldDefinition<MinecraftConfigSection> = {
|
|
7
|
+
id: 'minecraft',
|
|
8
|
+
label: 'Minecraft',
|
|
9
|
+
defaults: () => structuredClone(MINECRAFT_DEFAULTS as unknown as MinecraftConfigSection),
|
|
10
|
+
// The child process isolates Mineflayer physics and pathfinding from the main loop.
|
|
11
|
+
create: (ctx) =>
|
|
12
|
+
new MinecraftWorldProxy({
|
|
13
|
+
cfg: ctx.cfg,
|
|
14
|
+
timezone: ctx.timezone,
|
|
15
|
+
botName: ctx.botName,
|
|
16
|
+
dataDir: ctx.dataDir,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/** Minecraft 引擎子进程入口。创建 World 并通过 IPC 接收配置和工具请求,向主进程转发事件及 Core 请求。 */
|
|
2
|
+
import { createIpcLogger } from '../../core/ipc-logger.ts';
|
|
3
|
+
import { withAnchors } from '../../core/log-context.ts';
|
|
4
|
+
import { MinecraftWorld } from './world.ts';
|
|
5
|
+
import type {
|
|
6
|
+
ChildToMain,
|
|
7
|
+
CognitionReply,
|
|
8
|
+
EngineInit,
|
|
9
|
+
EngineRequest,
|
|
10
|
+
HostRequest,
|
|
11
|
+
MainToChild,
|
|
12
|
+
StorageStat,
|
|
13
|
+
} from './engine-ipc.ts';
|
|
14
|
+
import type {
|
|
15
|
+
EventEnvelope,
|
|
16
|
+
EventStoreReader,
|
|
17
|
+
WorldHost,
|
|
18
|
+
Logger,
|
|
19
|
+
StoragePart,
|
|
20
|
+
DeferredRendered,
|
|
21
|
+
} from '../../core/types.ts';
|
|
22
|
+
|
|
23
|
+
function send(msg: ChildToMain): void {
|
|
24
|
+
process.send?.(msg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const makeLogger = (area: string): Logger => createIpcLogger((note) => send({ t: 'note', note }), area);
|
|
28
|
+
|
|
29
|
+
const log = makeLogger('');
|
|
30
|
+
|
|
31
|
+
const HOST_RPC_TIMEOUT_MS = 10_000;
|
|
32
|
+
/** 认知请求的 RPC 超时为 16 分钟。 */
|
|
33
|
+
const COGNITION_RPC_TIMEOUT_MS = 16 * 60_000;
|
|
34
|
+
let nextHostReqId = 1;
|
|
35
|
+
const pendingHost = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();
|
|
36
|
+
|
|
37
|
+
function hostRpc(req: HostRequest, timeoutMs = HOST_RPC_TIMEOUT_MS): Promise<unknown> {
|
|
38
|
+
const id = nextHostReqId++;
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
pendingHost.delete(id);
|
|
42
|
+
reject(new Error(`主进程 ${timeoutMs / 1000}s 未回执宿主调用(${req.kind})`));
|
|
43
|
+
}, timeoutMs);
|
|
44
|
+
timer.unref?.();
|
|
45
|
+
pendingHost.set(id, { resolve, reject, timer });
|
|
46
|
+
send({ t: 'hreq', id, req });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 同步 Core 接口不能经异步 IPC 调用,使用时抛错。 */
|
|
51
|
+
const unavailable = (what: string): never => {
|
|
52
|
+
throw new Error(`${what} 在子进程宿主里不可用`);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const store: EventStoreReader = {
|
|
56
|
+
get: () => unavailable('store.get'),
|
|
57
|
+
latestCursor: () => unavailable('store.latestCursor'),
|
|
58
|
+
range: () => unavailable('store.range'),
|
|
59
|
+
around: () => unavailable('store.around'),
|
|
60
|
+
grep: () => unavailable('store.grep'),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** 按事件类型保存延迟渲染回调。 */
|
|
64
|
+
const textRender = (render: () => DeferredRendered | null | Promise<DeferredRendered | null>) =>
|
|
65
|
+
async (): Promise<string | null> => {
|
|
66
|
+
const out = await render();
|
|
67
|
+
return out === null || typeof out === 'string' ? out : out.text;
|
|
68
|
+
};
|
|
69
|
+
const deferredRenders = new Map<string, () => Promise<string | null>>();
|
|
70
|
+
|
|
71
|
+
let cognitionOn = false;
|
|
72
|
+
|
|
73
|
+
/** 跨进程请求失败返回含 error 的结果。 */
|
|
74
|
+
const cognitionHost: NonNullable<WorldHost['cognition']> = {
|
|
75
|
+
request: async (req) => {
|
|
76
|
+
try {
|
|
77
|
+
return (await hostRpc({ kind: 'cognition', req }, COGNITION_RPC_TIMEOUT_MS)) as CognitionReply;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return { error: `后台思考没跑成:${e instanceof Error ? e.message : String(e)}` };
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const host: WorldHost = {
|
|
85
|
+
pushEvent: (e, opts) => hostRpc({ kind: 'push', evt: e as Parameters<typeof host.pushEvent>[0] & { blobs?: undefined }, opts }) as Promise<EventEnvelope>,
|
|
86
|
+
pushDeferred: (e, opts) => {
|
|
87
|
+
deferredRenders.set(e.type, textRender(e.render));
|
|
88
|
+
send({
|
|
89
|
+
t: 'note',
|
|
90
|
+
note: {
|
|
91
|
+
kind: 'arm-deferred',
|
|
92
|
+
type: e.type,
|
|
93
|
+
...(e.senderKey !== undefined ? { senderKey: e.senderKey } : {}),
|
|
94
|
+
...(e.meta !== undefined ? { meta: e.meta } : {}),
|
|
95
|
+
...(e.tags !== undefined ? { tags: e.tags } : {}),
|
|
96
|
+
...(opts?.trigger !== undefined ? { trigger: opts.trigger } : {}),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
store,
|
|
101
|
+
drainPendingEvents: () => hostRpc({ kind: 'drain' }) as Promise<EventEnvelope[]>,
|
|
102
|
+
modelFacts: {
|
|
103
|
+
model: () => unavailable('modelFacts.model'),
|
|
104
|
+
accepts: () => unavailable('modelFacts.accepts'),
|
|
105
|
+
contextWindow: () => unavailable('modelFacts.contextWindow'),
|
|
106
|
+
},
|
|
107
|
+
blob: () => unavailable('blob'),
|
|
108
|
+
reportUsage: (usage, opts) => send({ t: 'note', note: { kind: 'usage', usage, opts } }),
|
|
109
|
+
/** 能力随配置消息更新。 */
|
|
110
|
+
get cognition(): WorldHost['cognition'] {
|
|
111
|
+
return cognitionOn ? cognitionHost : undefined;
|
|
112
|
+
},
|
|
113
|
+
log,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let mod: MinecraftWorld | null = null;
|
|
117
|
+
let cfg: EngineInit['cfg'] | null = null;
|
|
118
|
+
let statusTimer: ReturnType<typeof setInterval> | null = null;
|
|
119
|
+
let lastStatus = '';
|
|
120
|
+
|
|
121
|
+
function applyCfg(target: Record<string, unknown>, next: Record<string, unknown>): void {
|
|
122
|
+
for (const [k, v] of Object.entries(next)) {
|
|
123
|
+
const cur = target[k];
|
|
124
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && cur && typeof cur === 'object' && !Array.isArray(cur)) {
|
|
125
|
+
applyCfg(cur as Record<string, unknown>, v as Record<string, unknown>);
|
|
126
|
+
} else {
|
|
127
|
+
target[k] = v;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function storageStats(parts: StoragePart[]): StorageStat[] {
|
|
133
|
+
return parts.map((p) => {
|
|
134
|
+
let stat = '';
|
|
135
|
+
try {
|
|
136
|
+
stat = p.stat();
|
|
137
|
+
} catch {
|
|
138
|
+
stat = '(取不到)';
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
key: p.key,
|
|
142
|
+
label: p.label,
|
|
143
|
+
kind: p.kind,
|
|
144
|
+
location: p.location,
|
|
145
|
+
danger: p.danger,
|
|
146
|
+
note: p.note,
|
|
147
|
+
order: p.order,
|
|
148
|
+
stat,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function pushStatus(): void {
|
|
154
|
+
if (!mod) return;
|
|
155
|
+
const decl = mod.console();
|
|
156
|
+
const note = {
|
|
157
|
+
kind: 'status' as const,
|
|
158
|
+
decl: { lamps: decl.lamps, badges: decl.badges, links: decl.links },
|
|
159
|
+
storage: storageStats(decl.storage ?? []),
|
|
160
|
+
};
|
|
161
|
+
const key = JSON.stringify(note);
|
|
162
|
+
if (key === lastStatus) return;
|
|
163
|
+
lastStatus = key;
|
|
164
|
+
send({ t: 'note', note });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function handleRequest(req: EngineRequest): Promise<unknown> {
|
|
168
|
+
if (req.kind === 'init') {
|
|
169
|
+
if (mod) throw new Error('已经 init 过了');
|
|
170
|
+
cfg = req.init.cfg;
|
|
171
|
+
mod = new MinecraftWorld({
|
|
172
|
+
cfg,
|
|
173
|
+
timezone: req.init.timezone,
|
|
174
|
+
botName: req.init.botName,
|
|
175
|
+
...(req.init.dataDir ? { dataDir: req.init.dataDir } : {}),
|
|
176
|
+
});
|
|
177
|
+
await mod.start(host);
|
|
178
|
+
statusTimer = setInterval(pushStatus, 1000);
|
|
179
|
+
statusTimer.unref?.();
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
if (req.kind === 'shutdown') {
|
|
183
|
+
if (statusTimer) clearInterval(statusTimer);
|
|
184
|
+
statusTimer = null;
|
|
185
|
+
await mod?.stop();
|
|
186
|
+
mod = null;
|
|
187
|
+
setImmediate(() => process.exit(0));
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (req.kind === 'render-deferred') {
|
|
191
|
+
/** 渲染失败返回 null。 */
|
|
192
|
+
const render = deferredRenders.get(req.type);
|
|
193
|
+
return render ? await render() : null;
|
|
194
|
+
}
|
|
195
|
+
if (!mod) throw new Error('Minecraft 引擎还没 init');
|
|
196
|
+
if (req.kind === 'tool') {
|
|
197
|
+
const tool = mod.tools().find((t) => t.name === req.name);
|
|
198
|
+
if (!tool) throw new Error(`未知工具「${req.name}」`);
|
|
199
|
+
return withAnchors({ sess: req.role, ...(req.callId ? { call: req.callId } : {}), ...(req.round !== null ? { round: req.round } : {}) }, () => tool.handler(req.args, {
|
|
200
|
+
role: req.role,
|
|
201
|
+
log,
|
|
202
|
+
...(req.callId ? { callId: req.callId } : {}),
|
|
203
|
+
...(req.round !== null ? { round: req.round } : {}),
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
if (req.kind === 'storage-clear') {
|
|
207
|
+
const part = (mod.console().storage ?? []).find((p) => p.key === req.key);
|
|
208
|
+
if (!part) throw new Error(`未知存储部分: ${req.key}`);
|
|
209
|
+
return part.clear();
|
|
210
|
+
}
|
|
211
|
+
const invoke = mod.console().invoke;
|
|
212
|
+
if (!invoke) throw new Error('World 没有面板调用面');
|
|
213
|
+
return invoke(req.panel, req.method, req.args);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
process.on('message', (msg: MainToChild) => {
|
|
217
|
+
if (msg.t === 'req') {
|
|
218
|
+
void Promise.resolve()
|
|
219
|
+
.then(() => handleRequest(msg.req))
|
|
220
|
+
.then(
|
|
221
|
+
(value) => send({ t: 'rep', id: msg.id, ok: true, value }),
|
|
222
|
+
(err: unknown) =>
|
|
223
|
+
send({ t: 'rep', id: msg.id, ok: false, error: err instanceof Error ? err.message : String(err) }),
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (msg.t === 'hrep') {
|
|
228
|
+
const p = pendingHost.get(msg.id);
|
|
229
|
+
if (!p) return;
|
|
230
|
+
pendingHost.delete(msg.id);
|
|
231
|
+
clearTimeout(p.timer);
|
|
232
|
+
if (msg.ok) p.resolve(msg.value);
|
|
233
|
+
else p.reject(new Error(msg.error ?? '宿主调用失败'));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const cast = msg.cast;
|
|
237
|
+
if (cast.kind === 'caps') {
|
|
238
|
+
cognitionOn = cast.cognition;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (cast.kind === 'config' && cfg) {
|
|
242
|
+
applyCfg(cfg as unknown as Record<string, unknown>, cast.cfg as unknown as Record<string, unknown>);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
/** 父进程关闭时停止 World 及其管理的进程。 */
|
|
247
|
+
process.on('disconnect', () => {
|
|
248
|
+
const m = mod;
|
|
249
|
+
if (!m) {
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
void m.stop().finally(() => process.exit(0));
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/** 未处理的 Promise 拒绝只记录日志。 */
|
|
256
|
+
process.on('unhandledRejection', (reason) => {
|
|
257
|
+
log.emit('error', '引擎子进程未处理的 Promise 拒绝(已忽略)', { event: 'unhandled-rejection', err: reason });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/** 致命异常先尝试停止 World;8 秒后强制退出。 */
|
|
261
|
+
const FATAL_DRAIN_MS = 8_000;
|
|
262
|
+
let dying = false;
|
|
263
|
+
process.on('uncaughtException', (err) => {
|
|
264
|
+
log.emit('error', '引擎子进程未捕获异常', { event: 'uncaught-exception', err });
|
|
265
|
+
if (dying) return;
|
|
266
|
+
dying = true;
|
|
267
|
+
const m = mod;
|
|
268
|
+
mod = null;
|
|
269
|
+
if (!m) { process.exit(1); return; }
|
|
270
|
+
log.emit('warn', '引擎子进程收尾中(给服务端存档 8 秒)', { event: 'fatal-drain' });
|
|
271
|
+
const hard = setTimeout(() => process.exit(1), FATAL_DRAIN_MS);
|
|
272
|
+
hard.unref?.();
|
|
273
|
+
void m.stop().catch(() => undefined).finally(() => { clearTimeout(hard); process.exit(1); });
|
|
274
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minecraft 引擎子进程运行 Mineflayer、寻路、执行器和 World 心跳。
|
|
3
|
+
* 主进程转发工具、面板及宿主调用;子进程上报日志、事件和状态缓存。
|
|
4
|
+
*/
|
|
5
|
+
import type { LogNote } from '../../core/ipc-logger.ts';
|
|
6
|
+
import type {
|
|
7
|
+
CognitionRequest, CognitionResult, EventEnvelope, EventTag, LLMUsage, WorldConsoleDecl,
|
|
8
|
+
PushOptions, TriggerMode,
|
|
9
|
+
} from '../../core/types.ts';
|
|
10
|
+
import type { MinecraftConfigSection } from './config.ts';
|
|
11
|
+
|
|
12
|
+
/** 构造引擎子进程 World 的一次性参数。 */
|
|
13
|
+
export interface EngineInit {
|
|
14
|
+
timezone: string;
|
|
15
|
+
botName: string;
|
|
16
|
+
/** 账本目录 data/;null = 只在内存 */
|
|
17
|
+
dataDir: string | null;
|
|
18
|
+
cfg: MinecraftConfigSection;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 主 → 子:要回执的请求 */
|
|
22
|
+
export type EngineRequest =
|
|
23
|
+
| { kind: 'init'; init: EngineInit }
|
|
24
|
+
/** 主进程传入的轮序号;未知时为 null。 */
|
|
25
|
+
| {
|
|
26
|
+
kind: 'tool'; name: string; args: Record<string, unknown>; role: string;
|
|
27
|
+
callId: string | null; round: number | null;
|
|
28
|
+
}
|
|
29
|
+
| { kind: 'panel'; panel: string; method: string; args: unknown[] }
|
|
30
|
+
| { kind: 'storage-clear'; key: string }
|
|
31
|
+
/** 投递时调用子进程登记的渲染回调;null 表示不生成事件正文。 */
|
|
32
|
+
| { kind: 'render-deferred'; type: string }
|
|
33
|
+
| { kind: 'shutdown' };
|
|
34
|
+
|
|
35
|
+
/** 主 → 子:单向投递 */
|
|
36
|
+
export type EngineCast =
|
|
37
|
+
/** x-hot 配置快照(整段替换值,不换对象身份) */
|
|
38
|
+
| { kind: 'config'; cfg: MinecraftConfigSection }
|
|
39
|
+
/** 主进程可选宿主能力的当前状态;子进程据此提供或移除对应句柄。 */
|
|
40
|
+
| { kind: 'caps'; cognition: boolean };
|
|
41
|
+
|
|
42
|
+
/** StoragePart 的可序列化描述(stat 现值随状态推送,clear 走请求) */
|
|
43
|
+
export interface StorageStat {
|
|
44
|
+
key: string;
|
|
45
|
+
label: string;
|
|
46
|
+
kind: 'disk' | 'memory';
|
|
47
|
+
location?: string;
|
|
48
|
+
danger?: boolean;
|
|
49
|
+
note?: string;
|
|
50
|
+
order?: number;
|
|
51
|
+
stat: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 子 → 主:单向通知 */
|
|
55
|
+
export type EngineNote =
|
|
56
|
+
| LogNote
|
|
57
|
+
| { kind: 'usage'; usage: LLMUsage; opts?: Parameters<import('../../core/types.ts').WorldHost['reportUsage']>[1] }
|
|
58
|
+
/** 渲染回调保存在子进程;主进程登记待投递项,投递时发送 render-deferred 请求。 */
|
|
59
|
+
| {
|
|
60
|
+
kind: 'arm-deferred';
|
|
61
|
+
type: string;
|
|
62
|
+
senderKey?: string;
|
|
63
|
+
meta?: Record<string, unknown>;
|
|
64
|
+
tags?: readonly EventTag[];
|
|
65
|
+
trigger?: TriggerMode;
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
kind: 'status';
|
|
69
|
+
decl: Pick<WorldConsoleDecl, 'lamps' | 'badges' | 'links'>;
|
|
70
|
+
storage: StorageStat[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** 子进程调用宿主并等待结果。跨进程 drain 仅支持读取本 World 来源的事件。 */
|
|
74
|
+
export type HostRequest =
|
|
75
|
+
| {
|
|
76
|
+
kind: 'push';
|
|
77
|
+
evt: Omit<EventEnvelope, 'cursor' | 'origin'> & { origin?: EventEnvelope['origin'] };
|
|
78
|
+
opts?: PushOptions;
|
|
79
|
+
}
|
|
80
|
+
| { kind: 'drain' }
|
|
81
|
+
/**
|
|
82
|
+
* 主进程按 World 声明的工具名校验 req.tools。
|
|
83
|
+
* 宿主能力不可用时,error 以 COGNITION_ABSENT 开头。
|
|
84
|
+
*/
|
|
85
|
+
| { kind: 'cognition'; req: CognitionRequest };
|
|
86
|
+
|
|
87
|
+
/** host.cognition.request 的跨进程返回值。 */
|
|
88
|
+
export type CognitionReply = CognitionResult;
|
|
89
|
+
|
|
90
|
+
export type MainToChild =
|
|
91
|
+
| { t: 'req'; id: number; req: EngineRequest }
|
|
92
|
+
| { t: 'hrep'; id: number; ok: boolean; value?: unknown; error?: string }
|
|
93
|
+
| { t: 'cast'; cast: EngineCast };
|
|
94
|
+
|
|
95
|
+
export type ChildToMain =
|
|
96
|
+
| { t: 'rep'; id: number; ok: boolean; value?: unknown; error?: string }
|
|
97
|
+
| { t: 'hreq'; id: number; req: HostRequest }
|
|
98
|
+
| { t: 'note'; note: EngineNote };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** 实体元数据字段位置取自 registry.entitiesByName 的 metadataKeys。 */
|
|
2
|
+
|
|
3
|
+
interface NamedStack { name?: string }
|
|
4
|
+
|
|
5
|
+
interface FactEntity {
|
|
6
|
+
name?: string;
|
|
7
|
+
metadata?: unknown[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface FactBot {
|
|
11
|
+
entity?: { uuid?: string } | null;
|
|
12
|
+
/** 登录时玩家 UUID 优先取 bot.player,其次取实体或协议客户端。 */
|
|
13
|
+
player?: { uuid?: string } | null;
|
|
14
|
+
_client?: { uuid?: string } | null;
|
|
15
|
+
inventory?: { slots?: Array<NamedStack | null | undefined> };
|
|
16
|
+
registry?: {
|
|
17
|
+
entitiesByName?: Record<string, { metadataKeys?: string[] } | undefined>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function metaOf(bot: FactBot, entity: FactEntity, key: string): unknown {
|
|
22
|
+
const name = entity.name;
|
|
23
|
+
if (!name) return undefined;
|
|
24
|
+
const keys = bot.registry?.entitiesByName?.[name]?.metadataKeys ?? [];
|
|
25
|
+
const at = keys.indexOf(key);
|
|
26
|
+
return at >= 0 ? entity.metadata?.[at] : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 可驯服实体与对应道具。消耗道具只能证明使用发生,驯服结果须读取主人元数据。 */
|
|
30
|
+
export const TAME_ITEMS: Readonly<Record<string, readonly string[]>> = {
|
|
31
|
+
wolf: ['bone'],
|
|
32
|
+
cat: ['cod', 'salmon'],
|
|
33
|
+
ocelot: ['cod', 'salmon'],
|
|
34
|
+
parrot: ['wheat_seeds', 'beetroot_seeds', 'melon_seeds', 'pumpkin_seeds', 'torchflower_seeds', 'pitcher_pod'],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** 繁殖交互使用的实体与食物表;物品消耗不作为幼体出生的证明。 */
|
|
38
|
+
export const FEED_ITEMS: Readonly<Record<string, readonly string[]>> = {
|
|
39
|
+
cow: ['wheat'],
|
|
40
|
+
mooshroom: ['wheat'],
|
|
41
|
+
sheep: ['wheat'],
|
|
42
|
+
goat: ['wheat'],
|
|
43
|
+
pig: ['carrot', 'potato', 'beetroot'],
|
|
44
|
+
rabbit: ['carrot', 'golden_carrot', 'dandelion'],
|
|
45
|
+
chicken: ['wheat_seeds', 'beetroot_seeds', 'melon_seeds', 'pumpkin_seeds', 'torchflower_seeds', 'pitcher_pod'],
|
|
46
|
+
// 1.20.5 起狼的食物是 #wolf_food 物品标签:全部生熟肉(含腐肉)+全部鱼类
|
|
47
|
+
wolf: ['beef', 'porkchop', 'chicken', 'mutton', 'rabbit', 'cooked_beef', 'cooked_porkchop', 'cooked_chicken', 'cooked_mutton', 'cooked_rabbit', 'rotten_flesh', 'cod', 'salmon', 'cooked_cod', 'cooked_salmon', 'tropical_fish', 'pufferfish'],
|
|
48
|
+
cat: ['cod', 'salmon', 'tropical_fish', 'pufferfish'],
|
|
49
|
+
horse: ['golden_carrot', 'golden_apple', 'hay_block'],
|
|
50
|
+
donkey: ['golden_carrot', 'golden_apple', 'hay_block'],
|
|
51
|
+
llama: ['hay_block'],
|
|
52
|
+
armadillo: ['spider_eye'],
|
|
53
|
+
panda: ['bamboo'],
|
|
54
|
+
turtle: ['seagrass'],
|
|
55
|
+
fox: ['sweet_berries', 'glow_berries'],
|
|
56
|
+
bee: ['dandelion', 'poppy', 'sunflower'],
|
|
57
|
+
frog: ['slime_ball'],
|
|
58
|
+
sniffer: ['torchflower_seeds'],
|
|
59
|
+
camel: ['cactus'],
|
|
60
|
+
strider: ['warped_fungus'],
|
|
61
|
+
hoglin: ['crimson_fungus'],
|
|
62
|
+
axolotl: ['tropical_fish_bucket'],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** 这只活物现在的主人 UUID;没被驯服/读不到元数据返回 null。 */
|
|
66
|
+
export function readTamedBy(bot: FactBot, entity: FactEntity): string | null {
|
|
67
|
+
const raw = metaOf(bot, entity, 'owneruuid');
|
|
68
|
+
// 协议把"没有主人"编成缺席的 optional;不同版本的 protodef 分别给 undefined/null/''
|
|
69
|
+
if (raw === undefined || raw === null || raw === '') return null;
|
|
70
|
+
if (typeof raw === 'string') return raw;
|
|
71
|
+
// OptionalUUID 有时解成 { present, value } 或者裸的 UUID 对象
|
|
72
|
+
const obj = raw as { value?: unknown; present?: unknown };
|
|
73
|
+
if (obj.present === false) return null;
|
|
74
|
+
const inner = obj.value ?? raw;
|
|
75
|
+
return typeof inner === 'string' && inner !== '' ? inner : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 主人 UUID 与当前玩家一致时返回 true;缺少任一身份时返回 false。
|
|
80
|
+
* 登录阶段 bot.entity.uuid 可能缺失,优先使用 bot.player.uuid,并保留实体与协议客户端来源。
|
|
81
|
+
*/
|
|
82
|
+
export function tamedByMe(bot: FactBot, entity: FactEntity): boolean {
|
|
83
|
+
const owner = readTamedBy(bot, entity);
|
|
84
|
+
const me = bot.player?.uuid ?? bot.entity?.uuid ?? bot._client?.uuid;
|
|
85
|
+
return owner !== null && typeof me === 'string' && me !== '' && owner === me;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** TamableAnimal flags 的 0x01 位表示坐下;缺少该字段时返回 null。 */
|
|
89
|
+
export function readSitting(bot: FactBot, entity: FactEntity): boolean | null {
|
|
90
|
+
const raw = metaOf(bot, entity, 'flags');
|
|
91
|
+
if (typeof raw !== 'number') return null;
|
|
92
|
+
return (raw & 0x01) !== 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 猪和炽足兽读取 saddle;马科读取 flags 的 0x04 位。字段不可用时返回 null。 */
|
|
96
|
+
export function readSaddled(bot: FactBot, entity: FactEntity): boolean | null {
|
|
97
|
+
const direct = metaOf(bot, entity, 'saddle');
|
|
98
|
+
if (typeof direct === 'boolean') return direct;
|
|
99
|
+
if (typeof direct === 'number') return direct !== 0;
|
|
100
|
+
if (entity.name === 'horse' || entity.name === 'donkey' || entity.name === 'mule') {
|
|
101
|
+
const flags = metaOf(bot, entity, 'flags');
|
|
102
|
+
return typeof flags === 'number' ? (flags & 0x04) !== 0 : null;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 马科的「驯服」位:'flags' 的 0x02。不是马科或读不到返回 null。 */
|
|
108
|
+
export function readHorseTamed(bot: FactBot, entity: FactEntity): boolean | null {
|
|
109
|
+
if (entity.name !== 'horse' && entity.name !== 'donkey' && entity.name !== 'mule') return null;
|
|
110
|
+
const flags = metaOf(bot, entity, 'flags');
|
|
111
|
+
return typeof flags === 'number' ? (flags & 0x02) !== 0 : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 羊毛元数据的低四位是颜色,第五位(0x10)是"剪过了"。 */
|
|
115
|
+
const WOOL_COLORS = [
|
|
116
|
+
'white', 'orange', 'magenta', 'light_blue', 'yellow', 'lime', 'pink', 'gray',
|
|
117
|
+
'light_gray', 'cyan', 'purple', 'blue', 'brown', 'green', 'red', 'black',
|
|
118
|
+
] as const;
|
|
119
|
+
|
|
120
|
+
/** 染料 item 名 → 颜色名;不是染料返回 null。 */
|
|
121
|
+
export function dyeColorOf(item: string | null | undefined): string | null {
|
|
122
|
+
if (!item || !item.endsWith('_dye')) return null;
|
|
123
|
+
const color = item.slice(0, -'_dye'.length);
|
|
124
|
+
return (WOOL_COLORS as readonly string[]).includes(color) ? color : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 这只羊现在的颜色;读不到返回 null。 */
|
|
128
|
+
export function readSheepColor(bot: FactBot, entity: FactEntity): string | null {
|
|
129
|
+
const raw = metaOf(bot, entity, 'wool');
|
|
130
|
+
if (typeof raw !== 'number') return null;
|
|
131
|
+
return WOOL_COLORS[raw & 0x0f] ?? null;
|
|
132
|
+
}
|