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,331 @@
1
+ /**
2
+ * 选具:这一下该拿哪把、够不够级、快断了没有,以及 reserve 收着的那把被迫动用时的记号。
3
+ *
4
+ * 挑与装备在这里,挖与放在技能族里;回执里的那一句由 toolTraceNote 出。
5
+ */
6
+ import type { Bot } from 'mineflayer';
7
+ import { type InvItem } from './inventory.ts';
8
+ import { readDurability } from './item-facts.ts';
9
+ import { SkillBlocked, type ReserveHit, type SkillContext, type ToolTrace } from './skill-context.ts';
10
+ import { matchItemName } from './chests.ts';
11
+ import { zhName } from './names.ts';
12
+
13
+ /**
14
+ * 工具材质的等级序,好的在前;倒着读就是等级序的低往高。
15
+ * 金镐挖得最快但等级只等于木镐(挖不动铁矿),所以它排在石之后。
16
+ */
17
+ export const TOOL_RANK = ['netherite', 'diamond', 'iron', 'stone', 'golden', 'wooden'];
18
+
19
+ /** minecraft-data 的方块条目里跟"拿什么挖"有关的两个字段 */
20
+ export interface BlockToolData {
21
+ /** 掉落的硬条件:itemId → true。缺省 = 这块谁挖都掉东西 */
22
+ harvestTools?: Record<string, unknown>;
23
+ /** `mineable/pickaxe`、`plant;mineable/axe` 这类;只决定快慢 */
24
+ material?: string;
25
+ }
26
+
27
+ export function blockToolData(bot: Bot, blockName: string): BlockToolData | undefined {
28
+ return (bot.registry.blocksByName as unknown as Record<string, BlockToolData | undefined>)[blockName];
29
+ }
30
+
31
+ /** harvestTools 的 itemId 清单 → 物品名 */
32
+ export function harvestToolNames(bot: Bot, def: BlockToolData | undefined): string[] {
33
+ if (!def?.harvestTools) return [];
34
+ const items = bot.registry.items as unknown as Record<number, { name: string } | undefined>;
35
+ return Object.keys(def.harvestTools).map((id) => items[Number(id)]?.name).filter((n): n is string => !!n);
36
+ }
37
+
38
+ /** 名字属于哪一类家伙什:`stone_pickaxe` → pickaxe、`shears` → shears */
39
+ export function toolKindOf(itemName: string): string {
40
+ const i = itemName.lastIndexOf('_');
41
+ return i < 0 ? itemName : itemName.slice(i + 1);
42
+ }
43
+
44
+ export type MiningToolPlan = { mode: 'economy' | 'fastest' } | { mode: 'exact'; item: string };
45
+
46
+ export interface ToolDecision {
47
+ pick: InvItem | null;
48
+ canDrop: boolean;
49
+ need: string | null;
50
+ error: string | null;
51
+ reserve?: { reason: 'only-capable' | 'override'; instead: string | null };
52
+ }
53
+
54
+ export function miningToolPlan(tool: string | undefined): MiningToolPlan {
55
+ return tool === undefined ? { mode: 'economy' }
56
+ : tool === 'fastest' ? { mode: 'fastest' }
57
+ : { mode: 'exact', item: tool };
58
+ }
59
+
60
+ /** 方块无需工具或没有适用工具时,不让上一把耐久物品继续替空手承受消耗。 */
61
+ export function isDurabilityItem(name: string): boolean {
62
+ return readDurability({ name }) !== null;
63
+ }
64
+
65
+ export function nearBreak(item: InvItem): { left: number; max: number } | null {
66
+ const d = readDurability(item);
67
+ if (!d || d.left > Math.max(3, Math.ceil(d.max * 0.05))) return null;
68
+ return d;
69
+ }
70
+
71
+ /** 返回被换下的耐久物品;没有换手时为 null。 */
72
+ export async function avoidUnsuitableHeldTool(bot: Bot): Promise<string | null> {
73
+ const held = bot.heldItem;
74
+ if (!held || !isDurabilityItem(held.name)) return null;
75
+ const substitute = bot.inventory.items().find((item) => !isDurabilityItem(item.name));
76
+ if (substitute) {
77
+ try {
78
+ await bot.equip(substitute, 'hand');
79
+ return held.name;
80
+ } catch {
81
+ // 继续尝试腾空主手。
82
+ }
83
+ }
84
+ // mineflayer 在主物品栏全满时 unequip 可能把手上物品丢到地上;这种情况保留原物品。
85
+ if (bot.inventory.items().length >= 36 || typeof bot.unequip !== 'function') return null;
86
+ try {
87
+ await bot.unequip('hand');
88
+ return held.name;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ export function toolRank(name: string): number {
95
+ const i = TOOL_RANK.findIndex((r) => name.startsWith(r));
96
+ return i < 0 ? TOOL_RANK.length : i;
97
+ }
98
+
99
+ export function durabilityLeft(item: InvItem): number {
100
+ return readDurability(item)?.left ?? Number.MAX_SAFE_INTEGER;
101
+ }
102
+
103
+ export function toolOrder(mode: 'economy' | 'fastest'): (a: InvItem, b: InvItem) => number {
104
+ if (mode === 'fastest') {
105
+ const speed = ['golden', 'netherite', 'diamond', 'iron', 'stone', 'wooden'];
106
+ const speedRank = (name: string): number => {
107
+ const i = speed.findIndex((tier) => name.startsWith(tier));
108
+ return i < 0 ? speed.length : i;
109
+ };
110
+ return (a, b) => Number(nearBreak(a) !== null) - Number(nearBreak(b) !== null)
111
+ || speedRank(a.name) - speedRank(b.name)
112
+ || durabilityLeft(b) - durabilityLeft(a);
113
+ }
114
+ return (a, b) => Number(nearBreak(a) !== null) - Number(nearBreak(b) !== null)
115
+ || toolRank(b.name) - toolRank(a.name)
116
+ || durabilityLeft(b) - durabilityLeft(a);
117
+ }
118
+
119
+ export function reservedBy(ctx: SkillContext, name: string): boolean {
120
+ return (ctx.policy?.get().reserve ?? []).some((r) => matchItemName(r, name));
121
+ }
122
+
123
+ export function chooseTool(
124
+ bot: Bot,
125
+ block: { name: string; canHarvest?: (type: number | null) => boolean },
126
+ ctx: SkillContext,
127
+ plan: MiningToolPlan,
128
+ ): ToolDecision {
129
+ const def = blockToolData(bot, block.name);
130
+ const harvest = harvestToolNames(bot, def);
131
+ const need = minHarvestTool(bot, block.name);
132
+ const canDrop = (item: InvItem): boolean => typeof block.canHarvest === 'function'
133
+ ? block.canHarvest(item.type)
134
+ : harvest.length === 0 || harvest.includes(item.name);
135
+
136
+ if (plan.mode === 'exact') {
137
+ const exact = bot.inventory.items().find((item) => item.name === plan.item && item.count > 0);
138
+ if (!exact) {
139
+ return { pick: null, canDrop: false, need, error: `包里没有本步指定的${zhName(plan.item)};没有改用别的工具` };
140
+ }
141
+ if (!canDrop(exact)) {
142
+ return {
143
+ pick: exact,
144
+ canDrop: false,
145
+ need,
146
+ error: `本步指定的${zhName(exact.name)}挖${zhName(block.name)}不掉东西${need ? `,要${zhName(need)}及以上` : ''};没有改用别的工具`,
147
+ };
148
+ }
149
+ return {
150
+ pick: exact,
151
+ canDrop: true,
152
+ need,
153
+ error: null,
154
+ ...(reservedBy(ctx, exact.name) ? { reserve: { reason: 'override' as const, instead: null } } : {}),
155
+ };
156
+ }
157
+
158
+ if (plan.mode === 'economy' && harvest.length === 0) {
159
+ return { pick: null, canDrop: true, need, error: null };
160
+ }
161
+
162
+ const kinds = new Set(harvest.length > 0
163
+ ? harvest.map(toolKindOf)
164
+ : [...(def?.material ?? '').matchAll(/mineable\/(\w+)/g)].map((m) => m[1]));
165
+ if (kinds.size === 0) return { pick: null, canDrop: true, need, error: null };
166
+ const inClass = bot.inventory.items()
167
+ .filter((item) => [...kinds].some((kind) => item.name === kind || item.name.endsWith(`_${kind}`)));
168
+ if (inClass.length === 0 && harvest.length === 0) {
169
+ return { pick: null, canDrop: true, need, error: null };
170
+ }
171
+ const capable = inClass.filter(canDrop);
172
+ if (capable.length === 0) {
173
+ return {
174
+ pick: null,
175
+ canDrop: false,
176
+ need,
177
+ error: `包里没有能保住${zhName(block.name)}掉落的工具${need ? `,要${zhName(need)}及以上` : ''};没动方块`,
178
+ };
179
+ }
180
+ const usable = plan.mode === 'economy' ? capable.filter((item) => nearBreak(item) === null) : capable;
181
+ if (usable.length === 0) {
182
+ const worn = capable.map((item) => {
183
+ const d = nearBreak(item)!;
184
+ return `${zhName(item.name)} ${d.left}/${d.max}`;
185
+ }).join('、');
186
+ return {
187
+ pick: null,
188
+ canDrop: false,
189
+ need,
190
+ error: `能保住${zhName(block.name)}掉落的工具都临近损坏(${worn});节约模式没动方块;本步写 tool:"fastest" 或具体工具名可临时覆盖`,
191
+ };
192
+ }
193
+ const pool = usable.sort(toolOrder(plan.mode));
194
+ const free = pool.filter((item) => !reservedBy(ctx, item.name));
195
+ if (free.length > 0) return { pick: free[0], canDrop: canDrop(free[0]), need, error: null };
196
+ const pick = pool[0];
197
+ const alternate = inClass.filter((item) => !reservedBy(ctx, item.name)).sort(toolOrder(plan.mode))[0];
198
+ return {
199
+ pick,
200
+ canDrop: canDrop(pick),
201
+ need,
202
+ error: null,
203
+ reserve: { reason: 'only-capable' as const, instead: alternate?.name ?? null },
204
+ };
205
+ }
206
+
207
+ export function recordToolChoice(ctx: SkillContext, block: string, plan: MiningToolPlan, decision: ToolDecision): void {
208
+ const trace = ctx.toolTrace;
209
+ if (!trace) return;
210
+ const pick = decision.pick?.name ?? null;
211
+ const first = trace.last === undefined;
212
+ const changed = !first && trace.last !== pick;
213
+ if (first || changed) {
214
+ if (pick === null) {
215
+ trace.notes.push(first
216
+ ? `${zhName(block)}不需工具,已换下耐久工具`
217
+ : `挖到${zhName(block)}时换下耐久工具`);
218
+ } else if (plan.mode === 'exact') {
219
+ trace.notes.push(`本步临时指定${zhName(pick)}`);
220
+ } else if (plan.mode === 'fastest') {
221
+ trace.notes.push(`${first ? '本步临时用最快工具' : `挖到${zhName(block)}时换成`}:${zhName(pick)}`);
222
+ } else {
223
+ trace.notes.push(`${first ? '节约模式选' : `挖到${zhName(block)}时换成`}:${zhName(pick)}`);
224
+ }
225
+ trace.last = pick;
226
+ }
227
+ if (decision.pick) {
228
+ const d = nearBreak(decision.pick);
229
+ const key = decision.pick.name;
230
+ if (d && !trace.near.has(key)) {
231
+ trace.near.add(key);
232
+ trace.notes.push(`${zhName(decision.pick.name)}临近损坏,只剩 ${d.left}/${d.max} 耐久`);
233
+ }
234
+ }
235
+ }
236
+
237
+ export function toolTraceNote(trace: ToolTrace | undefined): string {
238
+ return trace && trace.notes.length > 0 ? `;工具:${trace.notes.join(';')}` : '';
239
+ }
240
+
241
+ /** harvestTools 限定能产出掉落的工具;没有此限制时,material 的 mineable/tool 只决定速度。 */
242
+ export async function equipToolFor(
243
+ bot: Bot,
244
+ block: { name: string; canHarvest?: (type: number | null) => boolean },
245
+ ctx: SkillContext,
246
+ plan?: MiningToolPlan,
247
+ ): Promise<void> {
248
+ const actualPlan = plan ?? { mode: 'fastest' as const };
249
+ const decision = chooseTool(bot, block, ctx, actualPlan);
250
+ if (decision.error) throw new SkillBlocked(decision.error);
251
+ if (!decision.pick) {
252
+ const replaced = await avoidUnsuitableHeldTool(bot);
253
+ if (plan && replaced) recordToolChoice(ctx, block.name, actualPlan, decision);
254
+ return;
255
+ }
256
+ if (decision.reserve && ctx.reserveHits
257
+ && !ctx.reserveHits.some((hit) => hit.tool === decision.pick!.name && hit.block === block.name)) {
258
+ ctx.reserveHits.push({
259
+ tool: decision.pick.name,
260
+ block: block.name,
261
+ instead: decision.reserve.instead,
262
+ reason: decision.reserve.reason,
263
+ });
264
+ }
265
+ try {
266
+ await bot.equip(decision.pick, 'hand');
267
+ if (plan) recordToolChoice(ctx, block.name, actualPlan, decision);
268
+ } catch {
269
+ await avoidUnsuitableHeldTool(bot);
270
+ throw new SkillBlocked(`选了${zhName(decision.pick.name)}挖${zhName(block.name)},但没能拿到手;没动方块`);
271
+ }
272
+ }
273
+
274
+ /** 例外进这一步的回执;这一步没触发例外时是空串 */
275
+ export function reserveNote(hits: readonly ReserveHit[], mark: number): string {
276
+ return hits.slice(mark).map((h) => h.reason === 'override'
277
+ ? `;本步点名覆盖 reserve,拿了收着的${zhName(h.tool)}`
278
+ : h.instead
279
+ ? `;${zhName(h.instead)}挖不出${zhName(h.block)}的掉落,拿了收着的${zhName(h.tool)}`
280
+ : `;只有收着的${zhName(h.tool)}挖得出${zhName(h.block)}的掉落,拿了`).join('');
281
+ }
282
+
283
+ /** 能收获这块的家伙里等级最低的那一件;这块不挑工具时为 null */
284
+ export function minHarvestTool(bot: Bot, blockName: string): string | null {
285
+ const names = harvestToolNames(bot, blockToolData(bot, blockName));
286
+ if (names.length === 0) return null;
287
+ // TOOL_RANK 倒着读就是低到高:先命中的那个就是原版说的"及以上"那一级
288
+ for (const tier of [...TOOL_RANK].reverse()) {
289
+ const hit = names.find((n) => n.startsWith(tier));
290
+ if (hit) return hit;
291
+ }
292
+ return names[0];
293
+ }
294
+
295
+ /**
296
+ * 挖掘前以 canHarvest 报告工具能否产出掉落及最低等级;仅报告,不阻止挖掘。
297
+ * 原版允许工具等级不足时挖掉方块,但不产生掉落。
298
+ */
299
+ export function harvestFact(
300
+ bot: Bot,
301
+ b: { name: string; canHarvest?: (t: number | null) => boolean },
302
+ ): string | null {
303
+ if (typeof b.canHarvest !== 'function' || b.canHarvest(bot.heldItem?.type ?? null)) return null;
304
+ const hand = bot.heldItem ? zhName(bot.heldItem.name) : '空手';
305
+ const need = minHarvestTool(bot, b.name);
306
+ return `${hand}挖${zhName(b.name)}不掉东西${need ? `,要${zhName(need)}及以上` : ''}`;
307
+ }
308
+
309
+ /** 拿在手上才有用的那几类:镐斧锹锄剑,以及打火石、水桶这些一次性道具不算 */
310
+ export const HANDHELD_SUFFIXES = ['_pickaxe', '_axe', '_shovel', '_hoe', '_sword'];
311
+
312
+ /**
313
+ * equip 的目标槽位。盔甲的槽位是物品自带的属性(minecraft-data 的
314
+ * `equipmentSlot`/`equipDest`),不必自己按名字猜——猜出来的表迟早跟不上版本。
315
+ * 数据里没写的一律拿主手;盾牌的副手位是协议约定,数据里没有,单列一条。
316
+ */
317
+ export function equipDestOf(name: string, registry?: Bot['registry']): 'head' | 'torso' | 'legs' | 'feet' | 'off-hand' | 'hand' {
318
+ if (name === 'shield') return 'off-hand';
319
+ const def = registry
320
+ ? (registry.itemsByName as Record<string, { equipDest?: string; equipmentSlot?: string } | undefined>)[name]
321
+ : undefined;
322
+ const slot = def?.equipDest ?? def?.equipmentSlot;
323
+ if (slot === 'head' || slot === 'torso' || slot === 'legs' || slot === 'feet') return slot;
324
+ // registry 不在手上(纯文案场景)时按后缀兜一层:装备槽这件事本身不靠它做决定
325
+ if (name.endsWith('helmet')) return 'head';
326
+ if (name.endsWith('chestplate') || name === 'elytra') return 'torso';
327
+ if (name.endsWith('leggings')) return 'legs';
328
+ if (name.endsWith('boots')) return 'feet';
329
+ return 'hand';
330
+ }
331
+