@zhin.js/command 1.1.17 → 1.2.1
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 +3 -3
- package/lib/command-index.d.ts +10 -0
- package/lib/command-index.js +80 -26
- package/lib/definition.d.ts +47 -9
- package/lib/definition.js +54 -43
- package/lib/execution-runtime.d.ts +3 -1
- package/lib/execution-runtime.js +3 -2
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/messages.d.ts +1 -1
- package/lib/messages.js +2 -1
- package/lib/permission-explanation.js +2 -3
- package/lib/permit.d.ts +8 -5
- package/lib/permit.js +8 -5
- package/package.json +6 -7
- package/src/arguments.ts +0 -308
- package/src/command-index.ts +0 -1063
- package/src/command-menu.ts +0 -116
- package/src/definition.ts +0 -816
- package/src/execution-policy.ts +0 -39
- package/src/execution-runtime.ts +0 -202
- package/src/index.ts +0 -31
- package/src/messages.ts +0 -63
- package/src/permission-explanation.ts +0 -61
- package/src/permit.ts +0 -28
- package/src/provider.ts +0 -320
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ import { defineCommand } from 'zhin.js/command';
|
|
|
16
16
|
export default defineCommand({
|
|
17
17
|
description: 'List GitHub issues',
|
|
18
18
|
alias: ['issues'], // 可多词;owner 不参与用户路由
|
|
19
|
-
permit: ['adapter(icqq)', '
|
|
19
|
+
permit: ['adapter(icqq)', 'owner'], // 数组 AND;失败返回 denied
|
|
20
20
|
// shortcut: { '列 issue': {} }, // 全局整句
|
|
21
21
|
execute: ({ args }) => `issues:${args.join(',')}`,
|
|
22
22
|
});
|
|
@@ -154,7 +154,7 @@ parent 和 thread 独立计算。没有完整可信身份或真实 IM operation
|
|
|
154
154
|
新策略的并发数用于新准入,原来已开始的冷却保留到原到期时间。移除策略即停止新请求的预算检查。
|
|
155
155
|
状态只保留在当前进程,进程重启后重置;容量满时拒绝新身份,不驱逐活跃或仍在冷却的记录。
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
进度默认关闭,只在业务等待阶段发送一次“正在处理中,请稍候。”(按当前操作的语言本地化),走当前平台统一发送链路。
|
|
158
158
|
开始自动最终回复前停止尚未开始的提示,并等待已开始的提示完成;已开始的 I/O 随原 lease 排空。
|
|
159
159
|
执行内显式 `$reply()` 不会停止进度计时。提示失败不重试,也不表示任务取消或远端已经展示。
|
|
160
160
|
`@zhin.js/command/internal/execution` 是 Core 装配使用的内部入口,不是插件创作 API。
|
|
@@ -173,7 +173,7 @@ pnpm --filter @zhin.js/command test
|
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
完整用户契约见[命令创作指南](../../../docs/authoring/commands.md),架构迁移背景见
|
|
176
|
-
[
|
|
176
|
+
[Legacy 概念迁移](../../../docs/contributing/legacy-concepts.md)。
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
命令可声明 `descriptionI18n: { en: 'Create a reminder' }` 翻译描述。若静态目录名是中文,可沿用 `alias` 提供英文入口,并用 `preferredAliasI18n` 显式选择英文展示:
|
package/lib/command-index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface CommandDescriptor {
|
|
|
25
25
|
readonly execution?: CommandDefinition['execution'];
|
|
26
26
|
/** shortcut 触发键列表(不含预填 params)。 */
|
|
27
27
|
readonly shortcut?: readonly string[];
|
|
28
|
+
/** `match` 正则的字面形式(如 `/^demo$/i`)。 */
|
|
29
|
+
readonly match?: string;
|
|
28
30
|
}
|
|
29
31
|
/** One dispatch reads the caller's exact operation generation and presentation. */
|
|
30
32
|
export interface CommandDispatchRequest {
|
|
@@ -46,6 +48,14 @@ export interface CommandDispatchResult {
|
|
|
46
48
|
readonly owner?: PluginId;
|
|
47
49
|
readonly value?: unknown;
|
|
48
50
|
readonly menu?: CommandMenuPage;
|
|
51
|
+
/**
|
|
52
|
+
* `denied` only: the first unmet permit and a one-sentence reason for the sender (e.g. 「机器人不是本群管理员」).
|
|
53
|
+
* Undefined when no Permission host / source answered.
|
|
54
|
+
*/
|
|
55
|
+
readonly denial?: Readonly<{
|
|
56
|
+
permit: string;
|
|
57
|
+
reason: string;
|
|
58
|
+
}>;
|
|
49
59
|
}
|
|
50
60
|
/** Semantic menu navigation; the IM composition layer supplies platform controls. */
|
|
51
61
|
export interface CommandMenuNavigation {
|
package/lib/command-index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SegmentMatcher, TypeMatcherRegistry, } from 'segment-matcher';
|
|
2
2
|
import { htmlRendererToken, resolveLocale, } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import { bindOperationInput } from '@zhin.js/feature-kit';
|
|
4
|
-
import { permissionHostToken, } from '@zhin.js/permission';
|
|
4
|
+
import { describeDeniedPermit, permissionHostToken, } from '@zhin.js/permission';
|
|
5
5
|
import { commandAdapterMatches, toCommandPermissionSubject } from './permission-explanation.js';
|
|
6
6
|
import { commandUsage, commandHelp, parseCommandArguments, requestsCommandHelp, commandValidationIssue } from './arguments.js';
|
|
7
7
|
import { commandDescription, commandMessages } from './messages.js';
|
|
@@ -90,6 +90,7 @@ export class CommandIndex {
|
|
|
90
90
|
...(alias ? { alias } : {}),
|
|
91
91
|
...(permit ? { permit } : {}),
|
|
92
92
|
...(shortcutKeys && shortcutKeys.length > 0 ? { shortcut: shortcutKeys } : {}),
|
|
93
|
+
...(slot.definition.match ? { match: String(slot.definition.match), pattern: slot.definition.match } : {}),
|
|
93
94
|
slot,
|
|
94
95
|
segments: Object.freeze(primarySegments),
|
|
95
96
|
preferredSegments: Object.freeze(Object.fromEntries(Object.entries(slot.definition.preferredAliasI18n ?? {}).map(([locale, entry]) => [locale, Object.freeze([...namespace, ...aliasRuntimeSegments(entry, primarySegments.slice(namespace.length))])]))),
|
|
@@ -175,7 +176,7 @@ export class CommandIndex {
|
|
|
175
176
|
this.#diagnoseParameter(name);
|
|
176
177
|
throw new Error(`Unknown Command: ${name}`);
|
|
177
178
|
}
|
|
178
|
-
if (
|
|
179
|
+
if ((await this.#permitDenial(match.command, undefined, operationSnapshot)) !== undefined)
|
|
179
180
|
throw new Error(`Permission denied for Command: ${match.command.name}`);
|
|
180
181
|
const definition = match.command.slot.definition;
|
|
181
182
|
const supplied = [...match.remaining, ...(args.length > 0 ? [{ type: 'text', data: { text: args.join(' ') } }] : [])];
|
|
@@ -201,7 +202,7 @@ export class CommandIndex {
|
|
|
201
202
|
const prefix = commandPrefix ?? '';
|
|
202
203
|
if (this.#menu) {
|
|
203
204
|
const keyword = this.#menu.keyword ?? commandMessages(locale, 'menuKeyword');
|
|
204
|
-
const menuResult = this.#dispatchMenu(input, { commandPrefix: prefix, snapshot: operationSnapshot, locale, keyword });
|
|
205
|
+
const menuResult = await this.#dispatchMenu(input, { commandPrefix: prefix, snapshot: operationSnapshot, locale, keyword, source });
|
|
205
206
|
if (menuResult !== undefined) {
|
|
206
207
|
return Object.freeze({
|
|
207
208
|
matched: true,
|
|
@@ -213,17 +214,19 @@ export class CommandIndex {
|
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
const shortcut = this.#matchShortcut(input);
|
|
216
|
-
const
|
|
217
|
+
const routed = shortcut
|
|
217
218
|
? { command: shortcut.record, params: shortcut.params, remaining: Object.freeze([]), helpRequested: false }
|
|
218
219
|
: this.#match(input, false);
|
|
220
|
+
const match = routed ? withPattern(routed, input) : this.#matchPattern(input);
|
|
219
221
|
if (!match) {
|
|
220
222
|
const candidate = this.#invalidRoute(input);
|
|
221
223
|
if (!candidate)
|
|
222
224
|
return Object.freeze({ matched: false, status: 'unmatched' });
|
|
223
225
|
if (!commandAdapterMatches(candidate, source))
|
|
224
226
|
return Object.freeze({ matched: false, status: 'unmatched' });
|
|
225
|
-
|
|
226
|
-
|
|
227
|
+
const candidateDenial = await this.#permitDenial(candidate, source, operationSnapshot);
|
|
228
|
+
if (candidateDenial !== undefined)
|
|
229
|
+
return this.#denied(candidate, candidateDenial, locale);
|
|
227
230
|
const help = commandHelp(commandDisplayName(candidate, locale), candidate.slot.definition, prefix, locale);
|
|
228
231
|
const segments = normalizeSegments(typeof input === 'string' ? [{ type: 'text', data: { text: input } }] : input);
|
|
229
232
|
if (requestsCommandHelp(segments))
|
|
@@ -233,8 +236,9 @@ export class CommandIndex {
|
|
|
233
236
|
}
|
|
234
237
|
if (!commandAdapterMatches(match.command, source))
|
|
235
238
|
return Object.freeze({ matched: false, status: 'unmatched' });
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
const denial = await this.#permitDenial(match.command, source, operationSnapshot);
|
|
240
|
+
if (denial !== undefined)
|
|
241
|
+
return this.#denied(match.command, denial, locale);
|
|
238
242
|
const definition = match.command.slot.definition;
|
|
239
243
|
const authorSource = bindOperationInput(source, match.command.slot.owner, operationSnapshot);
|
|
240
244
|
const help = commandHelp(commandDisplayName(match.command, locale), definition, prefix, locale);
|
|
@@ -255,7 +259,8 @@ export class CommandIndex {
|
|
|
255
259
|
operation,
|
|
256
260
|
interaction: interactionFactory?.(authorSource), adapter: definition.adapter,
|
|
257
261
|
presentation: { locale, formatCommand: (localPath, owner = match.command.owner) => this.formatCommand(owner, localPath, source && typeof source === 'object' && 'conversation' in source ? commandPrefix : undefined, locale) },
|
|
258
|
-
}, { args: textArgs(parsed.remaining), params: parsed.params, segments: parsed.remaining, options: parsed.options
|
|
262
|
+
}, { args: textArgs(parsed.remaining), params: parsed.params, segments: parsed.remaining, options: parsed.options,
|
|
263
|
+
...(match.regex ? { match: match.regex } : {}) }));
|
|
259
264
|
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value });
|
|
260
265
|
}
|
|
261
266
|
finally {
|
|
@@ -265,34 +270,60 @@ export class CommandIndex {
|
|
|
265
270
|
catch { /* Cleanup never replaces the command's actual outcome. */ }
|
|
266
271
|
}
|
|
267
272
|
}
|
|
268
|
-
#denied(record) {
|
|
269
|
-
return Object.freeze({ matched: true, status: 'denied', command: record.name, owner: record.slot.owner
|
|
273
|
+
#denied(record, permit, locale) {
|
|
274
|
+
return Object.freeze({ matched: true, status: 'denied', command: record.name, owner: record.slot.owner,
|
|
275
|
+
...(permit ? { denial: Object.freeze({ permit, reason: describeDeniedPermit(permit, locale) }) } : {}) });
|
|
270
276
|
}
|
|
271
277
|
/** Diagnose only a reliable static route prefix; fully dynamic routes are ordinary chat candidates. */
|
|
272
278
|
#invalidRoute(input) {
|
|
273
279
|
const segments = normalizeSegments(typeof input === 'string' ? [{ type: 'text', data: { text: input } }] : input);
|
|
280
|
+
// segment-matcher clones its input before matching, so one copy serves every route.
|
|
281
|
+
const matcherSegments = asMatcherSegments(segments);
|
|
274
282
|
for (const route of this.#routes) {
|
|
275
283
|
if (!route.record.parameter)
|
|
276
284
|
continue;
|
|
277
|
-
const result = route.literalMatcher?.match(
|
|
285
|
+
const result = route.literalMatcher?.match(matcherSegments);
|
|
278
286
|
if (result && hasCommandBoundary(result.remaining))
|
|
279
287
|
return route.record;
|
|
280
288
|
}
|
|
281
289
|
return undefined;
|
|
282
290
|
}
|
|
283
|
-
|
|
291
|
+
/**
|
|
292
|
+
* ADR 0056 §5: the shared evaluator (PermissionHost) over the three dimensions — sender zhin role,
|
|
293
|
+
* sender platform standing and the Bot's own standing (resolved from the Adapter index cache).
|
|
294
|
+
* `undefined` = allowed; a permit string = the first unmet one; `null` = denied without a reason
|
|
295
|
+
* (no source or no Permission host).
|
|
296
|
+
*/
|
|
297
|
+
async #permitDenial(record, source, operationSnapshot, cachedOnly = false) {
|
|
284
298
|
const permits = record.permit;
|
|
285
299
|
if (!permits || permits.length === 0)
|
|
286
|
-
return
|
|
300
|
+
return undefined;
|
|
287
301
|
if (!source || typeof source !== 'object')
|
|
288
|
-
return
|
|
302
|
+
return null;
|
|
289
303
|
const host = this.#resolveHost(operationSnapshot);
|
|
290
304
|
if (!host)
|
|
291
|
-
return
|
|
305
|
+
return null;
|
|
292
306
|
const subject = toCommandPermissionSubject(source);
|
|
293
307
|
if (!subject.sender?.id && !subject.scene?.id && !subject.adapter && !subject.endpoint)
|
|
308
|
+
return null;
|
|
309
|
+
const options = { source, ...(cachedOnly ? { cachedOnly } : {}) };
|
|
310
|
+
if (await host.checkAll(permits, subject, options))
|
|
311
|
+
return undefined;
|
|
312
|
+
// Denied: name the first unmet permit for the reply (same evaluator; Bot standing is cached now).
|
|
313
|
+
try {
|
|
314
|
+
return (await host.firstDenied?.(permits, subject, options)) ?? null;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/** Whether the menu should list `record` for `source` (adapter restriction + permits). */
|
|
321
|
+
async #listable(record, source, operationSnapshot) {
|
|
322
|
+
if (source === undefined)
|
|
323
|
+
return true;
|
|
324
|
+
if (!commandAdapterMatches(record, source))
|
|
294
325
|
return false;
|
|
295
|
-
return
|
|
326
|
+
return (await this.#permitDenial(record, source, operationSnapshot)) === undefined;
|
|
296
327
|
}
|
|
297
328
|
#resolveHost(operationSnapshot) {
|
|
298
329
|
try {
|
|
@@ -308,6 +339,18 @@ export class CommandIndex {
|
|
|
308
339
|
return undefined;
|
|
309
340
|
}
|
|
310
341
|
}
|
|
342
|
+
/** `match` 正则:路由不中时,按指令列表顺序取第一条正文符合的指令(只看纯文本消息)。 */
|
|
343
|
+
#matchPattern(input) {
|
|
344
|
+
const text = exactMessageText(input);
|
|
345
|
+
if (text === undefined)
|
|
346
|
+
return undefined;
|
|
347
|
+
for (const command of this.#commands) {
|
|
348
|
+
const regex = command.pattern?.exec(text);
|
|
349
|
+
if (regex)
|
|
350
|
+
return { command, params: Object.freeze({}), remaining: Object.freeze([]), helpRequested: false, regex };
|
|
351
|
+
}
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
311
354
|
#matchShortcut(input) {
|
|
312
355
|
const text = exactMessageText(input);
|
|
313
356
|
if (text === undefined)
|
|
@@ -322,14 +365,16 @@ export class CommandIndex {
|
|
|
322
365
|
: input);
|
|
323
366
|
if (segments.length === 0)
|
|
324
367
|
return undefined;
|
|
368
|
+
// segment-matcher clones its input before matching, so one copy serves every route.
|
|
369
|
+
const matcherSegments = asMatcherSegments(segments);
|
|
325
370
|
for (const route of this.#routes) {
|
|
326
371
|
// Options may appear between the literal route and a directory-bound parameter.
|
|
327
|
-
const literalResult = !exact ? route.literalMatcher?.match(
|
|
372
|
+
const literalResult = !exact ? route.literalMatcher?.match(matcherSegments) : undefined;
|
|
328
373
|
const helpRequested = literalResult && hasCommandBoundary(literalResult.remaining)
|
|
329
374
|
&& requestsCommandHelp(normalizeSegments(literalResult.remaining));
|
|
330
375
|
const parseDeclaredArguments = !exact && route.record.parameter && route.literalMatcher !== undefined
|
|
331
376
|
&& (Object.hasOwn(route.record.slot.definition.params ?? {}, route.record.parameter.name) || helpRequested);
|
|
332
|
-
const result = parseDeclaredArguments ? literalResult : route.matcher.match(
|
|
377
|
+
const result = parseDeclaredArguments ? literalResult : route.matcher.match(matcherSegments);
|
|
333
378
|
if (!result || !hasCommandBoundary(result.remaining))
|
|
334
379
|
continue;
|
|
335
380
|
const parameter = route.record.parameter;
|
|
@@ -380,7 +425,7 @@ export class CommandIndex {
|
|
|
380
425
|
// ==========================================================================
|
|
381
426
|
// 内置菜单命令
|
|
382
427
|
// ==========================================================================
|
|
383
|
-
#dispatchMenu(input, context) {
|
|
428
|
+
async #dispatchMenu(input, context) {
|
|
384
429
|
const { commandPrefix, keyword, locale } = context;
|
|
385
430
|
const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
|
|
386
431
|
if (text === undefined)
|
|
@@ -414,7 +459,7 @@ export class CommandIndex {
|
|
|
414
459
|
}
|
|
415
460
|
return undefined;
|
|
416
461
|
}
|
|
417
|
-
#buildMenu({ pluginKey, page = 1 }, { commandPrefix, keyword, snapshot, locale }) {
|
|
462
|
+
async #buildMenu({ pluginKey, page = 1 }, { commandPrefix, keyword, snapshot, locale, source }) {
|
|
418
463
|
const root = snapshot.root;
|
|
419
464
|
const targetId = pluginKey
|
|
420
465
|
? `${root}/${pluginKey.split('.').join('/')}`
|
|
@@ -422,15 +467,17 @@ export class CommandIndex {
|
|
|
422
467
|
const node = snapshot.tree.get(targetId);
|
|
423
468
|
if (!node)
|
|
424
469
|
return { value: commandMessages(locale, 'missingPlugin', { plugin: pluginKey ?? '' }) };
|
|
425
|
-
const
|
|
470
|
+
const listable = await Promise.all(this.#commands.map(async (cmd) => (await this.#listable(cmd, source, snapshot)) ? cmd : undefined));
|
|
471
|
+
const available = listable.filter((cmd) => cmd !== undefined);
|
|
472
|
+
const directCommands = available.filter((cmd) => cmd.slot.owner === targetId);
|
|
426
473
|
const children = node.children
|
|
427
474
|
.map((childId) => snapshot.tree.get(childId))
|
|
428
475
|
.filter((child) => !!child);
|
|
429
476
|
const effectiveCommands = targetId === root
|
|
430
477
|
&& directCommands.length === 0
|
|
431
|
-
&&
|
|
432
|
-
&&
|
|
433
|
-
?
|
|
478
|
+
&& available.length > 0
|
|
479
|
+
&& available.length <= MENU_PAGE_SIZE
|
|
480
|
+
? available
|
|
434
481
|
: directCommands;
|
|
435
482
|
const pages = Math.max(1, Math.ceil((effectiveCommands.length + children.length) / MENU_PAGE_SIZE));
|
|
436
483
|
const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
|
|
@@ -849,7 +896,14 @@ function textArgs(segments) {
|
|
|
849
896
|
return splitCommand(segment.data.text);
|
|
850
897
|
}));
|
|
851
898
|
}
|
|
852
|
-
|
|
899
|
+
/** A routed command whose `match` also fits the text exposes the match result too. */
|
|
900
|
+
function withPattern(match, input) {
|
|
901
|
+
const pattern = match.command.pattern;
|
|
902
|
+
const text = pattern ? exactMessageText(input) : undefined;
|
|
903
|
+
const regex = pattern && text !== undefined ? pattern.exec(text) : null;
|
|
904
|
+
return regex ? { ...match, regex } : match;
|
|
905
|
+
}
|
|
906
|
+
function toDescriptor({ slot: _slot, segments: _segments, parameter: _parameter, pattern: _pattern, ...descriptor }) {
|
|
853
907
|
return descriptor;
|
|
854
908
|
}
|
|
855
909
|
export class CommandParameterValueError extends TypeError {
|
package/lib/definition.d.ts
CHANGED
|
@@ -112,7 +112,19 @@ export interface CommandScene {
|
|
|
112
112
|
export interface CommandSender {
|
|
113
113
|
readonly id: string;
|
|
114
114
|
readonly name?: string;
|
|
115
|
-
|
|
115
|
+
/** Highest zhin chat role effective in this conversation (ADR 0056), set by the Runtime. */
|
|
116
|
+
readonly chatRole?: 'owner' | 'admin' | 'trusted';
|
|
117
|
+
/** Per role, the topmost level it is held at on this conversation's chain, set by the Runtime. */
|
|
118
|
+
readonly chatRoleLevels?: Readonly<Partial<Record<'owner' | 'admin' | 'trusted', 'global' | 'adapter' | 'endpoint' | 'conversation'>>>;
|
|
119
|
+
/** Normalized platform standing; absent in private chats. */
|
|
120
|
+
readonly platform?: Readonly<{
|
|
121
|
+
standing: 'owner' | 'admin' | 'member' | 'guest';
|
|
122
|
+
roles?: readonly Readonly<{
|
|
123
|
+
id: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
}>[];
|
|
126
|
+
permissions?: readonly string[];
|
|
127
|
+
}>;
|
|
116
128
|
}
|
|
117
129
|
/**
|
|
118
130
|
* 命令侧入站会话契约(结构对齐 `@zhin.js/im-contract` 的 ConversationRef;
|
|
@@ -147,7 +159,9 @@ export interface CommandMessage {
|
|
|
147
159
|
readonly sender?: {
|
|
148
160
|
readonly id: string;
|
|
149
161
|
readonly name?: string;
|
|
150
|
-
readonly
|
|
162
|
+
readonly chatRole?: CommandSender['chatRole'];
|
|
163
|
+
readonly chatRoleLevels?: CommandSender['chatRoleLevels'];
|
|
164
|
+
readonly platform?: CommandSender['platform'];
|
|
151
165
|
};
|
|
152
166
|
readonly id?: string;
|
|
153
167
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
@@ -240,6 +254,11 @@ export interface CommandContext<TConfig = unknown, TInput extends CommandMessage
|
|
|
240
254
|
*/
|
|
241
255
|
/** Canonical user-facing input/confirmation/selection module. */
|
|
242
256
|
readonly interaction?: UserInteraction;
|
|
257
|
+
/**
|
|
258
|
+
* 由 `match` 正则触发时的匹配结果(捕获组、具名组 `match.groups`)。
|
|
259
|
+
* 走指令名路由且正文同样符合 `match` 时也会填入;其余情况为 `undefined`。
|
|
260
|
+
*/
|
|
261
|
+
readonly match?: Readonly<RegExpExecArray>;
|
|
243
262
|
}
|
|
244
263
|
export interface CommandDefinition<TConfig = unknown, TResult = unknown, TInput extends CommandMessage = CommandMessage, TAdapter extends string | undefined = string | undefined> {
|
|
245
264
|
/** @internal Runtime feature brand. */
|
|
@@ -265,7 +284,7 @@ export interface CommandDefinition<TConfig = unknown, TResult = unknown, TInput
|
|
|
265
284
|
readonly preferredAliasI18n?: Readonly<Partial<Record<Locale, string>>>;
|
|
266
285
|
/**
|
|
267
286
|
* 内置 permit DSL(AND)。单项内逗号为 OR。
|
|
268
|
-
* 例:`adapter(icqq)`、`
|
|
287
|
+
* 例:`adapter(icqq)`、`owner`、`trusted`、`platform:admin`、`group(123,456)`。
|
|
269
288
|
*/
|
|
270
289
|
readonly permit?: readonly string[];
|
|
271
290
|
/** Optional full-operation cooldown, concurrency and one-shot progress feedback. */
|
|
@@ -275,6 +294,17 @@ export interface CommandDefinition<TConfig = unknown, TResult = unknown, TInput
|
|
|
275
294
|
* 与普通路由一样是全局用户输入,冲突会在 generation 构建时拒绝。
|
|
276
295
|
*/
|
|
277
296
|
readonly shortcut?: Readonly<Record<string, Readonly<Record<string, CommandDynamicValue>>>>;
|
|
297
|
+
/**
|
|
298
|
+
* 正则触发:消息正文(去掉指令前缀并 trim 后、只含文本时)符合此正则即执行本指令,
|
|
299
|
+
* 匹配结果见 `context.match`。指令名路由照常可用,先按路由匹配,路由不中再试正则;
|
|
300
|
+
* 多条指令的正则都符合时取指令列表顺序中的第一条。不允许 `g` / `y` 标志(有状态)。
|
|
301
|
+
*/
|
|
302
|
+
readonly match?: RegExp;
|
|
303
|
+
/**
|
|
304
|
+
* 固定回复:与 `execute` 二选一,等价于 `execute: () => reply`。
|
|
305
|
+
* 可以是文本、消息段或 JSX 等任何 `execute` 可返回的内容。
|
|
306
|
+
*/
|
|
307
|
+
readonly reply?: TResult;
|
|
278
308
|
execute(context: CommandContext<TConfig, TInput, TAdapter>): TResult | Promise<TResult>;
|
|
279
309
|
}
|
|
280
310
|
declare module '@zhin.js/plugin-runtime' {
|
|
@@ -286,18 +316,23 @@ declare module '@zhin.js/plugin-runtime' {
|
|
|
286
316
|
* 定义一个命令模块(`commands/` 约定目录下默认导出)。
|
|
287
317
|
* @public 用户侧创作面,承诺 semver(见 docs/contributing/public-api-surface.md)。
|
|
288
318
|
*/
|
|
289
|
-
type CommandAuthoringDefinition<TConfig, TResult, TInput extends CommandMessage, P extends CommandParams, O extends CommandOptions> =
|
|
319
|
+
type CommandAuthoringDefinition<TConfig, TResult, TInput extends CommandMessage, P extends CommandParams, O extends CommandOptions> = Omit<CommandDefinition<TConfig, TResult, TInput, undefined>, '$feature' | '$parameter' | 'params' | 'options' | 'execute' | 'reply'> & {
|
|
290
320
|
readonly params?: P;
|
|
291
321
|
readonly options?: O;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
[TAdapter in RegisteredAdapterName]: Omit<CommandDefinition<TConfig, TResult, TInput, TAdapter>, '$feature' | '$parameter' | 'params' | 'options' | 'execute'> & {
|
|
322
|
+
} & CommandBody<TConfig, TResult, TInput, undefined, P, O> | {
|
|
323
|
+
[TAdapter in RegisteredAdapterName]: Omit<CommandDefinition<TConfig, TResult, TInput, TAdapter>, '$feature' | '$parameter' | 'params' | 'options' | 'execute' | 'reply'> & {
|
|
295
324
|
readonly adapter: TAdapter;
|
|
296
325
|
readonly params?: P;
|
|
297
326
|
readonly options?: O;
|
|
298
|
-
|
|
299
|
-
};
|
|
327
|
+
} & CommandBody<TConfig, TResult, TInput, TAdapter, P, O>;
|
|
300
328
|
}[RegisteredAdapterName];
|
|
329
|
+
/** `execute` 与固定回复 `reply` 二选一。 */
|
|
330
|
+
type CommandBody<TConfig, TResult, TInput extends CommandMessage, TAdapter extends string | undefined, P extends CommandParams, O extends CommandOptions> = {
|
|
331
|
+
execute(context: TypedCommandContext<TConfig, TInput, TAdapter, P, O>): TResult | Promise<TResult>;
|
|
332
|
+
} | {
|
|
333
|
+
readonly reply: TResult;
|
|
334
|
+
readonly execute?: undefined;
|
|
335
|
+
};
|
|
301
336
|
type TypedCommandContext<TConfig, TInput extends CommandMessage, TAdapter extends string | undefined, P extends CommandParams, O extends CommandOptions> = Omit<CommandContext<TConfig, TInput, TAdapter>, 'params' | 'options'> & {
|
|
302
337
|
readonly params: keyof P extends never ? Readonly<Record<string, CommandParameterValue>> : InferCommandParams<P>;
|
|
303
338
|
readonly options: InferCommandOptions<O>;
|
|
@@ -325,6 +360,8 @@ export declare namespace defineCommand {
|
|
|
325
360
|
}
|
|
326
361
|
/** @internal Validate display-only translations without changing their fallback text. */
|
|
327
362
|
export declare function validateDescriptionI18n(value: CommandDefinition['descriptionI18n']): void;
|
|
363
|
+
/** @internal `match` is tested many times per generation, so stateful flags would skip matches. */
|
|
364
|
+
export declare function validateCommandMatch(match: unknown): void;
|
|
328
365
|
/** @internal Convention-loader assembly helper. */
|
|
329
366
|
export declare function bindCommandParameter<TConfig, TResult, TInput extends CommandMessage, TAdapter extends string | undefined>(definition: CommandDefinition<TConfig, TResult, TInput, TAdapter>, parameter: CommandParameterDefinition | undefined): Readonly<CommandDefinition<TConfig, TResult, TInput, TAdapter>>;
|
|
330
367
|
/** @internal Runtime validation for convention-discovered modules. */
|
|
@@ -341,6 +378,7 @@ export interface CommandInvocationArguments {
|
|
|
341
378
|
readonly params?: Readonly<Record<string, CommandParameterValue>>;
|
|
342
379
|
readonly options?: Readonly<Record<string, CommandParameterValue>>;
|
|
343
380
|
readonly segments?: readonly Readonly<CommandSegment>[];
|
|
381
|
+
readonly match?: RegExpExecArray;
|
|
344
382
|
}
|
|
345
383
|
/** Author and lifetime authority captured by the dispatcher for this invocation. */
|
|
346
384
|
export interface CommandInvocationOperation {
|
package/lib/definition.js
CHANGED
|
@@ -26,9 +26,7 @@ export const commandParameterTypes = new Set([
|
|
|
26
26
|
'rps',
|
|
27
27
|
]);
|
|
28
28
|
export function defineCommand(definition) {
|
|
29
|
-
|
|
30
|
-
throw new TypeError('Command execute must be a function');
|
|
31
|
-
}
|
|
29
|
+
const body = normalizeCommandBody(definition);
|
|
32
30
|
validateAdapterName(definition.adapter);
|
|
33
31
|
if (definition.params !== undefined) {
|
|
34
32
|
if (!definition.params || typeof definition.params !== 'object') {
|
|
@@ -47,8 +45,9 @@ export function defineCommand(definition) {
|
|
|
47
45
|
validatePreferredAliases(definition);
|
|
48
46
|
validateCommandPermit(definition.permit);
|
|
49
47
|
validateCommandShortcutShape(definition.shortcut);
|
|
48
|
+
validateCommandMatch(definition.match);
|
|
50
49
|
const execution = normalizeExecutionPolicy(definition.execution);
|
|
51
|
-
return Object.freeze({ $feature: commandBrand, ...definition, ...(execution ? { execution } : {}) });
|
|
50
|
+
return Object.freeze({ $feature: commandBrand, ...definition, ...body, ...(execution ? { execution } : {}) });
|
|
52
51
|
}
|
|
53
52
|
/** @internal Validate display-only translations without changing their fallback text. */
|
|
54
53
|
export function validateDescriptionI18n(value) {
|
|
@@ -77,6 +76,32 @@ export function validateDescriptionI18n(value) {
|
|
|
77
76
|
defineCommand.withConfig = function withConfig() {
|
|
78
77
|
return (definition) => defineCommand(definition);
|
|
79
78
|
};
|
|
79
|
+
function normalizeCommandBody(definition) {
|
|
80
|
+
const hasReply = Object.hasOwn(definition, 'reply') && definition.reply !== undefined;
|
|
81
|
+
if (typeof definition.execute === 'function') {
|
|
82
|
+
if (hasReply)
|
|
83
|
+
throw new TypeError('Command declares both execute and reply; use one of them');
|
|
84
|
+
return { execute: definition.execute };
|
|
85
|
+
}
|
|
86
|
+
if (definition.execute !== undefined)
|
|
87
|
+
throw new TypeError('Command execute must be a function');
|
|
88
|
+
if (!hasReply)
|
|
89
|
+
throw new TypeError('Command needs execute or reply');
|
|
90
|
+
if (typeof definition.reply === 'function') {
|
|
91
|
+
throw new TypeError('Command reply is a fixed value; pass a function as execute instead');
|
|
92
|
+
}
|
|
93
|
+
const reply = definition.reply;
|
|
94
|
+
return { execute: () => reply };
|
|
95
|
+
}
|
|
96
|
+
/** @internal `match` is tested many times per generation, so stateful flags would skip matches. */
|
|
97
|
+
export function validateCommandMatch(match) {
|
|
98
|
+
if (match === undefined)
|
|
99
|
+
return;
|
|
100
|
+
if (!(match instanceof RegExp))
|
|
101
|
+
throw new TypeError('Command match must be a RegExp');
|
|
102
|
+
if (match.global || match.sticky)
|
|
103
|
+
throw new TypeError('Command match must not use the g or y flag');
|
|
104
|
+
}
|
|
80
105
|
function validateAdapterName(adapter) {
|
|
81
106
|
if (adapter !== undefined && (typeof adapter !== 'string' || adapter.trim() === '')) {
|
|
82
107
|
throw new TypeError('Command adapter must be a non-empty string');
|
|
@@ -176,7 +201,7 @@ export function resolveDynamicParams(params, source) {
|
|
|
176
201
|
/** @internal Command dispatcher assembly helper; each value has one owner. */
|
|
177
202
|
export function createCommandContext(operation, invocation) {
|
|
178
203
|
const { snapshot, ownerId, input, interaction, adapter, presentation } = operation;
|
|
179
|
-
const { args, params = {}, options = {}, segments = [] } = invocation;
|
|
204
|
+
const { args, params = {}, options = {}, segments = [], match } = invocation;
|
|
180
205
|
const context = createCapabilityContext({ snapshot, ownerId, signal: readOperationInputSignal(input), operation: operation.operation });
|
|
181
206
|
const session = resolveCommandSession(input);
|
|
182
207
|
const parameterValues = { __proto__: null, ...params };
|
|
@@ -192,6 +217,7 @@ export function createCommandContext(operation, invocation) {
|
|
|
192
217
|
segments: freezeSegments(segments),
|
|
193
218
|
...(input !== undefined ? { input: input } : {}),
|
|
194
219
|
...(interaction !== undefined ? { interaction } : {}),
|
|
220
|
+
...(match !== undefined ? { match: Object.freeze(match) } : {}),
|
|
195
221
|
};
|
|
196
222
|
Object.defineProperty(result, 'session', {
|
|
197
223
|
enumerable: true,
|
|
@@ -213,14 +239,12 @@ export function resolveCommandSession(input) {
|
|
|
213
239
|
if (operation) {
|
|
214
240
|
const endpoint = input && typeof input === 'object' ? input.endpointId : undefined;
|
|
215
241
|
const sender = operation.sender;
|
|
216
|
-
const roles = [...new Set([...sender?.authorizationRoles ?? [], ...sender?.platformRoles ?? []])];
|
|
217
242
|
return Object.freeze({
|
|
218
243
|
adapter: operation.scene.endpoint.adapter,
|
|
219
244
|
...(typeof endpoint === 'string' && endpoint ? { endpoint } : {}),
|
|
220
245
|
scene: Object.freeze({ id: operation.scene.id, type: operation.scene.kind,
|
|
221
246
|
...(operation.scene.name !== undefined ? { name: operation.scene.name } : {}) }),
|
|
222
|
-
...(sender ? { sender:
|
|
223
|
-
role: Object.freeze(roles.length ? roles : ['user']) }) } : {}),
|
|
247
|
+
...(sender ? { sender: freezeSender(sender) } : {}),
|
|
224
248
|
});
|
|
225
249
|
}
|
|
226
250
|
if (!isCommandMessageLike(input))
|
|
@@ -281,38 +305,8 @@ export function resolveCommandSender(input, metadata) {
|
|
|
281
305
|
if (!id)
|
|
282
306
|
return undefined;
|
|
283
307
|
const name = input.sender?.name || firstString(metadata?.nickname, metadata?.senderName, metadata?.name);
|
|
284
|
-
|
|
285
|
-
return
|
|
286
|
-
id,
|
|
287
|
-
...(name !== undefined ? { name } : {}),
|
|
288
|
-
role,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
function resolveRoles(input, metadata) {
|
|
292
|
-
if (input.sender?.roles?.length) {
|
|
293
|
-
return Object.freeze([...input.sender.roles]);
|
|
294
|
-
}
|
|
295
|
-
const roles = [];
|
|
296
|
-
const push = (value) => {
|
|
297
|
-
if (typeof value !== 'string')
|
|
298
|
-
return;
|
|
299
|
-
const trimmed = value.trim();
|
|
300
|
-
if (trimmed && !roles.includes(trimmed))
|
|
301
|
-
roles.push(trimmed);
|
|
302
|
-
};
|
|
303
|
-
if (Array.isArray(metadata?.roles)) {
|
|
304
|
-
for (const item of metadata.roles)
|
|
305
|
-
push(item);
|
|
306
|
-
}
|
|
307
|
-
push(metadata?.senderRole);
|
|
308
|
-
push(metadata?.role);
|
|
309
|
-
if (metadata?.isMaster === true)
|
|
310
|
-
push('master');
|
|
311
|
-
if (metadata?.isTrusted === true)
|
|
312
|
-
push('trusted');
|
|
313
|
-
if (roles.length === 0)
|
|
314
|
-
roles.push('user');
|
|
315
|
-
return Object.freeze(roles);
|
|
308
|
+
// Roles come only from the Runtime-set sender fields, never from metadata (ADR 0056).
|
|
309
|
+
return freezeSender({ ...(input.sender ?? {}), id, ...(name !== undefined ? { name } : {}) });
|
|
316
310
|
}
|
|
317
311
|
function isCommandScene(value) {
|
|
318
312
|
if (!value || typeof value !== 'object')
|
|
@@ -329,14 +323,31 @@ function isCommandSender(value) {
|
|
|
329
323
|
const sender = value;
|
|
330
324
|
return typeof sender.id === 'string'
|
|
331
325
|
&& sender.id.length > 0
|
|
332
|
-
&&
|
|
333
|
-
|
|
326
|
+
&& (sender.chatRole === undefined || isChatRoleName(sender.chatRole));
|
|
327
|
+
}
|
|
328
|
+
function isChatRoleName(value) {
|
|
329
|
+
return value === 'owner' || value === 'admin' || value === 'trusted';
|
|
330
|
+
}
|
|
331
|
+
const STANDINGS = new Set(['owner', 'admin', 'member', 'guest']);
|
|
332
|
+
const LEVELS = new Set(['global', 'adapter', 'endpoint', 'conversation']);
|
|
333
|
+
function chatRoleLevelsOf(value) {
|
|
334
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
335
|
+
return undefined;
|
|
336
|
+
const levels = Object.fromEntries(['owner', 'admin', 'trusted']
|
|
337
|
+
.filter(role => LEVELS.has(String(value[role])))
|
|
338
|
+
.map(role => [role, value[role]]));
|
|
339
|
+
return Object.keys(levels).length ? Object.freeze(levels) : undefined;
|
|
334
340
|
}
|
|
335
341
|
function freezeSender(sender) {
|
|
342
|
+
const platform = sender.platform && typeof sender.platform === 'object'
|
|
343
|
+
&& STANDINGS.has(String(sender.platform.standing))
|
|
344
|
+
? sender.platform : undefined;
|
|
336
345
|
return Object.freeze({
|
|
337
346
|
id: sender.id,
|
|
338
347
|
...(sender.name !== undefined ? { name: sender.name } : {}),
|
|
339
|
-
|
|
348
|
+
...(isChatRoleName(sender.chatRole) ? { chatRole: sender.chatRole } : {}),
|
|
349
|
+
...(isChatRoleName(sender.chatRole) && chatRoleLevelsOf(sender.chatRoleLevels) ? { chatRoleLevels: chatRoleLevelsOf(sender.chatRoleLevels) } : {}),
|
|
350
|
+
...(platform ? { platform } : {}),
|
|
340
351
|
});
|
|
341
352
|
}
|
|
342
353
|
function firstString(...values) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Internal composition contract. Not part of the command authoring facade. */
|
|
2
|
-
import { type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { type Locale, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import type { CommandExecutionLimit, CommandExecutionPolicy } from './execution-policy.js';
|
|
4
4
|
interface ConversationIdentity {
|
|
5
5
|
readonly endpoint: {
|
|
@@ -55,6 +55,8 @@ export declare class CommandExecutionBudget {
|
|
|
55
55
|
readonly conversation: ConversationIdentity;
|
|
56
56
|
readonly senderId?: string;
|
|
57
57
|
readonly retirementSignal: AbortSignal;
|
|
58
|
+
/** The operation's language for progress feedback; defaults to the catalog default. */
|
|
59
|
+
readonly locale?: Locale;
|
|
58
60
|
readonly reply: (owner: PluginId, text: string) => Promise<unknown>;
|
|
59
61
|
}): CommandOperationController;
|
|
60
62
|
}
|
package/lib/execution-runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Internal composition contract. Not part of the command authoring facade. */
|
|
2
2
|
import { createToken } from '@zhin.js/plugin-runtime';
|
|
3
|
+
import { commandMessages } from './messages.js';
|
|
3
4
|
/** @internal Admission-only Root resource; no bind or finish authority is published. */
|
|
4
5
|
export const commandExecutionAdmissionToken = createToken('zhin.command.execution-admission');
|
|
5
6
|
/** @internal Root-instance-owned state shared by projections and draining retired generations. */
|
|
@@ -26,7 +27,7 @@ export class CommandExecutionBudget {
|
|
|
26
27
|
const operation = {
|
|
27
28
|
snapshot: input.snapshot, key: valid ? Object.freeze(values) : undefined,
|
|
28
29
|
active: true, progressStopped: false, admitted: false, signal: input.retirementSignal,
|
|
29
|
-
reply: input.reply, releases: [], timers: new Set(), feedback: [], accepted: new Set(), sends: new Set(),
|
|
30
|
+
locale: input.locale, reply: input.reply, releases: [], timers: new Set(), feedback: [], accepted: new Set(), sends: new Set(),
|
|
30
31
|
};
|
|
31
32
|
const stop = () => {
|
|
32
33
|
operation.progressStopped = true;
|
|
@@ -136,7 +137,7 @@ export class CommandExecutionBudget {
|
|
|
136
137
|
// Observe failure once. Never retry uncertain delivery and never expose the exception.
|
|
137
138
|
let send;
|
|
138
139
|
try {
|
|
139
|
-
send = Promise.resolve(operation.reply(owner, '
|
|
140
|
+
send = Promise.resolve(operation.reply(owner, commandMessages(operation.locale, 'progress'))).then(() => { }, () => { });
|
|
140
141
|
}
|
|
141
142
|
catch {
|
|
142
143
|
send = Promise.resolve();
|
package/lib/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from './command-index.js';
|
|
|
7
7
|
/** @public Command authoring contract and `defineCommand`. */
|
|
8
8
|
export * from './definition.js';
|
|
9
9
|
export * from './permission-explanation.js';
|
|
10
|
-
export { CONVERSATION_MANAGER_PERMIT, canManageConversation, assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, isBuiltinPermit,
|
|
10
|
+
export { CONVERSATION_MANAGER_PERMIT, canManageConversation, assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, isBuiltinPermit, removedPermitReplacement, parsePermitName, type ParsedPermit, type PermitKind, } from './permit.js';
|
|
11
11
|
export { bindCommandSource, CommandPathSyntaxError, commandFeatureId, type BoundCommandSource, default as commandFeature, } from './provider.js';
|
|
12
12
|
export { default } from './provider.js';
|
|
13
13
|
export type { CommandValidationIssue, CommandValidationIssueCode } from './arguments.js';
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,6 @@ export * from './command-index.js';
|
|
|
7
7
|
/** @public Command authoring contract and `defineCommand`. */
|
|
8
8
|
export * from './definition.js';
|
|
9
9
|
export * from './permission-explanation.js';
|
|
10
|
-
export { CONVERSATION_MANAGER_PERMIT, canManageConversation, assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, isBuiltinPermit,
|
|
10
|
+
export { CONVERSATION_MANAGER_PERMIT, canManageConversation, assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, isBuiltinPermit, removedPermitReplacement, parsePermitName, } from './permit.js';
|
|
11
11
|
export { bindCommandSource, CommandPathSyntaxError, commandFeatureId, default as commandFeature, } from './provider.js';
|
|
12
12
|
export { default } from './provider.js';
|
package/lib/messages.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export declare function commandDescription(value: Readonly<{
|
|
|
5
5
|
descriptionI18n?: Readonly<Partial<Record<Locale, string>>>;
|
|
6
6
|
}>, locale: Locale): string | undefined;
|
|
7
7
|
/** Static dictionaries; the caller supplies the operation's language. */
|
|
8
|
-
export declare const commandMessages: (locale: Locale | undefined, key: "choices" | "defaultValue" | "menuKeyword" | "invalidPage" | "missingPlugin" | "pageOutOfRange" | "menuTitle" | "menuSummary" | "childPlugins" | "emptyMenu" | "pluginHint" | "previousPage" | "nextPage" | "guideTitle" | "guideSubtitle" | "commandCount" | "pluginCount" | "commandIndex" | "pluginIndex" | "guideEmpty" | "helpHint" | "genericHelpHint" | "expectedType" | "expectedArray" | "minimum" | "maximum" | "invalidFormat" | "invalidValue" | "unknownOption" | "duplicateOption" | "defaultUndefined" | "parameterUndefined" | "missingOption" | "missingRequiredParam" | "unexpectedArguments" | "invalidParameter" | "dynamicValue" | "noAdapter" | "missingEndpoint" | "adapterComparison" | "conditionMismatch" | "missingPermissionHost" | "missingEvidence" | "noPermit" | "uncheckedConditions" | "conditionsMatch" | "unconstrained" | "simulatedIdentity" | "unevaluatedCode" | "defaultedRole", values?: import("@zhin.js/plugin-runtime").MessageValues) => string;
|
|
8
|
+
export declare const commandMessages: (locale: Locale | undefined, key: "choices" | "defaultValue" | "menuKeyword" | "progress" | "invalidPage" | "missingPlugin" | "pageOutOfRange" | "menuTitle" | "menuSummary" | "childPlugins" | "emptyMenu" | "pluginHint" | "previousPage" | "nextPage" | "guideTitle" | "guideSubtitle" | "commandCount" | "pluginCount" | "commandIndex" | "pluginIndex" | "guideEmpty" | "helpHint" | "genericHelpHint" | "expectedType" | "expectedArray" | "minimum" | "maximum" | "invalidFormat" | "invalidValue" | "unknownOption" | "duplicateOption" | "defaultUndefined" | "parameterUndefined" | "missingOption" | "missingRequiredParam" | "unexpectedArguments" | "invalidParameter" | "dynamicValue" | "noAdapter" | "missingEndpoint" | "adapterComparison" | "conditionMismatch" | "missingPermissionHost" | "missingEvidence" | "noPermit" | "uncheckedConditions" | "conditionsMatch" | "unconstrained" | "simulatedIdentity" | "unevaluatedCode" | "defaultedRole", values?: import("@zhin.js/plugin-runtime").MessageValues) => string;
|