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.
- package/README.md +7 -14
- package/package.json +1 -3
- package/src/boot.ts +18 -0
- package/src/bot.ts +41 -6
- package/src/core/README.md +4 -6
- package/src/core/blobs.ts +23 -2
- package/src/core/config-schema.ts +1 -1
- package/src/core/config.ts +2 -16
- package/src/core/core.ts +15 -5
- package/src/core/loop.ts +55 -27
- package/src/core/session.ts +3 -2
- package/src/core/timers.ts +8 -6
- package/src/core/types.ts +19 -3
- package/src/core/util.ts +17 -1
- package/src/deploy.ts +4 -1
- package/src/extensions/README.md +6 -4
- package/src/extensions/dry-mount.ts +23 -2
- package/src/extensions/manifest.ts +21 -11
- package/src/extensions.ts +147 -23
- package/src/launcher.ts +23 -9
- package/src/protocol/open-responses/context-log.ts +37 -9
- package/src/providers/README.md +32 -8
- package/src/providers/base.ts +3 -1
- package/src/providers/configuration.ts +2 -1
- package/src/providers/console/hub.ts +270 -0
- package/src/providers/console/settings.ts +8 -18
- package/src/providers/console/types.ts +5 -0
- package/src/providers/hub-api.ts +3 -0
- package/src/providers/llamacpp/config.ts +8 -6
- package/src/providers/llamacpp/console/runtime-panel.ts +5 -1
- package/src/providers/llamacpp/console/server.ts +3 -1
- package/src/providers/llamacpp/index.ts +3 -1
- package/src/providers/llamacpp/native.ts +10 -5
- package/src/providers/llamacpp/strings.ts +6 -6
- package/src/providers/name.ts +8 -0
- package/src/providers/openai-responses-compat/config.ts +13 -0
- package/src/providers/openai-responses-compat/console/client.ts +5 -0
- package/src/providers/openai-responses-compat/console/reasoning-panel.ts +84 -0
- package/src/providers/openai-responses-compat/console/server.ts +127 -0
- package/src/providers/openai-responses-compat/index.ts +38 -13
- package/src/providers/openai-responses-compat/native.ts +9 -4
- package/src/providers/openai-responses-compat/strings.ts +62 -1
- package/src/providers/registry.ts +5 -0
- package/src/providers/transport/responses-input.ts +59 -10
- package/src/web/README.md +11 -6
- package/src/web/auth.ts +80 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +5 -4
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +22 -8
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +6 -8
- package/src/web/client/console-pages/host.ts +24 -7
- package/src/web/client/core/api.ts +5 -1
- package/src/web/client/core/router.ts +15 -0
- package/src/web/client/features/core/strings.ts +2 -2
- package/src/web/client/features/extensions/index.ts +276 -41
- package/src/web/client/features/extensions/strings.ts +84 -10
- package/src/web/client/features/feature.ts +2 -2
- package/src/web/client/features/live/diagnostics.ts +32 -0
- package/src/web/client/features/live/index.ts +34 -11
- package/src/web/client/features/live/onboarding.ts +4 -1
- package/src/web/client/features/live/protocol.ts +1 -0
- package/src/web/client/features/live/strings.ts +20 -14
- package/src/web/client/features/live/timeline.ts +2 -1
- package/src/web/client/features/providers/detail.ts +262 -0
- package/src/web/client/features/providers/drafts.ts +23 -0
- package/src/web/client/features/providers/index.ts +173 -87
- package/src/web/client/features/providers/strings.ts +44 -17
- package/src/web/client/features/providers/types.ts +15 -0
- package/src/web/client/features/settings/general.ts +13 -0
- package/src/web/client/features/settings/index.ts +1 -0
- package/src/web/client/features/settings/strings.ts +6 -0
- package/src/web/client/features/worlds/index.ts +1 -0
- package/src/web/client/main.ts +4 -0
- package/src/web/client/shell/index.ts +28 -48
- package/src/web/client/shell/strings.ts +0 -22
- package/src/web/client/ui/icons.ts +14 -1
- package/src/web/client/ui/prompt-input.tsx +17 -5
- package/src/web/client/ui/strings.ts +0 -2
- package/src/web/console-pages.ts +1 -1
- package/src/web/diagnostics.ts +133 -0
- package/src/web/public/login.html +67 -0
- package/src/web/public/styles.css +143 -26
- package/src/web/server.ts +233 -19
- package/src/web/shared/client-panel.ts +4 -1
- package/src/web/shared/console-protocol.ts +4 -1
- package/src/worlds/bilibili/README.md +1 -1
- package/src/worlds/bilibili/overlay/server.ts +4 -2
- package/src/worlds/minecraft/ADAPT.md +66 -0
- package/src/worlds/minecraft/README.md +69 -11
- package/src/worlds/minecraft/cell-facts.ts +226 -0
- package/src/worlds/minecraft/chests.ts +5 -0
- package/src/worlds/minecraft/containers.ts +325 -0
- package/src/worlds/minecraft/entity-facts.ts +31 -5
- package/src/worlds/minecraft/executor.ts +283 -10348
- package/src/worlds/minecraft/inventory.ts +268 -0
- package/src/worlds/minecraft/melee.ts +419 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +55 -1
- package/src/worlds/minecraft/placed-ledger.ts +152 -0
- package/src/worlds/minecraft/placement.ts +1038 -0
- package/src/worlds/minecraft/receipt.ts +340 -0
- package/src/worlds/minecraft/skill-context.ts +358 -0
- package/src/worlds/minecraft/skills-build.ts +1169 -0
- package/src/worlds/minecraft/skills-container.ts +1343 -0
- package/src/worlds/minecraft/skills-craft.ts +333 -0
- package/src/worlds/minecraft/skills-dig.ts +624 -0
- package/src/worlds/minecraft/skills-gather.ts +1230 -0
- package/src/worlds/minecraft/skills-interact.ts +1559 -0
- package/src/worlds/minecraft/tools.ts +331 -0
- package/src/worlds/minecraft/travel.ts +763 -0
- package/src/worlds/minecraft/until.ts +75 -0
- package/src/worlds/qq/normalize.ts +14 -0
- package/src/worlds/qq/world.ts +53 -7
- package/src/worlds/terminal/world.ts +5 -3
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 早停名单:find 与 tunnel 声明的 `until`,把类别名摊成方块 id,一路上撞见就收工。
|
|
3
|
+
*
|
|
4
|
+
* 只认名单与判定,收不收工由技能自己决定。
|
|
5
|
+
*/
|
|
6
|
+
import type { Bot } from 'mineflayer';
|
|
7
|
+
import { UNTIL_CATEGORIES } from './skills.ts';
|
|
8
|
+
import { matchBlockIds } from './travel.ts';
|
|
9
|
+
import { canSeeBlockAt } from './terrain.ts';
|
|
10
|
+
import { zhName } from './names.ts';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `until` 的早停名单 → 方块 id 集合。
|
|
14
|
+
*
|
|
15
|
+
* `#类别` 先探一次 registry 的 tag 数据面(本仓库钉住的版本没有这一面,留着是为了
|
|
16
|
+
* 将来有了就自动走过去),取不到落到 `UNTIL_CATEGORIES` 那几个内置类别。
|
|
17
|
+
* 认不出的名字不抛错 —— 早停是这一单的副条件,它认不出来不该把整步判死;
|
|
18
|
+
* 认不出哪几个由回执点名(静默吃掉参数是这条链上最贵的一类失败)。
|
|
19
|
+
*/
|
|
20
|
+
export function untilBlockIds(bot: Bot, until: readonly string[]): { ids: number[]; unknown: string[] } {
|
|
21
|
+
const byName = bot.registry.blocksByName as Record<string, { id: number; name: string }>;
|
|
22
|
+
const tags = (bot.registry as unknown as { blockTags?: Record<string, string[]> }).blockTags;
|
|
23
|
+
const ids = new Set<number>();
|
|
24
|
+
const unknown: string[] = [];
|
|
25
|
+
for (const raw of until) {
|
|
26
|
+
const name = raw.replace(/^minecraft:/, '');
|
|
27
|
+
if (name.startsWith('#')) {
|
|
28
|
+
const bare = name.slice(1);
|
|
29
|
+
const tagged = tags?.[bare] ?? tags?.[`minecraft:${bare}`] ?? null;
|
|
30
|
+
if (tagged) {
|
|
31
|
+
for (const n of tagged) {
|
|
32
|
+
const b = byName[n.replace(/^minecraft:/, '')];
|
|
33
|
+
if (b) ids.add(b.id);
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const pred = UNTIL_CATEGORIES[bare];
|
|
38
|
+
if (!pred) { unknown.push(name); continue; }
|
|
39
|
+
for (const b of Object.values(byName)) if (pred(b.name)) ids.add(b.id);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const matched = matchBlockIds(bot, name);
|
|
43
|
+
if (matched.length === 0) { unknown.push(name); continue; }
|
|
44
|
+
for (const id of matched) ids.add(id);
|
|
45
|
+
}
|
|
46
|
+
return { ids: [...ids], unknown };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `until` 命中的那一格 */
|
|
50
|
+
export interface UntilHit { x: number; y: number; z: number; what: string }
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 周身有没有碰到早停名单里的东西。
|
|
54
|
+
*
|
|
55
|
+
* `visible` = 只认看得见的:行军途中用这一档(与 find 自己的感知规则同源)。
|
|
56
|
+
* 挖通道那一档不设视线闸 —— 铲子下去露出来的那一面本来就贴着脸,视线判据在坑里没意义。
|
|
57
|
+
*/
|
|
58
|
+
export function untilHit(bot: Bot, ids: readonly number[], radius: number, visible: boolean): UntilHit | null {
|
|
59
|
+
if (ids.length === 0) return null;
|
|
60
|
+
const found = bot.findBlocks({ matching: [...ids], maxDistance: radius, count: 16 });
|
|
61
|
+
const p = found.find((q) => !visible || canSeeBlockAt(bot, q));
|
|
62
|
+
if (!p) return null;
|
|
63
|
+
return { x: p.x, y: p.y, z: p.z, what: zhName(bot.blockAt(p)?.name ?? 'unknown') };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 行军途中每走完一段扫一次早停的半径(格) */
|
|
67
|
+
export const UNTIL_TRAVEL_RADIUS = 16;
|
|
68
|
+
/** 挖通道每挖完一格扫一次早停的半径(格):坑壁上露出来的那一圈 */
|
|
69
|
+
export const UNTIL_DIG_RADIUS = 4;
|
|
70
|
+
|
|
71
|
+
/** 认不出的早停名字那一句;全认得出返回空串 */
|
|
72
|
+
export function untilUnknownNote(unknown: readonly string[]): string {
|
|
73
|
+
return unknown.length > 0 ? `(until 里的 ${unknown.join('、')} 认不出来,这几样没算进去)` : '';
|
|
74
|
+
}
|
|
75
|
+
|
|
@@ -178,6 +178,9 @@ export function renderSegmentsPlain(segments: Segment[]): string {
|
|
|
178
178
|
case 'image':
|
|
179
179
|
parts.push({ text: '[图片]', isToken: true });
|
|
180
180
|
break;
|
|
181
|
+
case 'record':
|
|
182
|
+
parts.push({ text: '[语音]', isToken: true });
|
|
183
|
+
break;
|
|
181
184
|
case 'face': {
|
|
182
185
|
const name = QFACE_NAMES[String(data.id ?? '')];
|
|
183
186
|
parts.push({ text: name ? `[表情:${name}]` : '[QQ表情]', isToken: true });
|
|
@@ -253,6 +256,11 @@ export function renderIncoming(
|
|
|
253
256
|
parts.push({ text: renderImage(data), isToken: true });
|
|
254
257
|
break;
|
|
255
258
|
}
|
|
259
|
+
// 语音条的音频不随消息到达,取回它要另一次动作调用;这里只说这是一条语音。
|
|
260
|
+
case 'record': {
|
|
261
|
+
parts.push({ text: '[语音]', isToken: true });
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
256
264
|
case 'face': {
|
|
257
265
|
const name = QFACE_NAMES[String(data.id ?? '')];
|
|
258
266
|
parts.push({ text: name ? `[表情:${name}]` : '[QQ表情]', isToken: true });
|
|
@@ -301,14 +309,20 @@ interface OutgoingArgs {
|
|
|
301
309
|
reply_to_message_id?: number | string;
|
|
302
310
|
/** 图片base64(不含data:前缀);有则编译成image段(OneBot base64://) */
|
|
303
311
|
image_base64?: string;
|
|
312
|
+
/** 语音base64(不含data:前缀);有则整条消息只有这一个record段 */
|
|
313
|
+
record_base64?: string;
|
|
304
314
|
}
|
|
305
315
|
|
|
306
316
|
/**
|
|
307
317
|
* 出站 → segment数组:reply段(如有)在前,然后image段(如有),最后text段
|
|
308
318
|
* (text非空才加——支持只发图不带字)。
|
|
319
|
+
* 语音条独占一条消息:QQ 的 record 段不与别的段同发,给了它就只发它。
|
|
309
320
|
* 不做 @ 编译:agent 写出的“@某人”作为纯文本原样发出。
|
|
310
321
|
*/
|
|
311
322
|
export function buildOutgoing(args: OutgoingArgs): Segment[] {
|
|
323
|
+
if (args.record_base64) {
|
|
324
|
+
return [{ type: 'record', data: { file: `base64://${args.record_base64}` } }];
|
|
325
|
+
}
|
|
312
326
|
const segments: Segment[] = [];
|
|
313
327
|
if (args.reply_to_message_id !== undefined) {
|
|
314
328
|
segments.push({ type: 'reply', data: { id: String(args.reply_to_message_id) } });
|
package/src/worlds/qq/world.ts
CHANGED
|
@@ -109,6 +109,8 @@ interface PendingDraft {
|
|
|
109
109
|
replyMessageId?: number | string;
|
|
110
110
|
/** 要发送的图片的句柄(`log:` 她看见过的 / `mem:` 她收藏的);无=纯文本 */
|
|
111
111
|
imageRef?: string;
|
|
112
|
+
/** 要发送的语音的句柄;语音条独占一条消息,与 text、imageRef 互斥 */
|
|
113
|
+
voiceRef?: string;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
interface ForwardNode {
|
|
@@ -1235,7 +1237,14 @@ export class QQWorld implements World {
|
|
|
1235
1237
|
/** 实际发送到目标会话,回录 qq.self,返回回执文本 */
|
|
1236
1238
|
private async sendToTarget(
|
|
1237
1239
|
target: Conv,
|
|
1238
|
-
opts: {
|
|
1240
|
+
opts: {
|
|
1241
|
+
text: string;
|
|
1242
|
+
replyMessageId?: number | string;
|
|
1243
|
+
imageRef?: string;
|
|
1244
|
+
imageBytes?: Uint8Array;
|
|
1245
|
+
voiceRef?: string;
|
|
1246
|
+
voiceBytes?: Uint8Array;
|
|
1247
|
+
},
|
|
1239
1248
|
): Promise<string> {
|
|
1240
1249
|
const host = this.host!;
|
|
1241
1250
|
const driver = this.driver!;
|
|
@@ -1247,6 +1256,7 @@ export class QQWorld implements World {
|
|
|
1247
1256
|
text: opts.text,
|
|
1248
1257
|
reply_to_message_id: opts.replyMessageId,
|
|
1249
1258
|
image_base64: imageBase64,
|
|
1259
|
+
...(opts.voiceBytes ? { record_base64: Buffer.from(opts.voiceBytes).toString('base64') } : {}),
|
|
1250
1260
|
});
|
|
1251
1261
|
const action = target.kind === 'group' ? 'send_group_msg' : 'send_private_msg';
|
|
1252
1262
|
const params =
|
|
@@ -1259,7 +1269,13 @@ export class QQWorld implements World {
|
|
|
1259
1269
|
// 回录正文:文字 + (如有)图片标记,保证自己发的图在历史里也可见。
|
|
1260
1270
|
// 行首保留平台消息号,使历史查询结果可引用自身消息。
|
|
1261
1271
|
const imgMark = opts.imageRef ? `[图片: ${opts.imageRef}]` : '';
|
|
1262
|
-
const
|
|
1272
|
+
const voiceMark = opts.voiceRef ? `[语音: ${opts.voiceRef}]` : '';
|
|
1273
|
+
const selfBody = [opts.text, imgMark, voiceMark].filter(Boolean).join(' ');
|
|
1274
|
+
const selfBlob = opts.imageRef
|
|
1275
|
+
? { handle: opts.imageRef, fallbackText: '你发出的图片' }
|
|
1276
|
+
: opts.voiceRef
|
|
1277
|
+
? { handle: opts.voiceRef, fallbackText: '你发出的语音' }
|
|
1278
|
+
: null;
|
|
1263
1279
|
const idTag = messageId !== undefined ? `#${messageId} ` : '';
|
|
1264
1280
|
const env = await host.pushEvent(
|
|
1265
1281
|
{
|
|
@@ -1267,8 +1283,8 @@ export class QQWorld implements World {
|
|
|
1267
1283
|
ts: nowIso(this.timezone),
|
|
1268
1284
|
source: this.id,
|
|
1269
1285
|
text: `${idTag}[${this.convLabel(target)} ${shortTime(this.timezone)}] 你: ${selfBody}`,
|
|
1270
|
-
//
|
|
1271
|
-
...(
|
|
1286
|
+
// 发出去的图和语音按句柄附在自己这条记录上:时间线与吃这些类型的模型都看得到发了什么
|
|
1287
|
+
...(selfBlob ? { blobs: [selfBlob] } : {}),
|
|
1272
1288
|
senderKey: String(driver.identity?.selfId ?? 'self'),
|
|
1273
1289
|
meta: {
|
|
1274
1290
|
message_id: messageId,
|
|
@@ -1320,6 +1336,13 @@ export class QQWorld implements World {
|
|
|
1320
1336
|
'Optional. An image to send, given by handle: a log: handle from a [blob ...] line you saw, '
|
|
1321
1337
|
+ 'or a mem: handle of one kept in your workspace.',
|
|
1322
1338
|
},
|
|
1339
|
+
voice: {
|
|
1340
|
+
type: 'string',
|
|
1341
|
+
description:
|
|
1342
|
+
'Optional. Audio to send as a QQ voice bar, given by handle the same way as image. '
|
|
1343
|
+
+ 'A voice bar is a message of its own: leave text, image and reply_to out. '
|
|
1344
|
+
+ 'The protocol end converts the audio it accepts (wav, mp3) to what QQ plays.',
|
|
1345
|
+
},
|
|
1323
1346
|
reply_to: {
|
|
1324
1347
|
type: 'string',
|
|
1325
1348
|
description:
|
|
@@ -1335,7 +1358,12 @@ export class QQWorld implements World {
|
|
|
1335
1358
|
const rawText = typeof args.text === 'string' ? args.text : '';
|
|
1336
1359
|
const imageArg =
|
|
1337
1360
|
typeof args.image === 'string' && args.image.trim() ? args.image.trim() : undefined;
|
|
1338
|
-
|
|
1361
|
+
const voiceArg =
|
|
1362
|
+
typeof args.voice === 'string' && args.voice.trim() ? args.voice.trim() : undefined;
|
|
1363
|
+
if (!rawText.trim() && !imageArg && !voiceArg) return '[bad input] provide at least one of text, image or voice';
|
|
1364
|
+
if (voiceArg && (rawText.trim() || imageArg || (args.reply_to !== undefined && args.reply_to !== null && String(args.reply_to) !== ''))) {
|
|
1365
|
+
return '[bad input] a voice bar is a message of its own; it carries no text, image or quote-reply';
|
|
1366
|
+
}
|
|
1339
1367
|
// 纯空白正文视为不带字(避免只发图时混进一个空白 text 段)
|
|
1340
1368
|
const text = rawText.trim() ? rawText : '';
|
|
1341
1369
|
|
|
@@ -1373,7 +1401,17 @@ export class QQWorld implements World {
|
|
|
1373
1401
|
: `[will send image: ${imageArg}]`;
|
|
1374
1402
|
}
|
|
1375
1403
|
|
|
1376
|
-
|
|
1404
|
+
// 语音:同样按句柄确认取得到,MIME 必须是音频
|
|
1405
|
+
let voiceRef: string | undefined;
|
|
1406
|
+
if (voiceArg) {
|
|
1407
|
+
const got = host.blob(voiceArg);
|
|
1408
|
+
if (!got) return `[send failed] no blob for ${voiceArg}; use a log: handle from a [blob ...] line or a mem: handle from your workspace`;
|
|
1409
|
+
if (!got.mime.startsWith('audio/')) return `[send failed] ${voiceArg} is ${got.mime}, not audio`;
|
|
1410
|
+
voiceRef = voiceArg;
|
|
1411
|
+
previewLine = `[will send voice: ${voiceArg}]`;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
this.pendingDraft = { target, text, replyMessageId: rep.mid, imageRef, voiceRef };
|
|
1377
1415
|
|
|
1378
1416
|
// 排出当下已积、尚未投递的QQ会话事件(consume-once)。正文不能伪装成
|
|
1379
1417
|
// draft 工具结果:交回 loop 按常规投递,本轮下一次推理前它们就在眼前,
|
|
@@ -1382,7 +1420,7 @@ export class QQWorld implements World {
|
|
|
1382
1420
|
ctx.queueExternalEvents?.(drained);
|
|
1383
1421
|
|
|
1384
1422
|
const lines = [`[draft staged → ${this.targetDesc(target)}]`];
|
|
1385
|
-
lines.push(text.trim() ? `Text: ${text}` : '(image only, no text)');
|
|
1423
|
+
lines.push(text.trim() ? `Text: ${text}` : voiceRef ? '(voice bar, no text)' : '(image only, no text)');
|
|
1386
1424
|
if (previewLine) lines.push(previewLine);
|
|
1387
1425
|
lines.push(
|
|
1388
1426
|
'',
|
|
@@ -1433,11 +1471,19 @@ export class QQWorld implements World {
|
|
|
1433
1471
|
if (!got) return `[send failed] blob ${draft.imageRef} is gone (draft kept)`;
|
|
1434
1472
|
imageBytes = got.bytes;
|
|
1435
1473
|
}
|
|
1474
|
+
let voiceBytes: Uint8Array | undefined;
|
|
1475
|
+
if (draft.voiceRef) {
|
|
1476
|
+
const got = host.blob(draft.voiceRef);
|
|
1477
|
+
if (!got) return `[send failed] blob ${draft.voiceRef} is gone (draft kept)`;
|
|
1478
|
+
voiceBytes = got.bytes;
|
|
1479
|
+
}
|
|
1436
1480
|
const receipt = await this.sendToTarget(draft.target, {
|
|
1437
1481
|
text: draft.text,
|
|
1438
1482
|
replyMessageId: draft.replyMessageId,
|
|
1439
1483
|
imageRef: draft.imageRef,
|
|
1440
1484
|
imageBytes,
|
|
1485
|
+
voiceRef: draft.voiceRef,
|
|
1486
|
+
voiceBytes,
|
|
1441
1487
|
});
|
|
1442
1488
|
this.pendingDraft = null; // 成功才清;失败保留草稿,可再 confirm 重试
|
|
1443
1489
|
return receipt;
|
|
@@ -251,6 +251,8 @@ interface TerminalWorldOptions {
|
|
|
251
251
|
|
|
252
252
|
const NAME_MAX = 32;
|
|
253
253
|
const TEXT_MAX = 4000;
|
|
254
|
+
/** 按钮标签进事件正文前的长度上限。 */
|
|
255
|
+
const LABEL_MAX = 40;
|
|
254
256
|
|
|
255
257
|
/** 一条消息最多带几张图,单张解码后的字节上限。与面板侧的上限一致。 */
|
|
256
258
|
const IMAGES_MAX = 8;
|
|
@@ -606,8 +608,8 @@ export class TerminalWorld implements World {
|
|
|
606
608
|
this.sendJson(client.peer, { type: 'sys', text: t.botNotConnected });
|
|
607
609
|
return;
|
|
608
610
|
}
|
|
609
|
-
//
|
|
610
|
-
const label = typeof msg.label === 'string' ? msg.label.replace(/\s+/g, ' ').trim() : '';
|
|
611
|
+
// 标签逐字进事件正文,所以压成一行并截断;没有标签就没有可引用的事实,这一帧作废。
|
|
612
|
+
const label = typeof msg.label === 'string' ? msg.label.replace(/\s+/g, ' ').trim().slice(0, LABEL_MAX) : '';
|
|
611
613
|
if (!label) return;
|
|
612
614
|
const host = this.host;
|
|
613
615
|
host.pushEvent(
|
|
@@ -628,7 +630,7 @@ export class TerminalWorld implements World {
|
|
|
628
630
|
this.sendJson(client.peer, { type: 'sys', text: t.unknownType(String(msg.type)) });
|
|
629
631
|
}
|
|
630
632
|
|
|
631
|
-
/**
|
|
633
|
+
/** 已保存的终端事件里有没有人说过话。 */
|
|
632
634
|
private spokenBefore(): boolean {
|
|
633
635
|
if (!this.host) return false;
|
|
634
636
|
return this.host.store
|