@zhin.js/command 1.1.13 → 1.1.15
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/lib/arguments.d.ts +1 -1
- package/lib/arguments.js +2 -2
- package/lib/command-index.d.ts +3 -1
- package/lib/command-index.js +42 -16
- package/lib/definition.d.ts +2 -1
- package/lib/definition.js +1 -1
- package/lib/messages.d.ts +1 -1
- package/lib/messages.js +1 -0
- package/package.json +4 -4
- package/src/arguments.ts +3 -3
- package/src/command-index.ts +43 -19
- package/src/definition.ts +3 -1
- package/src/messages.ts +1 -0
package/lib/arguments.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CommandDefinition, CommandParams, CommandOptions, CommandParameterValue, CommandSegment, CommandSession } from './definition.js';
|
|
2
2
|
import type { Locale } from '@zhin.js/plugin-runtime';
|
|
3
|
-
export type CommandValidationIssueCode = 'expectedType' | 'expectedArray' | 'minimum' | 'maximum' | 'choices' | 'invalidFormat' | 'invalidValue' | 'unknownOption' | 'duplicateOption' | 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'unexpectedArguments' | 'invalidParameter' | 'custom';
|
|
3
|
+
export type CommandValidationIssueCode = 'expectedType' | 'expectedArray' | 'minimum' | 'maximum' | 'choices' | 'invalidFormat' | 'invalidValue' | 'unknownOption' | 'duplicateOption' | 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'missingRequiredParam' | 'unexpectedArguments' | 'invalidParameter' | 'custom';
|
|
4
4
|
export interface CommandValidationIssue {
|
|
5
5
|
readonly field: string;
|
|
6
6
|
readonly message: string;
|
package/lib/arguments.js
CHANGED
|
@@ -105,7 +105,7 @@ export function requestsCommandHelp(segments) {
|
|
|
105
105
|
continue;
|
|
106
106
|
if (token.value === '--')
|
|
107
107
|
return false;
|
|
108
|
-
if (token.value === '--help')
|
|
108
|
+
if (token.value === '--help' || token.value === '-h')
|
|
109
109
|
return true;
|
|
110
110
|
}
|
|
111
111
|
return false;
|
|
@@ -300,7 +300,7 @@ export function parseCommandArguments(definition, remaining, initial, session, l
|
|
|
300
300
|
else if (schema.default !== undefined)
|
|
301
301
|
params[name] = typeof schema.default === 'function' ? schema.default(session) : schema.default;
|
|
302
302
|
else if (!schema.optional && !(definition.$parameter?.name === name && definition.$parameter.optional)) {
|
|
303
|
-
issues.push(commandValidationIssue('
|
|
303
|
+
issues.push(commandValidationIssue('missingRequiredParam', name, locale, { type: schema.type }));
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
if (Object.hasOwn(params, name) && params[name] === undefined)
|
package/lib/command-index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CapabilityId, type CapabilitySlot, type PluginId, type RuntimeSnapshot, type Locale } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import type { UserInteractionFactory } from '@zhin.js/interaction';
|
|
3
|
+
import { type CapabilityOperationFactory } from '@zhin.js/feature-kit';
|
|
3
4
|
import { type CommandValidationIssue } from './arguments.js';
|
|
4
5
|
import { type CommandMenuEntry, type CommandMenuAppearance } from './command-menu.js';
|
|
5
6
|
export type { CommandMenuEntry, CommandMenuAppearance } from './command-menu.js';
|
|
@@ -32,6 +33,7 @@ export interface CommandDispatchRequest {
|
|
|
32
33
|
readonly interactionFactory?: UserInteractionFactory;
|
|
33
34
|
readonly commandPrefix?: string;
|
|
34
35
|
readonly snapshot?: RuntimeSnapshot;
|
|
36
|
+
readonly operation?: CapabilityOperationFactory;
|
|
35
37
|
readonly locale?: Locale;
|
|
36
38
|
}
|
|
37
39
|
export interface CommandDispatchResult {
|
|
@@ -81,7 +83,7 @@ export declare class CommandIndex {
|
|
|
81
83
|
formatMenu(prefix: string | undefined, locale: Locale): string | undefined;
|
|
82
84
|
has(name: string): boolean;
|
|
83
85
|
execute(name: string, args?: readonly string[], operationSnapshot?: RuntimeSnapshot): Promise<unknown>;
|
|
84
|
-
dispatch({ input, source, interactionFactory, commandPrefix, snapshot: operationSnapshot, locale: localeOverride }: CommandDispatchRequest): Promise<CommandDispatchResult>;
|
|
86
|
+
dispatch({ input, source, interactionFactory, commandPrefix, snapshot: operationSnapshot, locale: localeOverride, operation: operationFactory }: CommandDispatchRequest): Promise<CommandDispatchResult>;
|
|
85
87
|
}
|
|
86
88
|
export declare function isCommandIndex(value: unknown): value is CommandIndex;
|
|
87
89
|
/**
|
package/lib/command-index.js
CHANGED
|
@@ -21,6 +21,7 @@ const segmentFields = {
|
|
|
21
21
|
dice: 'result',
|
|
22
22
|
rps: 'result',
|
|
23
23
|
};
|
|
24
|
+
const DEFAULT_MENU_KEYWORDS = new Set(['菜单', '帮助', 'help', 'menu']);
|
|
24
25
|
export class CommandIndex {
|
|
25
26
|
snapshot;
|
|
26
27
|
$projection = 'zhin.command-index/1';
|
|
@@ -195,7 +196,7 @@ export class CommandIndex {
|
|
|
195
196
|
admission.release();
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
|
-
async dispatch({ input, source, interactionFactory, commandPrefix, snapshot: operationSnapshot = this.snapshot, locale: localeOverride }) {
|
|
199
|
+
async dispatch({ input, source, interactionFactory, commandPrefix, snapshot: operationSnapshot = this.snapshot, locale: localeOverride, operation: operationFactory }) {
|
|
199
200
|
const locale = localeOverride ?? commandLocale(source, operationSnapshot);
|
|
200
201
|
const prefix = commandPrefix ?? '';
|
|
201
202
|
if (this.#menu) {
|
|
@@ -247,12 +248,22 @@ export class CommandIndex {
|
|
|
247
248
|
const limit = admitCommandExecution(authorSource, match.command.slot.id, match.command.slot.owner, match.command.execution, operationSnapshot);
|
|
248
249
|
if (limit)
|
|
249
250
|
return Object.freeze({ matched: true, status: 'limited', command: match.command.name, owner: match.command.slot.owner, limit });
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
const operation = operationFactory?.create({ owner: match.command.slot.owner, signal: operationFactory.signal });
|
|
252
|
+
try {
|
|
253
|
+
const value = await definition.execute(createCommandContext({
|
|
254
|
+
snapshot: operationSnapshot, ownerId: match.command.slot.owner, input: authorSource,
|
|
255
|
+
operation,
|
|
256
|
+
interaction: interactionFactory?.(authorSource), adapter: definition.adapter,
|
|
257
|
+
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 }));
|
|
259
|
+
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value });
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
try {
|
|
263
|
+
await operation?.resources?.close();
|
|
264
|
+
}
|
|
265
|
+
catch { /* Cleanup never replaces the command's actual outcome. */ }
|
|
266
|
+
}
|
|
256
267
|
}
|
|
257
268
|
#denied(record) {
|
|
258
269
|
return Object.freeze({ matched: true, status: 'denied', command: record.name, owner: record.slot.owner });
|
|
@@ -374,10 +385,19 @@ export class CommandIndex {
|
|
|
374
385
|
const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
|
|
375
386
|
if (text === undefined)
|
|
376
387
|
return undefined;
|
|
377
|
-
|
|
388
|
+
const spaceIndex = text.search(/\s/u);
|
|
389
|
+
const firstToken = spaceIndex === -1 ? text : text.slice(0, spaceIndex);
|
|
390
|
+
const normalizedFirst = !commandPrefix && firstToken.startsWith('/')
|
|
391
|
+
? firstToken.slice(1)
|
|
392
|
+
: firstToken;
|
|
393
|
+
const matchedDefaultAlias = this.#menu?.keyword === undefined
|
|
394
|
+
&& DEFAULT_MENU_KEYWORDS.has(normalizedFirst.toLowerCase());
|
|
395
|
+
if (text === keyword || (matchedDefaultAlias && spaceIndex === -1))
|
|
378
396
|
return this.#buildMenu({}, context);
|
|
379
|
-
if (text.startsWith(keyword + ' ')) {
|
|
380
|
-
const key = text.
|
|
397
|
+
if (text.startsWith(keyword + ' ') || (matchedDefaultAlias && spaceIndex !== -1)) {
|
|
398
|
+
const key = (text.startsWith(keyword + ' ')
|
|
399
|
+
? text.slice(keyword.length + 1)
|
|
400
|
+
: text.slice(spaceIndex + 1)).trim();
|
|
381
401
|
const args = key.split(/\s+/u);
|
|
382
402
|
const flag = args.indexOf('--page');
|
|
383
403
|
if (flag !== -1) {
|
|
@@ -406,17 +426,23 @@ export class CommandIndex {
|
|
|
406
426
|
const children = node.children
|
|
407
427
|
.map((childId) => snapshot.tree.get(childId))
|
|
408
428
|
.filter((child) => !!child);
|
|
409
|
-
const
|
|
429
|
+
const effectiveCommands = targetId === root
|
|
430
|
+
&& directCommands.length === 0
|
|
431
|
+
&& this.#commands.length > 0
|
|
432
|
+
&& this.#commands.length <= MENU_PAGE_SIZE
|
|
433
|
+
? this.#commands
|
|
434
|
+
: directCommands;
|
|
435
|
+
const pages = Math.max(1, Math.ceil((effectiveCommands.length + children.length) / MENU_PAGE_SIZE));
|
|
410
436
|
const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
|
|
411
437
|
if (page > pages)
|
|
412
438
|
return { value: commandMessages(locale, 'pageOutOfRange', { pages, command: invocation }) };
|
|
413
439
|
const offset = (page - 1) * MENU_PAGE_SIZE;
|
|
414
|
-
const pageCommands =
|
|
415
|
-
const pageChildren = children.slice(Math.max(0, offset -
|
|
440
|
+
const pageCommands = effectiveCommands.slice(offset, offset + MENU_PAGE_SIZE);
|
|
441
|
+
const pageChildren = children.slice(Math.max(0, offset - effectiveCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - effectiveCommands.length));
|
|
416
442
|
const displayKey = pluginKey ?? node.instanceKey;
|
|
417
443
|
const lines = [commandMessages(locale, 'menuTitle', { plugin: displayKey })];
|
|
418
444
|
if (pages > 1)
|
|
419
|
-
lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands:
|
|
445
|
+
lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: effectiveCommands.length, plugins: children.length }));
|
|
420
446
|
if (pageCommands.length > 0) {
|
|
421
447
|
lines.push('');
|
|
422
448
|
for (const cmd of pageCommands) {
|
|
@@ -434,7 +460,7 @@ export class CommandIndex {
|
|
|
434
460
|
lines.push(` ${childKey} (${label})`);
|
|
435
461
|
}
|
|
436
462
|
}
|
|
437
|
-
if (
|
|
463
|
+
if (effectiveCommands.length === 0 && children.length === 0) {
|
|
438
464
|
lines.push('');
|
|
439
465
|
lines.push(commandMessages(locale, 'emptyMenu'));
|
|
440
466
|
}
|
|
@@ -461,7 +487,7 @@ export class CommandIndex {
|
|
|
461
487
|
name: pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey,
|
|
462
488
|
description: child.metadata?.displayName ?? child.instanceKey,
|
|
463
489
|
}))),
|
|
464
|
-
commandTotal:
|
|
490
|
+
commandTotal: effectiveCommands.length, pluginTotal: children.length, hint, text,
|
|
465
491
|
appearance: Object.freeze(readMenuAppearance(config?.menuStyle)),
|
|
466
492
|
});
|
|
467
493
|
if (!snapshot.resources.get(root)?.has(htmlRendererToken.id))
|
package/lib/definition.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module zhin.js/command
|
|
4
4
|
*/
|
|
5
5
|
import { type Locale, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
6
|
-
import { type OperationSession, type AdapterClient, type RegisteredAdapterName, type CapabilityContext } from '@zhin.js/feature-kit';
|
|
6
|
+
import { type CapabilityOperation, type OperationSession, type AdapterClient, type RegisteredAdapterName, type CapabilityContext } from '@zhin.js/feature-kit';
|
|
7
7
|
import type { UserInteraction } from '@zhin.js/interaction';
|
|
8
8
|
import { type CommandExecutionPolicy } from './execution-policy.js';
|
|
9
9
|
export type { CommandExecutionPolicy, CommandExecutionLimit } from './execution-policy.js';
|
|
@@ -346,6 +346,7 @@ export interface CommandInvocationArguments {
|
|
|
346
346
|
export interface CommandInvocationOperation {
|
|
347
347
|
readonly snapshot: RuntimeSnapshot;
|
|
348
348
|
readonly ownerId: PluginId;
|
|
349
|
+
readonly operation?: CapabilityOperation;
|
|
349
350
|
readonly input?: unknown;
|
|
350
351
|
readonly interaction?: UserInteraction;
|
|
351
352
|
readonly adapter?: string;
|
package/lib/definition.js
CHANGED
|
@@ -177,7 +177,7 @@ export function resolveDynamicParams(params, source) {
|
|
|
177
177
|
export function createCommandContext(operation, invocation) {
|
|
178
178
|
const { snapshot, ownerId, input, interaction, adapter, presentation } = operation;
|
|
179
179
|
const { args, params = {}, options = {}, segments = [] } = invocation;
|
|
180
|
-
const context = createCapabilityContext(snapshot, ownerId,
|
|
180
|
+
const context = createCapabilityContext({ snapshot, ownerId, signal: readOperationInputSignal(input), operation: operation.operation });
|
|
181
181
|
const session = resolveCommandSession(input);
|
|
182
182
|
const parameterValues = { __proto__: null, ...params };
|
|
183
183
|
const optionValues = { __proto__: null, ...options };
|
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" | "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" | "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;
|
package/lib/messages.js
CHANGED
|
@@ -37,6 +37,7 @@ export const commandMessages = createMessages({
|
|
|
37
37
|
defaultUndefined: { 'zh-CN': '{field}:默认值返回了 undefined', en: '{field}: default returned undefined' },
|
|
38
38
|
parameterUndefined: { 'zh-CN': '{field}:默认值或快捷指令返回了 undefined', en: '{field}: default or shortcut returned undefined' },
|
|
39
39
|
missingOption: { 'zh-CN': '缺少选项 --{field}', en: 'Missing option --{field}' },
|
|
40
|
+
missingRequiredParam: { 'zh-CN': '缺少必填参数 {field}(类型:{type})', en: 'Missing required parameter {field} (expected {type})' },
|
|
40
41
|
unexpectedArguments: { 'zh-CN': '存在多余参数', en: 'Unexpected arguments' },
|
|
41
42
|
invalidParameter: { 'zh-CN': '缺少指令参数或参数不正确', en: 'Missing or invalid command parameter' },
|
|
42
43
|
defaultValue: { 'zh-CN': '默认值:{value}', en: 'default: {value}' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/command",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "Convention-based Command Feature for Zhin Plugin Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"segment-matcher": "^1.0.5",
|
|
24
|
-
"@zhin.js/feature-kit": "1.1.11",
|
|
25
24
|
"@zhin.js/interaction": "1.1.2",
|
|
26
|
-
"@zhin.js/permission": "1.1.
|
|
27
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
25
|
+
"@zhin.js/permission": "1.1.13",
|
|
26
|
+
"@zhin.js/plugin-runtime": "1.1.21",
|
|
27
|
+
"@zhin.js/feature-kit": "1.1.13"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^26.1.2",
|
package/src/arguments.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { commandDescription, commandMessages } from './messages.js';
|
|
|
7
7
|
|
|
8
8
|
export type CommandValidationIssueCode = 'expectedType' | 'expectedArray' | 'minimum' | 'maximum'
|
|
9
9
|
| 'choices' | 'invalidFormat' | 'invalidValue' | 'unknownOption' | 'duplicateOption'
|
|
10
|
-
| 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'unexpectedArguments'
|
|
10
|
+
| 'defaultUndefined' | 'parameterUndefined' | 'missingOption' | 'missingRequiredParam' | 'unexpectedArguments'
|
|
11
11
|
| 'invalidParameter' | 'custom';
|
|
12
12
|
|
|
13
13
|
export interface CommandValidationIssue {
|
|
@@ -136,7 +136,7 @@ export function requestsCommandHelp(segments: readonly Readonly<CommandSegment>[
|
|
|
136
136
|
for (const token of tokenize(segments)) {
|
|
137
137
|
if (!isTextToken(token) || token.quoted) continue;
|
|
138
138
|
if (token.value === '--') return false;
|
|
139
|
-
if (token.value === '--help') return true;
|
|
139
|
+
if (token.value === '--help' || token.value === '-h') return true;
|
|
140
140
|
}
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
@@ -280,7 +280,7 @@ export function parseCommandArguments(
|
|
|
280
280
|
if (value !== undefined) params[name] = value;
|
|
281
281
|
else if (schema.default !== undefined) params[name] = typeof schema.default === 'function' ? schema.default(session) : schema.default;
|
|
282
282
|
else if (!schema.optional && !(definition.$parameter?.name === name && definition.$parameter.optional)) {
|
|
283
|
-
issues.push(commandValidationIssue('
|
|
283
|
+
issues.push(commandValidationIssue('missingRequiredParam', name, locale, { type: schema.type }));
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
if (Object.hasOwn(params, name) && params[name] === undefined) issues.push(commandValidationIssue('parameterUndefined', name, locale));
|
package/src/command-index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type Locale,
|
|
14
14
|
} from '@zhin.js/plugin-runtime';
|
|
15
15
|
import type { UserInteractionFactory } from '@zhin.js/interaction';
|
|
16
|
-
import { bindOperationInput } from '@zhin.js/feature-kit';
|
|
16
|
+
import { bindOperationInput, type CapabilityOperationFactory } from '@zhin.js/feature-kit';
|
|
17
17
|
import {
|
|
18
18
|
permissionHostToken,
|
|
19
19
|
type PermissionHost,
|
|
@@ -65,6 +65,7 @@ export interface CommandDispatchRequest {
|
|
|
65
65
|
readonly interactionFactory?: UserInteractionFactory;
|
|
66
66
|
readonly commandPrefix?: string;
|
|
67
67
|
readonly snapshot?: RuntimeSnapshot;
|
|
68
|
+
readonly operation?: CapabilityOperationFactory;
|
|
68
69
|
readonly locale?: Locale;
|
|
69
70
|
}
|
|
70
71
|
/** Presentation and generation shared across one menu lookup/render operation. */
|
|
@@ -161,6 +162,8 @@ export interface CommandMenuConfig {
|
|
|
161
162
|
readonly keyword?: string;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
const DEFAULT_MENU_KEYWORDS = new Set(['菜单', '帮助', 'help', 'menu']);
|
|
166
|
+
|
|
164
167
|
export class CommandIndex {
|
|
165
168
|
readonly $projection = 'zhin.command-index/1' as const;
|
|
166
169
|
readonly #commands: readonly CommandRecord[];
|
|
@@ -351,7 +354,7 @@ export class CommandIndex {
|
|
|
351
354
|
}
|
|
352
355
|
|
|
353
356
|
async dispatch({ input, source, interactionFactory, commandPrefix, snapshot: operationSnapshot = this.snapshot,
|
|
354
|
-
locale: localeOverride }: CommandDispatchRequest): Promise<CommandDispatchResult> {
|
|
357
|
+
locale: localeOverride, operation: operationFactory }: CommandDispatchRequest): Promise<CommandDispatchResult> {
|
|
355
358
|
const locale = localeOverride ?? commandLocale(source, operationSnapshot);
|
|
356
359
|
const prefix = commandPrefix ?? '';
|
|
357
360
|
if (this.#menu) {
|
|
@@ -395,13 +398,19 @@ export class CommandIndex {
|
|
|
395
398
|
owner: match.command.slot.owner, issues: parsed.issues, help });
|
|
396
399
|
const limit = admitCommandExecution(authorSource, match.command.slot.id, match.command.slot.owner, match.command.execution, operationSnapshot);
|
|
397
400
|
if (limit) return Object.freeze({ matched: true, status: 'limited', command: match.command.name, owner: match.command.slot.owner, limit });
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
401
|
+
const operation = operationFactory?.create({ owner: match.command.slot.owner, signal: operationFactory.signal });
|
|
402
|
+
try {
|
|
403
|
+
const value = await definition.execute(createCommandContext({
|
|
404
|
+
snapshot: operationSnapshot, ownerId: match.command.slot.owner, input: authorSource,
|
|
405
|
+
operation,
|
|
406
|
+
interaction: interactionFactory?.(authorSource), adapter: definition.adapter,
|
|
407
|
+
presentation: { locale, formatCommand: (localPath, owner = match.command.owner) => this.formatCommand(owner, localPath,
|
|
408
|
+
source && typeof source === 'object' && 'conversation' in source ? commandPrefix : undefined, locale) },
|
|
409
|
+
}, { args: textArgs(parsed.remaining), params: parsed.params, segments: parsed.remaining, options: parsed.options }));
|
|
410
|
+
return Object.freeze({ matched: true, status: 'executed', command: match.command.name, owner: match.command.slot.owner, value });
|
|
411
|
+
} finally {
|
|
412
|
+
try { await operation?.resources?.close(); } catch { /* Cleanup never replaces the command's actual outcome. */ }
|
|
413
|
+
}
|
|
405
414
|
}
|
|
406
415
|
|
|
407
416
|
#denied(record: CommandRecord): CommandDispatchResult {
|
|
@@ -516,9 +525,18 @@ export class CommandIndex {
|
|
|
516
525
|
const { commandPrefix, keyword, locale } = context;
|
|
517
526
|
const text = typeof input === 'string' ? input.trim() : exactMessageText(input);
|
|
518
527
|
if (text === undefined) return undefined;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
528
|
+
const spaceIndex = text.search(/\s/u);
|
|
529
|
+
const firstToken = spaceIndex === -1 ? text : text.slice(0, spaceIndex);
|
|
530
|
+
const normalizedFirst = !commandPrefix && firstToken.startsWith('/')
|
|
531
|
+
? firstToken.slice(1)
|
|
532
|
+
: firstToken;
|
|
533
|
+
const matchedDefaultAlias = this.#menu?.keyword === undefined
|
|
534
|
+
&& DEFAULT_MENU_KEYWORDS.has(normalizedFirst.toLowerCase());
|
|
535
|
+
if (text === keyword || (matchedDefaultAlias && spaceIndex === -1)) return this.#buildMenu({}, context);
|
|
536
|
+
if (text.startsWith(keyword + ' ') || (matchedDefaultAlias && spaceIndex !== -1)) {
|
|
537
|
+
const key = (text.startsWith(keyword + ' ')
|
|
538
|
+
? text.slice(keyword.length + 1)
|
|
539
|
+
: text.slice(spaceIndex + 1)).trim();
|
|
522
540
|
const args = key.split(/\s+/u);
|
|
523
541
|
const flag = args.indexOf('--page');
|
|
524
542
|
if (flag !== -1) {
|
|
@@ -548,17 +566,23 @@ export class CommandIndex {
|
|
|
548
566
|
const children = node.children
|
|
549
567
|
.map((childId) => snapshot.tree.get(childId))
|
|
550
568
|
.filter((child): child is NonNullable<typeof child> => !!child);
|
|
551
|
-
|
|
552
|
-
|
|
569
|
+
const effectiveCommands = targetId === root
|
|
570
|
+
&& directCommands.length === 0
|
|
571
|
+
&& this.#commands.length > 0
|
|
572
|
+
&& this.#commands.length <= MENU_PAGE_SIZE
|
|
573
|
+
? this.#commands
|
|
574
|
+
: directCommands;
|
|
575
|
+
|
|
576
|
+
const pages = Math.max(1, Math.ceil((effectiveCommands.length + children.length) / MENU_PAGE_SIZE));
|
|
553
577
|
const invocation = `${commandPrefix}${keyword}${pluginKey ? ` ${pluginKey}` : ''}`;
|
|
554
578
|
if (page > pages) return { value: commandMessages(locale, 'pageOutOfRange', { pages, command: invocation }) };
|
|
555
579
|
const offset = (page - 1) * MENU_PAGE_SIZE;
|
|
556
|
-
const pageCommands =
|
|
557
|
-
const pageChildren = children.slice(Math.max(0, offset -
|
|
580
|
+
const pageCommands = effectiveCommands.slice(offset, offset + MENU_PAGE_SIZE);
|
|
581
|
+
const pageChildren = children.slice(Math.max(0, offset - effectiveCommands.length), Math.max(0, offset + MENU_PAGE_SIZE - effectiveCommands.length));
|
|
558
582
|
|
|
559
583
|
const displayKey = pluginKey ?? node.instanceKey;
|
|
560
584
|
const lines: string[] = [commandMessages(locale, 'menuTitle', { plugin: displayKey })];
|
|
561
|
-
if (pages > 1) lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands:
|
|
585
|
+
if (pages > 1) lines.push(commandMessages(locale, 'menuSummary', { page, pages, commands: effectiveCommands.length, plugins: children.length }));
|
|
562
586
|
|
|
563
587
|
if (pageCommands.length > 0) {
|
|
564
588
|
lines.push('');
|
|
@@ -579,7 +603,7 @@ export class CommandIndex {
|
|
|
579
603
|
}
|
|
580
604
|
}
|
|
581
605
|
|
|
582
|
-
if (
|
|
606
|
+
if (effectiveCommands.length === 0 && children.length === 0) {
|
|
583
607
|
lines.push('');
|
|
584
608
|
lines.push(commandMessages(locale, 'emptyMenu'));
|
|
585
609
|
}
|
|
@@ -605,7 +629,7 @@ export class CommandIndex {
|
|
|
605
629
|
name: pluginKey ? `${pluginKey}.${child.instanceKey}` : child.instanceKey,
|
|
606
630
|
description: child.metadata?.displayName ?? child.instanceKey,
|
|
607
631
|
}))),
|
|
608
|
-
commandTotal:
|
|
632
|
+
commandTotal: effectiveCommands.length, pluginTotal: children.length, hint, text,
|
|
609
633
|
appearance: Object.freeze(readMenuAppearance(config?.menuStyle)),
|
|
610
634
|
});
|
|
611
635
|
if (!snapshot.resources.get(root)?.has(htmlRendererToken.id)) return { value: text, menu };
|
package/src/definition.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { resolveLocale, type Locale, type PluginId, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
6
6
|
import {
|
|
7
7
|
createCapabilityContext,
|
|
8
|
+
type CapabilityOperation,
|
|
8
9
|
readOperationClient,
|
|
9
10
|
readOperationSession,
|
|
10
11
|
type OperationSession,
|
|
@@ -598,6 +599,7 @@ export interface CommandInvocationArguments {
|
|
|
598
599
|
export interface CommandInvocationOperation {
|
|
599
600
|
readonly snapshot: RuntimeSnapshot;
|
|
600
601
|
readonly ownerId: PluginId;
|
|
602
|
+
readonly operation?: CapabilityOperation;
|
|
601
603
|
readonly input?: unknown;
|
|
602
604
|
readonly interaction?: UserInteraction;
|
|
603
605
|
readonly adapter?: string;
|
|
@@ -611,7 +613,7 @@ export interface CommandInvocationOperation {
|
|
|
611
613
|
export function createCommandContext(operation: CommandInvocationOperation, invocation: CommandInvocationArguments): CommandContext {
|
|
612
614
|
const { snapshot, ownerId, input, interaction, adapter, presentation } = operation;
|
|
613
615
|
const { args, params = {}, options = {}, segments = [] } = invocation;
|
|
614
|
-
const context = createCapabilityContext(snapshot, ownerId,
|
|
616
|
+
const context = createCapabilityContext({ snapshot, ownerId, signal: readOperationInputSignal(input), operation: operation.operation });
|
|
615
617
|
const session = resolveCommandSession(input);
|
|
616
618
|
const parameterValues: Record<string, CommandParameterValue> = { __proto__: null, ...params };
|
|
617
619
|
const optionValues: Record<string, CommandParameterValue> = { __proto__: null, ...options };
|
package/src/messages.ts
CHANGED
|
@@ -42,6 +42,7 @@ export const commandMessages = createMessages({
|
|
|
42
42
|
defaultUndefined: { 'zh-CN': '{field}:默认值返回了 undefined', en: '{field}: default returned undefined' },
|
|
43
43
|
parameterUndefined: { 'zh-CN': '{field}:默认值或快捷指令返回了 undefined', en: '{field}: default or shortcut returned undefined' },
|
|
44
44
|
missingOption: { 'zh-CN': '缺少选项 --{field}', en: 'Missing option --{field}' },
|
|
45
|
+
missingRequiredParam: { 'zh-CN': '缺少必填参数 {field}(类型:{type})', en: 'Missing required parameter {field} (expected {type})' },
|
|
45
46
|
unexpectedArguments: { 'zh-CN': '存在多余参数', en: 'Unexpected arguments' },
|
|
46
47
|
invalidParameter: { 'zh-CN': '缺少指令参数或参数不正确', en: 'Missing or invalid command parameter' },
|
|
47
48
|
defaultValue: { 'zh-CN': '默认值:{value}', en: 'default: {value}' },
|