cortico 0.1.0 → 0.1.2

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 (112) hide show
  1. package/README.md +7 -14
  2. package/package.json +1 -3
  3. package/src/boot.ts +18 -0
  4. package/src/bot.ts +41 -6
  5. package/src/core/README.md +4 -6
  6. package/src/core/blobs.ts +23 -2
  7. package/src/core/config-schema.ts +1 -1
  8. package/src/core/config.ts +2 -16
  9. package/src/core/core.ts +15 -5
  10. package/src/core/loop.ts +55 -27
  11. package/src/core/session.ts +3 -2
  12. package/src/core/timers.ts +8 -6
  13. package/src/core/types.ts +19 -3
  14. package/src/core/util.ts +17 -1
  15. package/src/deploy.ts +4 -1
  16. package/src/extensions/README.md +6 -4
  17. package/src/extensions/dry-mount.ts +23 -2
  18. package/src/extensions/manifest.ts +21 -11
  19. package/src/extensions.ts +147 -23
  20. package/src/launcher.ts +23 -9
  21. package/src/protocol/open-responses/context-log.ts +37 -9
  22. package/src/providers/README.md +32 -8
  23. package/src/providers/base.ts +3 -1
  24. package/src/providers/configuration.ts +2 -1
  25. package/src/providers/console/hub.ts +270 -0
  26. package/src/providers/console/settings.ts +8 -18
  27. package/src/providers/console/types.ts +5 -0
  28. package/src/providers/hub-api.ts +3 -0
  29. package/src/providers/llamacpp/config.ts +8 -6
  30. package/src/providers/llamacpp/console/runtime-panel.ts +5 -1
  31. package/src/providers/llamacpp/console/server.ts +3 -1
  32. package/src/providers/llamacpp/index.ts +3 -1
  33. package/src/providers/llamacpp/native.ts +10 -5
  34. package/src/providers/llamacpp/strings.ts +6 -6
  35. package/src/providers/name.ts +8 -0
  36. package/src/providers/openai-responses-compat/config.ts +13 -0
  37. package/src/providers/openai-responses-compat/console/client.ts +5 -0
  38. package/src/providers/openai-responses-compat/console/reasoning-panel.ts +84 -0
  39. package/src/providers/openai-responses-compat/console/server.ts +127 -0
  40. package/src/providers/openai-responses-compat/index.ts +38 -13
  41. package/src/providers/openai-responses-compat/native.ts +9 -4
  42. package/src/providers/openai-responses-compat/strings.ts +62 -1
  43. package/src/providers/registry.ts +5 -0
  44. package/src/providers/transport/responses-input.ts +59 -10
  45. package/src/web/README.md +11 -6
  46. package/src/web/auth.ts +80 -0
  47. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +5 -4
  48. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +22 -8
  49. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +6 -8
  50. package/src/web/client/console-pages/host.ts +24 -7
  51. package/src/web/client/core/api.ts +5 -1
  52. package/src/web/client/core/router.ts +15 -0
  53. package/src/web/client/features/core/strings.ts +2 -2
  54. package/src/web/client/features/extensions/index.ts +276 -41
  55. package/src/web/client/features/extensions/strings.ts +84 -10
  56. package/src/web/client/features/feature.ts +2 -2
  57. package/src/web/client/features/live/diagnostics.ts +32 -0
  58. package/src/web/client/features/live/index.ts +34 -11
  59. package/src/web/client/features/live/onboarding.ts +4 -1
  60. package/src/web/client/features/live/protocol.ts +1 -0
  61. package/src/web/client/features/live/strings.ts +20 -14
  62. package/src/web/client/features/live/timeline.ts +2 -1
  63. package/src/web/client/features/providers/detail.ts +262 -0
  64. package/src/web/client/features/providers/drafts.ts +23 -0
  65. package/src/web/client/features/providers/index.ts +173 -87
  66. package/src/web/client/features/providers/strings.ts +44 -17
  67. package/src/web/client/features/providers/types.ts +15 -0
  68. package/src/web/client/features/settings/general.ts +13 -0
  69. package/src/web/client/features/settings/index.ts +1 -0
  70. package/src/web/client/features/settings/strings.ts +6 -0
  71. package/src/web/client/features/worlds/index.ts +1 -0
  72. package/src/web/client/main.ts +4 -0
  73. package/src/web/client/shell/index.ts +28 -48
  74. package/src/web/client/shell/strings.ts +0 -22
  75. package/src/web/client/ui/icons.ts +14 -1
  76. package/src/web/client/ui/prompt-input.tsx +17 -5
  77. package/src/web/client/ui/strings.ts +0 -2
  78. package/src/web/console-pages.ts +1 -1
  79. package/src/web/diagnostics.ts +133 -0
  80. package/src/web/public/login.html +67 -0
  81. package/src/web/public/styles.css +143 -26
  82. package/src/web/server.ts +233 -19
  83. package/src/web/shared/client-panel.ts +4 -1
  84. package/src/web/shared/console-protocol.ts +4 -1
  85. package/src/worlds/bilibili/README.md +1 -1
  86. package/src/worlds/bilibili/overlay/server.ts +4 -2
  87. package/src/worlds/minecraft/ADAPT.md +66 -0
  88. package/src/worlds/minecraft/README.md +69 -11
  89. package/src/worlds/minecraft/cell-facts.ts +226 -0
  90. package/src/worlds/minecraft/chests.ts +5 -0
  91. package/src/worlds/minecraft/containers.ts +325 -0
  92. package/src/worlds/minecraft/entity-facts.ts +31 -5
  93. package/src/worlds/minecraft/executor.ts +283 -10348
  94. package/src/worlds/minecraft/inventory.ts +268 -0
  95. package/src/worlds/minecraft/melee.ts +419 -0
  96. package/src/worlds/minecraft/mineflayer-fixes.ts +55 -1
  97. package/src/worlds/minecraft/placed-ledger.ts +152 -0
  98. package/src/worlds/minecraft/placement.ts +1038 -0
  99. package/src/worlds/minecraft/receipt.ts +340 -0
  100. package/src/worlds/minecraft/skill-context.ts +358 -0
  101. package/src/worlds/minecraft/skills-build.ts +1169 -0
  102. package/src/worlds/minecraft/skills-container.ts +1343 -0
  103. package/src/worlds/minecraft/skills-craft.ts +333 -0
  104. package/src/worlds/minecraft/skills-dig.ts +624 -0
  105. package/src/worlds/minecraft/skills-gather.ts +1230 -0
  106. package/src/worlds/minecraft/skills-interact.ts +1559 -0
  107. package/src/worlds/minecraft/tools.ts +331 -0
  108. package/src/worlds/minecraft/travel.ts +763 -0
  109. package/src/worlds/minecraft/until.ts +75 -0
  110. package/src/worlds/qq/normalize.ts +14 -0
  111. package/src/worlds/qq/world.ts +53 -7
  112. package/src/worlds/terminal/world.ts +5 -3
@@ -0,0 +1,358 @@
1
+ /**
2
+ * 技能执行的共享面:执行上下文、两种终态异常,以及 World 交给技能的只读取用口。
3
+ *
4
+ * 这里只有契约,没有技能逻辑:除 pathfinder 的目标类型外一律只取类型。
5
+ * 执行器与各技能族都依赖它,它反过来不依赖两者中的任何一个。
6
+ */
7
+ import type { Bot } from 'mineflayer';
8
+ import pathfinderPkg from 'mineflayer-pathfinder';
9
+ import type { Logger } from '../../core/types.ts';
10
+ import type { ChestBook } from './chests.ts';
11
+ import type { WorksBook } from './works.ts';
12
+ import type { MinecraftLog } from './log.ts';
13
+ import type { Direction } from './terrain.ts';
14
+ import type { SkillCall } from './skills.ts';
15
+ import type { PolicyDefaults, PolicySettings } from './policy.ts';
16
+ import type { NormalizedBlueprint, PositionXYZ } from './blueprint.ts';
17
+ import type { BlueprintPlan, ItemTally } from './blueprint-plan.ts';
18
+ import type { BowShotResult, RangedTarget } from './ranged.ts';
19
+ import type { ShowTempo } from './show.ts';
20
+ import type { FindObservationCache, SearchScope } from './search-observation.ts';
21
+
22
+ const { goals } = pathfinderPkg;
23
+
24
+ /** 一份代价配置下的试算结果:寻路器自己就是地形扫描器 */
25
+ export interface RouteProbe {
26
+ profile: 'style' | 'dig' | 'walk';
27
+ status: 'complete' | 'partial' | 'noPath' | 'timeout';
28
+ steps: number;
29
+ /** 要垫的方块数 */
30
+ place: number;
31
+ /** 要挖的方块数 */
32
+ breaks: number;
33
+ /** 这条路(或部分路)尽头离目标还有几格 */
34
+ endDist: number;
35
+ /**
36
+ * A* closed set 大小;未运行试算时为 undefined。
37
+ * 1 表示只展开起点,没有生成可行邻居。
38
+ */
39
+ visited?: number;
40
+ }
41
+
42
+ /**
43
+ * 目标点分诊:A* 对"目标本身进不去"只会以 timeout 收场,10 格外的树冠会被
44
+ * 说成"太远或太绕"。落脚预检与死角灌水(world.ts 纯函数)给出定性结论。
45
+ */
46
+ export type TargetDiag =
47
+ | { kind: 'open' }
48
+ | { kind: 'noStand' }
49
+ | { kind: 'sealed'; size: number };
50
+
51
+ /** 技能受阻是业务终态;scene 携带受阻时的坐标、可见性与试算事实,随回执返回。 */
52
+ export class SkillBlocked extends Error {
53
+ readonly scene: string[];
54
+ /**
55
+ * 受阻来源:server 表示操作后的回读或服务端结果,local 表示本地前置判断。
56
+ * 仅写入 World 诊断日志。
57
+ */
58
+ readonly source: 'server' | 'local';
59
+ constructor(message: string, scene: string[] = [], source: 'server' | 'local' = 'local') {
60
+ super(message);
61
+ this.scene = scene;
62
+ this.source = source;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 条件不成立且没有可作用对象时的无操作终态,不计失败。
68
+ * 继承 SkillBlocked 以共用捕获路径,需要区分终态的调用方再单独判断。
69
+ */
70
+ export class SkillNoop extends SkillBlocked {}
71
+
72
+ /**
73
+ * 一张已装载的蓝图,连同它在**这个世界**里的施工绑定。
74
+ * 设计跨世界(她画的图不属于某个存档),锚点与游标只在这个世界里算数。
75
+ */
76
+ export interface BlueprintSite {
77
+ key: string;
78
+ name: string | null;
79
+ blueprint: NormalizedBlueprint;
80
+ plan: BlueprintPlan;
81
+ /** 蓝图 [0,0,0] 落在世界的哪一格;这个世界里还没开工时 null */
82
+ anchor: PositionXYZ | null;
83
+ /** 已完成的 IR 步数(从头连着那些) */
84
+ cursor: number;
85
+ /** 改造模式的最近一次初探;没有或新建模式为 null。 */
86
+ survey?: BlueprintSurvey | null;
87
+ /** null 表示只完成了初探,尚未动工;旧施工面可省略。 */
88
+ startedAt?: number | null;
89
+ }
90
+
91
+ export interface BlueprintSurvey {
92
+ at: number;
93
+ matched: number;
94
+ missing: number;
95
+ unknown: number;
96
+ wrongBlock: number;
97
+ shouldBeAir: number;
98
+ samples: string[];
99
+ }
100
+
101
+ /**
102
+ * 蓝图施工面。装载表与缓存归 World 持有(world.ts 的 BlueprintBook),执行器只经
103
+ * 这个口取用 —— 与 `spawnAnchor` 同一种接法:World 是唯一写入口,技能只读,
104
+ * 外加一个纯机械的游标回写(进度变化**不是**语义写,不触发任何过夜提醒)。
105
+ */
106
+ export interface BlueprintDesk {
107
+ get(key: string): BlueprintSite | null;
108
+ keys(): string[];
109
+ /** 采集搭车认的那一张:被目标点名的,或者一共就装载了一张;说不清时 null */
110
+ activeKey(): string | null;
111
+ /** 首次开工登记锚点(realm 域) */
112
+ bind(key: string, anchor: PositionXYZ): void;
113
+ /** 改造模式的初探结果与锚点。 */
114
+ survey(key: string, anchor: PositionXYZ, result: BlueprintSurvey): void;
115
+ /** 进度游标回写 */
116
+ progress(key: string, cursor: number): void;
117
+ /** 三分账单的「在箱」一栏(容器账本合计;口径是「上次看见」) */
118
+ stored(): ItemTally;
119
+ }
120
+
121
+ /**
122
+ * 路标取用面(World 持有 mc_map 那张表;执行器只读)。
123
+ *
124
+ * 它只喂**回执侧的机械计算**:把一个裸坐标相对化成她认得的地方,把"这一单的目标
125
+ * 落进了她自己圈的危险区"当场陈述一句。两件事都不改变任何执行 —— 走不走、盖不盖
126
+ * 是她的权衡,系统只出事实(PWSR 主客观纪律)。
127
+ */
128
+ export interface MarkDesk {
129
+ /** 「离「新家」82 格」;`approx` 用于上界估算(「约 130 格」)。没有近处路标时 null */
130
+ near(pos: { x: number; y: number; z: number }, approx?: boolean): string | null;
131
+ /** 离这一点最近的那处路标本体:同一处路标的两个距离要对得起来时用它 */
132
+ nearest(pos: { x: number; y: number; z: number }): { name: string; x: number; y: number; z: number } | null;
133
+ /** 这一格落进的、**她自己标的**危险区名字;没有则空数组 */
134
+ danger(pos: { x: number; y: number; z: number }): string[];
135
+ /** `radius` 格以内那几处路标的名字与登记时刻;没有则空数组 */
136
+ around(pos: { x: number; y: number; z: number }, radius: number): Array<{ name: string; at: number }>;
137
+ }
138
+
139
+ /**
140
+ * 危险区那一句。**措辞铁律(PWSR 主客观纪律):** 参照系永远归她 ——
141
+ * 「你标记的危险区」是事实,「这里危险」「建议绕开」是系统在替她判断。
142
+ * 只陈述,不拦不劝:圈是她画的,她比系统更清楚圈里为什么危险、这一趟值不值。
143
+ */
144
+ export function dangerNoteText(names: readonly string[], what: string): string | null {
145
+ if (names.length === 0) return null;
146
+ return `${what}落在你标记的危险区${names.map((n) => `「${n}」`).join('、')}里`;
147
+ }
148
+
149
+ export type ResourcePlacementPermit =
150
+ | { ok: true; finish(placed: boolean): void }
151
+ | { ok: false; reason: string };
152
+
153
+ export type ResourcePlacementGate = (item: string) => ResourcePlacementPermit;
154
+
155
+ /** 试算用的只读判据:不占串行闸、不扣账,因此也没有 finish */
156
+ export type ResourcePlacementPreview = (item: string) => { ok: boolean; reason?: string };
157
+
158
+ export interface SkillContext {
159
+ aborted: () => boolean;
160
+ /** 谁把这一步打飞的(战斗/反射/mc_stop/顶替);没被打飞时 null */
161
+ abortedBy?: () => string | null;
162
+ log: Logger;
163
+ /** 战斗中生命跌破此值就收手撤退;0 = 不撤 */
164
+ fleeHealth: () => number;
165
+ /** 技能正在脱身(flee/surface/战斗撤退)时置 true:反射不抢占正在逃的任务 */
166
+ escape: { active: boolean };
167
+ /** 主动攻击独占身体与弓租约;被动战斗层只观察,不重复接管。 */
168
+ attack: {
169
+ acquire(targetId: number): TaskAttackLease;
170
+ release(lease: TaskAttackLease): void;
171
+ ranged?: TaskRangedActions;
172
+ };
173
+ /** World 日志;技能内部的判断过程记在这里 */
174
+ diag?: MinecraftLog;
175
+ /** 这一步属于哪个任务 */
176
+ taskId: number;
177
+ /** 计数类技能(collect/build/excavate/tunnel)每完成一个单位报一次;执行器据此出进度事件 */
178
+ progress?: (done: number, total: number) => void;
179
+ /** 常驻规矩(mc_policy;World 持有并落盘)。技能只读它,改由工具面走 */
180
+ policy?: {
181
+ get(): PolicySettings;
182
+ /** 她没设过名单时用哪一份;与寻路器的垫脚名单同源(见 world.ts 的接线注释) */
183
+ defaults(): PolicyDefaults;
184
+ };
185
+ /** 普通放置的材料许可;成功与否必须在同一 permit 上结算。 */
186
+ permitResourcePlacement?: ResourcePlacementGate;
187
+ /** 试算的材料判据;不取 permit,免得预览文案说出结算期的话 */
188
+ previewResourcePlacement?: ResourcePlacementPreview;
189
+ /**
190
+ * 这一步里被迫动用的、`reserve` 收着的家伙什。收着的那把是唯一挖得出掉落时的例外,
191
+ * 由 `reserveNote` 摘成回执里的一句 —— 「策略被事实覆盖」必须看得见。
192
+ */
193
+ reserveHits?: ReserveHit[];
194
+ /** 当前步骤的工具选择摘要;同一把连续使用只记一次。 */
195
+ toolTrace?: ToolTrace;
196
+ /**
197
+ * 以三份代价配置试算路径,未连接时返回 null。
198
+ * 试算与执行必须使用同一目标;水平寻路须显式传入 GoalNearXZ,省略时使用以 target 为中心、半径 1 的 GoalNear。
199
+ */
200
+ probeRoutes?: (
201
+ target: { x: number; y: number; z: number },
202
+ goal?: InstanceType<typeof goals.Goal>,
203
+ ) => RouteProbe[] | null;
204
+ /** 目标点分诊(落脚预检+死角灌水);没连上服务器时 null */
205
+ probeTarget?: (target: { x: number; y: number; z: number }) => TargetDiag | null;
206
+ /**
207
+ * 零位移探针里执行器自己看不见的那几格(战斗、环境 owner、队列冻结、断点)。
208
+ * 只读,不改任何动作;不接 = 那几格记 null(台架)。
209
+ */
210
+ bodyState?: () => BodyStateProbe;
211
+ /** `ts` 之后进「暂时挖不动」退避的格子(桥持有);受阻回执据此说清绕开了哪儿 */
212
+ digBackoffSince?: (ts: number) => Array<{ x: number; y: number; z: number }>;
213
+ /** 容器账本(World 持有,跨任务):箱子内容、炉子槽位与到期、自备工作站的来历 */
214
+ chests?: ChestBook;
215
+ /**
216
+ * 成果登记(World 持有,跨任务跨场次):她做成的蓝图格、耕地、作物。
217
+ * 技能只往里记与读事实,不据此阻断任何动作。
218
+ */
219
+ works?: WorksBook;
220
+ /** 挂钟时刻 HH:MM:SS(与回执同一时区);账本里的 placedAt/到期估计都用它渲染 */
221
+ clock?: (ms: number) => string;
222
+ /** probe 差分的单槽记忆(执行器持有,跨任务;mc_stop 不清,重启清) */
223
+ probeMemo?: { last: ProbeMemo | null };
224
+ /** find 边走边找收工(走满/命中)落探索覆盖账本(World 持久化) */
225
+ explored?: (dimension: string, direction: Direction, distance: number, biome: string) => void;
226
+ /** find 的短期真实观察;只供回执,不改变动作。 */
227
+ search: {
228
+ history: FindObservationCache;
229
+ scope(): SearchScope;
230
+ };
231
+ /** 容器 GUI 演出节拍;摄像机没开/演出关着时回 null,每单容器操作开工时现取一次 */
232
+ showTempo?: () => ShowTempo | null;
233
+ /**
234
+ * 这一单还剩哪些步、当前是第几步,以及「这一步顺手把后面某一步也做掉了」的登记口。
235
+ * 目前只有 stow 用它并窗(见 collectStowBatch):登记过的步执行器不再跑,直接用
236
+ * 登记的那句当回执。
237
+ */
238
+ batch?: {
239
+ steps: readonly SkillCall[];
240
+ index: number;
241
+ absorb(stepIndex: number, receipt: string): void;
242
+ };
243
+ /** 登记部分完成:技能正常返回,任务终态为部分完成,gap 说明未完成的量。 */
244
+ partial?: (gap: string) => void;
245
+ /**
246
+ * 刚刚有没有把重生点记到这张床上(World 听 set_spawn 系统消息)。白天点床原版是
247
+ * 「先记重生点,再拒绝睡觉」,回执只说没躺下的话,那一次点击在她眼里就是纯空操作。
248
+ */
249
+ spawnNote?: () => string | null;
250
+ /**
251
+ * 这一步有意放的那些格(cellKey),由 build 自己登记。执行器据此把「路上垫脚/搭路
252
+ * 用掉了」那句里的落点摘掉,落点之外的放置仍是耗材,照报。执行器每步换一只新的,
253
+ * 蓝图那层的内层 ctx 是浅拷贝,登记进的是同一只。
254
+ */
255
+ intended?: Set<string>;
256
+ /** 禁止本步补光,供蓝图清场使用,避免在要求空置的格重新放火把。 */
257
+ noLight?: boolean;
258
+ /**
259
+ * 个人重生点那一格(床/重生锚);没设过为 null。 World 持有唯一写入口(setPersonalSpawn)。
260
+ * 技能只读它,用来把「这一下动的是你的重生锚」当场说出来。
261
+ */
262
+ spawnAnchor?: () => { x: number; y: number; z: number; dimension?: string } | null;
263
+ /**
264
+ * 蓝图施工面(World 持有);没接 = 这个部署没有蓝图能力,build 的 blueprint 形态
265
+ * 会如实说"这边没装载"。采集搭车也读它。
266
+ */
267
+ blueprints?: () => BlueprintDesk;
268
+ /**
269
+ * 路标取用面(World 持有 mc_map);不接 = 这个部署没有路标(台架)。
270
+ * 技能只读它做回执侧的机械并置,不改表也不改执行。
271
+ */
272
+ marks?: () => MarkDesk;
273
+ /**
274
+ * 这一步现在正躺在床上等醒(见 waitForWake)。进度事件据此换一句话说 ——
275
+ * 等醒期间人本来就一动不动,周期进度报「进行中、挪了 0 格」会被读成卡住。
276
+ * 技能自己置位与复位,执行器只读。
277
+ */
278
+ sleeping?: boolean;
279
+ }
280
+
281
+ /** 上一次探查的指纹:形状+锚点(key)与读数(hash),都是 FNV-1a */
282
+ export interface ProbeMemo {
283
+ key: number;
284
+ hash: number;
285
+ /** 同参同读数连续探查到第几次 */
286
+ count: number;
287
+ /** 上次回执的第一句,重复时带给她当参照 */
288
+ summary: string;
289
+ }
290
+
291
+ /** 抢占方随异常一起走:message 就是日志与回执里那句「被谁打断的」 */
292
+ export class Aborted extends Error {
293
+ readonly by: string | null;
294
+ constructor(by: string | null = null) {
295
+ super(by ? `aborted: ${by}` : 'aborted');
296
+ this.by = by;
297
+ }
298
+ }
299
+
300
+ export function checkAbort(ctx: SkillContext): void {
301
+ if (ctx.aborted()) throw new Aborted(ctx.abortedBy?.() ?? null);
302
+ }
303
+
304
+ export async function sleep(ms: number): Promise<void> {
305
+ await new Promise((r) => setTimeout(r, ms));
306
+ }
307
+
308
+ export interface TaskRangedActions {
309
+ ready(bot: Bot): boolean;
310
+ shoot(target: RangedTarget, ownerToken: unknown): Promise<BowShotResult>;
311
+ abort(): void;
312
+ }
313
+
314
+ export interface TaskAttackLease {
315
+ token: object;
316
+ targetId: number;
317
+ swings: number;
318
+ meleeHits: number;
319
+ arrows: number;
320
+ rangedHits: number;
321
+ hurts: number;
322
+ lastHurtAt: number;
323
+ lastSwingAt: number;
324
+ lastSwingTargetId: number;
325
+ dead: boolean;
326
+ disconnected: boolean;
327
+ }
328
+
329
+ /**
330
+ * 零位移探针里执行器够不着的那几格。战斗与环境 owner 归 World 接线,
331
+ * 队列冻结与断点归执行器自己,合成一只递给 SkillContext(见 `bodyState`)。
332
+ */
333
+ export interface BodyStateProbe {
334
+ /** 战斗会话正占着身体 */
335
+ combatActive: boolean;
336
+ /** 环境自保正占着身体(岩浆/溺水/窒息);没有时 null */
337
+ environmentOwnerKind: 'lava' | 'drown' | 'suffocation' | null;
338
+ /** 队列冻结令牌还在谁手上(冻结理由);没冻结时 null */
339
+ queueHold: string | null;
340
+ /** 战斗挂起的断点任务号;没有时 null */
341
+ frozenTaskId: number | null;
342
+ }
343
+
344
+ export interface ToolTrace {
345
+ last: string | null | undefined;
346
+ notes: string[];
347
+ near: Set<string>;
348
+ }
349
+
350
+ /** 收着的工具被选中时的回执事实。 */
351
+ export interface ReserveHit {
352
+ tool: string;
353
+ block: string;
354
+ /** 名单外还剩的最好那把;一把不剩时 null */
355
+ instead: string | null;
356
+ reason?: 'only-capable' | 'override';
357
+ }
358
+