@zhin.js/core 1.5.11 → 1.5.13

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 (55) hide show
  1. package/README.md +2 -2
  2. package/lib/adapter.d.ts +3 -0
  3. package/lib/built/ai-trigger.d.ts +0 -2
  4. package/lib/built/ai-trigger.js +0 -1
  5. package/lib/built/inbound-pipeline.js +0 -1
  6. package/lib/built/inbound-runner.js +0 -2
  7. package/lib/built/login-assist.d.ts +22 -4
  8. package/lib/built/login-assist.js +71 -7
  9. package/lib/built/message-enrich.d.ts +0 -1
  10. package/lib/built/message-enrich.js +0 -1
  11. package/lib/built/rich-segments/markdown-to-text.js +2 -2
  12. package/lib/built/schema-endpoint-manager.js +6 -6
  13. package/lib/built/segment-contract/types.d.ts +4 -45
  14. package/lib/built/segment-contract/types.js +0 -4
  15. package/lib/built/user-interaction.d.ts +30 -0
  16. package/lib/built/user-interaction.js +248 -0
  17. package/lib/feature/handler.d.ts +1 -1
  18. package/lib/feature/handler.js +1 -1
  19. package/lib/im-scene.js +0 -2
  20. package/lib/index.d.ts +4 -3
  21. package/lib/index.js +3 -3
  22. package/lib/message.d.ts +0 -5
  23. package/lib/message.js +0 -13
  24. package/lib/plugin-runtime/im/im-runtime.d.ts +30 -8
  25. package/lib/plugin-runtime/im/im-runtime.js +471 -145
  26. package/lib/plugin-runtime/im/index.d.ts +2 -0
  27. package/lib/plugin-runtime/im/index.js +2 -0
  28. package/lib/plugin-runtime/im/login-assist-host.d.ts +6 -0
  29. package/lib/plugin-runtime/im/login-assist-host.js +6 -0
  30. package/lib/plugin-runtime/im/message-dispatcher.d.ts +2 -2
  31. package/lib/plugin-runtime/im/message-dispatcher.js +2 -2
  32. package/lib/plugin-runtime/im/outbound-segments.d.ts +4 -0
  33. package/lib/plugin-runtime/im/outbound-segments.js +26 -0
  34. package/lib/plugin-runtime/im/side-event-gateway.d.ts +13 -0
  35. package/lib/plugin-runtime/im/side-event-gateway.js +2 -0
  36. package/lib/plugin.d.ts +1 -0
  37. package/lib/{prompt.d.ts → schema-interaction.d.ts} +6 -6
  38. package/lib/{prompt.js → schema-interaction.js} +12 -12
  39. package/lib/side-event/base.d.ts +7 -0
  40. package/lib/side-event/dispatch.d.ts +15 -0
  41. package/lib/side-event/dispatch.js +140 -0
  42. package/lib/side-event/index.d.ts +1 -0
  43. package/lib/side-event/index.js +1 -0
  44. package/lib/side-event/normalize.d.ts +2 -0
  45. package/lib/side-event/normalize.js +4 -0
  46. package/lib/side-event/types.d.ts +4 -2
  47. package/lib/side-event/types.js +1 -1
  48. package/lib/system-event.d.ts +15 -0
  49. package/lib/system-event.js +7 -0
  50. package/lib/types.d.ts +0 -15
  51. package/package.json +11 -10
  52. package/lib/built/prepend-quote-context.d.ts +0 -34
  53. package/lib/built/prepend-quote-context.js +0 -130
  54. package/lib/message-quote.d.ts +0 -10
  55. package/lib/message-quote.js +0 -90
@@ -1,8 +1,10 @@
1
1
  export * from './contracts.js';
2
2
  export * from './im-runtime.js';
3
3
  export * from './interactive.js';
4
+ export * from './login-assist-host.js';
4
5
  export * from './message-bus.js';
5
6
  export * from './message-dispatcher.js';
6
7
  export * from './outbound-renderer.js';
7
8
  export * from './outbound-segments.js';
8
9
  export * from './service-tokens.js';
10
+ export * from './side-event-gateway.js';
@@ -1,8 +1,10 @@
1
1
  export * from './contracts.js';
2
2
  export * from './im-runtime.js';
3
3
  export * from './interactive.js';
4
+ export * from './login-assist-host.js';
4
5
  export * from './message-bus.js';
5
6
  export * from './message-dispatcher.js';
6
7
  export * from './outbound-renderer.js';
7
8
  export * from './outbound-segments.js';
8
9
  export * from './service-tokens.js';
10
+ export * from './side-event-gateway.js';
@@ -0,0 +1,6 @@
1
+ import { LoginAssist } from '../../built/login-assist.js';
2
+ /**
3
+ * Plugin Runtime login-assist port(与 messageGatewayToken / sideEventGatewayToken 并列)。
4
+ * 适配器 waitForInput;Console / stdin 经 listPending + submit 消费。
5
+ */
6
+ export declare const loginAssistToken: import("@zhin.js/plugin-runtime").Token<LoginAssist>;
@@ -0,0 +1,6 @@
1
+ import { createToken } from '@zhin.js/plugin-runtime';
2
+ /**
3
+ * Plugin Runtime login-assist port(与 messageGatewayToken / sideEventGatewayToken 并列)。
4
+ * 适配器 waitForInput;Console / stdin 经 listPending + submit 消费。
5
+ */
6
+ export const loginAssistToken = createToken('zhin.im.login-assist');
@@ -1,4 +1,4 @@
1
- import { type CommandPromptFactory } from '@zhin.js/command';
1
+ import type { UserInteractionFactory } from '@zhin.js/interaction';
2
2
  import type { RuntimeSnapshot } from '@zhin.js/plugin-runtime';
3
3
  import type { Message, MessageDispatchResult } from './contracts.js';
4
4
  /**
@@ -14,5 +14,5 @@ export declare const defaultCommandPrefixResolver: CommandPrefixResolver;
14
14
  export declare class MessageDispatcher {
15
15
  private readonly resolvePrefix;
16
16
  constructor(resolvePrefix?: CommandPrefixResolver);
17
- dispatch(message: Message, snapshot: RuntimeSnapshot, promptFactory?: CommandPromptFactory): Promise<MessageDispatchResult>;
17
+ dispatch(message: Message, snapshot: RuntimeSnapshot, interactionFactory?: UserInteractionFactory): Promise<MessageDispatchResult>;
18
18
  }
@@ -26,7 +26,7 @@ export class MessageDispatcher {
26
26
  constructor(resolvePrefix = defaultCommandPrefixResolver) {
27
27
  this.resolvePrefix = resolvePrefix;
28
28
  }
29
- async dispatch(message, snapshot, promptFactory) {
29
+ async dispatch(message, snapshot, interactionFactory) {
30
30
  const prefix = this.resolvePrefix(message, snapshot);
31
31
  let input = message.content.trim();
32
32
  if (prefix && !input.startsWith(prefix)) {
@@ -43,7 +43,7 @@ export class MessageDispatcher {
43
43
  ? stripCommandPrefix(message.segments, prefix)
44
44
  : undefined;
45
45
  const matchInput = structuredInput ?? input;
46
- const result = await commands.dispatch(matchInput, message, promptFactory, prefix);
46
+ const result = await commands.dispatch(matchInput, message, interactionFactory, prefix);
47
47
  if (result.matched && result.value !== undefined) {
48
48
  if (!result.owner)
49
49
  throw new Error('Matched Command is missing its owner');
@@ -35,6 +35,7 @@ export type OutboundMediaPolicy = 'base64' | 'url-or-text' | 'passthrough';
35
35
  export interface OutboundSegmentsPolicy {
36
36
  readonly outboundMedia: readonly ('url' | 'path' | 'base64' | 'upload')[];
37
37
  readonly interactive?: InteractivePolicy;
38
+ readonly markdown?: 'native' | 'text';
38
39
  }
39
40
  export interface NormalizeOutboundOptions {
40
41
  /** 缺省 `url-or-text`(未声明 adapter 的保守兜底:仅 URL 媒体可达)。 */
@@ -52,6 +53,9 @@ export declare function isOutboundSegment(value: unknown): value is OutboundSegm
52
53
  * `segments.interactive`,否则按平台名查内置表,未知平台回退 'text'。
53
54
  */
54
55
  export declare function resolveOutboundInteractivePolicy(adapter: CapabilityId, snapshot: RuntimeSnapshot): InteractivePolicy;
56
+ /** Preserve semantic Markdown only when the endpoint explicitly declares native consumption. */
57
+ export declare function resolveOutboundMarkdownPolicy(adapter: CapabilityId, snapshot: RuntimeSnapshot): 'native' | 'text';
58
+ export declare function applyOutboundMarkdownPolicy(payload: unknown, policy: 'native' | 'text'): unknown;
55
59
  /**
56
60
  * keyboard 段中央降级:'text' 端点把 keyboard 渲染为编号文本(复用旧轨
57
61
  * `renderKeyboardAsText`),并把有效 fallback 映射经 `remember` 回调写入
@@ -3,6 +3,7 @@ import { toCanonicalSegments } from '../../built/generic-segment-mapper.js';
3
3
  import { effectiveKeyboardFallbackMap, renderKeyboardAsText, } from '../../built/interactive-segments/resolve.js';
4
4
  import { DEFAULT_INTERACTIVE_POLICY, isKeyboardSegment, } from '../../built/interactive-segments/types.js';
5
5
  import { isMediaRef } from '../../built/segment-contract/index.js';
6
+ import { markdownToPlainText } from '../../built/rich-segments/markdown-to-text.js';
6
7
  const DEFAULT_CARD_WIDTH = 540;
7
8
  const DEFAULT_CARD_FILENAME = 'card.png';
8
9
  const DEFAULT_MEDIA_POLICY = 'url-or-text';
@@ -63,6 +64,31 @@ export function resolveOutboundInteractivePolicy(adapter, snapshot) {
63
64
  return (adapterType ? OUTBOUND_INTERACTIVE_POLICY_BY_ADAPTER[adapterType] : undefined)
64
65
  ?? DEFAULT_INTERACTIVE_POLICY;
65
66
  }
67
+ /** Preserve semantic Markdown only when the endpoint explicitly declares native consumption. */
68
+ export function resolveOutboundMarkdownPolicy(adapter, snapshot) {
69
+ const slot = snapshot.capabilities.get(adapter)
70
+ ?? snapshot.capabilities.get(baseSlotCapabilityId(adapter));
71
+ if (!slot?.definition || typeof slot.definition !== 'object')
72
+ return 'text';
73
+ const segments = slot.definition.segments;
74
+ if (!segments || typeof segments !== 'object')
75
+ return 'text';
76
+ return segments.markdown === 'native' ? 'native' : 'text';
77
+ }
78
+ export function applyOutboundMarkdownPolicy(payload, policy) {
79
+ if (policy === 'native')
80
+ return payload;
81
+ const convert = (item) => {
82
+ if (!item || typeof item !== 'object' || Array.isArray(item))
83
+ return item;
84
+ const segment = item;
85
+ if (segment.type !== 'markdown')
86
+ return item;
87
+ const content = String(segment.data?.content ?? segment.data?.text ?? '');
88
+ return { type: 'text', data: { text: markdownToPlainText(content) } };
89
+ };
90
+ return Array.isArray(payload) ? payload.map(convert) : convert(payload);
91
+ }
66
92
  function readDeclaredInteractivePolicy(definition) {
67
93
  if (!definition || typeof definition !== 'object')
68
94
  return undefined;
@@ -0,0 +1,13 @@
1
+ import type { Notice } from '../../notice.js';
2
+ import type { Request } from '../../request.js';
3
+ import type { SystemEvent } from '../../system-event.js';
4
+ /**
5
+ * Plugin Runtime 侧事件入站口(与 messageGatewayToken 并列)。
6
+ * 适配器归一 Notice / Request / SystemEvent 后调用,由 ImRuntime 分发给 HandlerIndex。
7
+ */
8
+ export interface SideEventGateway {
9
+ receiveNotice(notice: Notice): Promise<void>;
10
+ receiveRequest(request: Request): Promise<void>;
11
+ receiveSystem(event: SystemEvent): Promise<void>;
12
+ }
13
+ export declare const sideEventGatewayToken: import("@zhin.js/plugin-runtime").Token<SideEventGateway>;
@@ -0,0 +1,2 @@
1
+ import { createToken } from '@zhin.js/plugin-runtime';
2
+ export const sideEventGatewayToken = createToken('zhin.im.side-event-gateway');
package/lib/plugin.d.ts CHANGED
@@ -185,6 +185,7 @@ export declare namespace Plugin {
185
185
  'endpoint.error': [import('./built/endpoint-lifecycle.js').EndpointLifecyclePayload];
186
186
  "request.receive": [import('./request.js').Request];
187
187
  "notice.receive": [import('./notice.js').Notice];
188
+ "system.receive": [import('./system-event.js').SystemEvent];
188
189
  "ai.processing.start": [Plugin.AIEventPayload];
189
190
  "ai.processing.finish": [Plugin.AIEventPayload];
190
191
  "ai.processing.error": [Plugin.AIEventPayload];
@@ -5,10 +5,10 @@ import { Schema } from '@zhin.js/schema';
5
5
  /**
6
6
  * Prompt类:用于实现机器人与用户的交互式提问与输入收集。
7
7
  * 支持文本、数字、确认、列表、选项、Schema等多种输入类型,自动处理超时、默认值、格式化等。
8
- * 典型用法:await new Prompt(plugin, event).text('请输入内容')
8
+ * 典型用法:await new SchemaInteraction(plugin, event).text('请输入内容')
9
9
  * @template P 适配器类型
10
10
  */
11
- export declare class Prompt<P extends RegisteredAdapter> {
11
+ export declare class SchemaInteraction<P extends RegisteredAdapter> {
12
12
  private plugin;
13
13
  private event;
14
14
  /**
@@ -25,7 +25,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
25
25
  * 通用提问方法,支持自定义格式化、超时、默认值等
26
26
  * @param config 提问配置
27
27
  */
28
- private prompt;
28
+ private collect;
29
29
  /**
30
30
  * 注册一次性消息中间件,等待用户输入或超时
31
31
  * @param callback 输入回调
@@ -52,7 +52,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
52
52
  /**
53
53
  * 列表输入,支持多值分隔
54
54
  */
55
- list<T extends Prompt.SingleType = 'text'>(tips: string, config?: Prompt.ListConfig<T>, timeoutText?: string): Promise<Prompt.Result<T>[]>;
55
+ list<T extends SchemaInteraction.SingleType = 'text'>(tips: string, config?: SchemaInteraction.ListConfig<T>, timeoutText?: string): Promise<SchemaInteraction.Result<T>[]>;
56
56
  /**
57
57
  * 返回常量值(用于Schema)
58
58
  */
@@ -60,7 +60,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
60
60
  /**
61
61
  * 选项选择,支持单选/多选
62
62
  */
63
- pick<T extends Prompt.SingleType, M extends boolean = false>(tips: string, config: Prompt.PickConfig<T, M>, timeoutText?: string): Promise<Prompt.PickResult<T, M>>;
63
+ pick<T extends SchemaInteraction.SingleType, M extends boolean = false>(tips: string, config: SchemaInteraction.PickConfig<T, M>, timeoutText?: string): Promise<SchemaInteraction.PickResult<T, M>>;
64
64
  /**
65
65
  * 基于Schema的选项选择
66
66
  */
@@ -79,7 +79,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
79
79
  /**
80
80
  * Prompt命名空间:类型辅助定义
81
81
  */
82
- export declare namespace Prompt {
82
+ export declare namespace SchemaInteraction {
83
83
  interface SingleMap {
84
84
  text: string;
85
85
  number: number;
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Prompt类:用于实现机器人与用户的交互式提问与输入收集。
3
3
  * 支持文本、数字、确认、列表、选项、Schema等多种输入类型,自动处理超时、默认值、格式化等。
4
- * 典型用法:await new Prompt(plugin, event).text('请输入内容')
4
+ * 典型用法:await new SchemaInteraction(plugin, event).text('请输入内容')
5
5
  * @template P 适配器类型
6
6
  */
7
- export class Prompt {
7
+ export class SchemaInteraction {
8
8
  plugin;
9
9
  event;
10
10
  /**
@@ -26,10 +26,10 @@ export class Prompt {
26
26
  * 通用提问方法,支持自定义格式化、超时、默认值等
27
27
  * @param config 提问配置
28
28
  */
29
- prompt(config) {
29
+ collect(config) {
30
30
  return new Promise(async (resolve, reject) => {
31
31
  if (!this.event.$reply) {
32
- reject(new Error('Prompt requires outbound capability on the current endpoint'));
32
+ reject(new Error('SchemaInteraction requires outbound capability on the current endpoint'));
33
33
  return;
34
34
  }
35
35
  const id = await this.event.$reply(config.tips);
@@ -72,7 +72,7 @@ export class Prompt {
72
72
  * 文本输入
73
73
  */
74
74
  async text(tips, timeout, defaultValue = '', timeoutText) {
75
- return this.prompt({
75
+ return this.collect({
76
76
  tips,
77
77
  defaultValue,
78
78
  timeoutText,
@@ -84,7 +84,7 @@ export class Prompt {
84
84
  * 任意输入
85
85
  */
86
86
  async any(tips, timeout, defaultValue = '', timeoutText) {
87
- return this.prompt({
87
+ return this.collect({
88
88
  tips,
89
89
  defaultValue,
90
90
  timeoutText,
@@ -96,7 +96,7 @@ export class Prompt {
96
96
  * 数字输入
97
97
  */
98
98
  async number(tips, timeout, defaultValue = 0, timeoutText) {
99
- return this.prompt({
99
+ return this.collect({
100
100
  tips,
101
101
  defaultValue,
102
102
  timeoutText,
@@ -108,7 +108,7 @@ export class Prompt {
108
108
  * 确认输入(如 yes/no)
109
109
  */
110
110
  async confirm(tips, condition = 'yes', timeout, defaultValue = false, timeoutText) {
111
- return this.prompt({
111
+ return this.collect({
112
112
  tips: `${tips}\n输入“${condition}”以确认`,
113
113
  defaultValue,
114
114
  timeout,
@@ -121,7 +121,7 @@ export class Prompt {
121
121
  */
122
122
  async list(tips, config = { type: 'text' }, timeoutText) {
123
123
  const separator = config.separator || ',';
124
- return this.prompt({
124
+ return this.collect({
125
125
  tips: `${tips}\n值之间使用“${separator}”分隔`,
126
126
  defaultValue: config.defaultValue || [],
127
127
  timeout: config.timeout,
@@ -154,7 +154,7 @@ export class Prompt {
154
154
  const separator = config.separator || ',';
155
155
  if (config.multiple)
156
156
  moreTextArr.push(`多选请用“${separator}”分隔`);
157
- return this.prompt({
157
+ return this.collect({
158
158
  tips: `${tips}\n${moreTextArr.join('\n')}`,
159
159
  defaultValue: config.defaultValue,
160
160
  timeout: config.timeout,
@@ -218,13 +218,13 @@ export class Prompt {
218
218
  throw new Error('Object schema missing object definition');
219
219
  return (await this.getValueWithSchemas(schema.options.object));
220
220
  case 'date':
221
- return await this.prompt({
221
+ return await this.collect({
222
222
  tips: schema.meta.description || schema.meta.key || 'Enter a date',
223
223
  defaultValue: schema.meta.default || new Date(),
224
224
  format: (input) => new Date(input),
225
225
  });
226
226
  case 'regexp':
227
- return await this.prompt({
227
+ return await this.collect({
228
228
  tips: schema.meta.description || schema.meta.key || 'Enter a regex pattern',
229
229
  defaultValue: schema.meta.default || '',
230
230
  format: (input) => new RegExp(input),
@@ -25,6 +25,13 @@ export interface SideEventBase {
25
25
  $target?: MessageSender;
26
26
  /** 毫秒时间戳 */
27
27
  $timestamp: number;
28
+ /** Canonical relation fields populated by the Adapter boundary. */
29
+ $message_id?: string;
30
+ $reaction?: string;
31
+ $operation?: 'added' | 'removed';
32
+ $duration_seconds?: number;
33
+ $role?: string;
34
+ $enabled?: boolean;
28
35
  }
29
36
  /** 组合 Side Event 完整类型名 */
30
37
  export declare function composeSideEventName(type: string, sceneType: string | undefined, subType: string | undefined): string;
@@ -0,0 +1,15 @@
1
+ import type { SideEventGateway } from '../plugin-runtime/im/side-event-gateway.js';
2
+ import { type OneBotLikeRawEvent, type SideEventPlatform } from './normalize.js';
3
+ export interface OneBotLikeSideEventInput {
4
+ readonly adapter: string;
5
+ readonly endpointKey: string;
6
+ readonly platform?: SideEventPlatform;
7
+ readonly raw: OneBotLikeRawEvent;
8
+ readonly approve?: (flag: string, remark?: string) => void | Promise<void>;
9
+ readonly reject?: (flag: string, reason?: string) => void | Promise<void>;
10
+ }
11
+ /**
12
+ * Normalize OneBot / icqq-style notice|request|meta payloads and forward to SideEventGateway.
13
+ * Returns which kind was dispatched, or null when the payload is not a side event.
14
+ */
15
+ export declare function receiveOneBotLikeSideEvent(gateway: SideEventGateway, input: OneBotLikeSideEventInput): Promise<'notice' | 'request' | 'system' | null>;
@@ -0,0 +1,140 @@
1
+ import { buildNotice, buildRequest, buildSystem, mapNoticeParts, mapRequestParts, resolveSideEventDedupeKey, senderFromId, } from './normalize.js';
2
+ /**
3
+ * Normalize OneBot / icqq-style notice|request|meta payloads and forward to SideEventGateway.
4
+ * Returns which kind was dispatched, or null when the payload is not a side event.
5
+ */
6
+ export async function receiveOneBotLikeSideEvent(gateway, input) {
7
+ const raw = input.raw;
8
+ const platform = input.platform ?? 'onebot';
9
+ const postType = String(raw.post_type ?? raw.type ?? '');
10
+ if (postType === 'notice' || postType.startsWith('notice.')) {
11
+ const noticeType = String(raw.notice_type ?? (postType.replace(/^notice\.?/, '') || 'unknown'));
12
+ const parts = mapNoticeParts(platform, noticeType, {
13
+ sub_type: raw.sub_type != null ? String(raw.sub_type) : undefined,
14
+ is_group: raw.group_id != null,
15
+ });
16
+ const id = resolveSideEventDedupeKey(raw, 'notice');
17
+ const memberEvent = ['member_increase', 'member_decrease', 'ban', 'admin_change'].includes(parts.sub_type);
18
+ const actorId = raw.operator_id
19
+ ?? (!memberEvent ? raw.user_id : undefined);
20
+ const targetId = memberEvent
21
+ ? raw.user_id
22
+ : parts.sub_type === 'poke'
23
+ ? raw.target_id ?? raw.self_id
24
+ : raw.user_id != null && String(raw.user_id) !== String(raw.operator_id ?? '')
25
+ ? raw.user_id
26
+ : undefined;
27
+ const notice = buildNotice(raw, {
28
+ $id: id,
29
+ $adapter: input.adapter,
30
+ $endpoint: input.endpointKey,
31
+ $type: 'notice',
32
+ $scene_id: String(raw.group_id ?? raw.user_id ?? input.endpointKey),
33
+ $scene_type: parts.scene_type,
34
+ $sub_type: parts.sub_type,
35
+ $actor: senderFromId(actorId),
36
+ $target: senderFromId(targetId),
37
+ $timestamp: toMillis(raw.time),
38
+ ...(raw.message_id != null ? { $message_id: String(raw.message_id) } : {}),
39
+ ...(raw.emoji_id != null || raw.code != null
40
+ ? { $reaction: String(raw.emoji_id ?? raw.code) }
41
+ : {}),
42
+ ...(parts.sub_type === 'emoji_reaction'
43
+ ? { $operation: /delete|remove/i.test(String(raw.sub_type ?? noticeType)) ? 'removed' : 'added' }
44
+ : {}),
45
+ ...(raw.duration != null ? { $duration_seconds: Math.max(0, Number(raw.duration) || 0) } : {}),
46
+ ...(parts.sub_type === 'admin_change'
47
+ ? { $role: 'admin', $enabled: String(raw.sub_type ?? '') === 'set' }
48
+ : {}),
49
+ });
50
+ await gateway.receiveNotice(notice);
51
+ return 'notice';
52
+ }
53
+ if (postType === 'request' || postType.startsWith('request.')) {
54
+ const requestType = String(raw.request_type ?? (postType.replace(/^request\.?/, '') || 'friend'));
55
+ const parts = mapRequestParts(platform, requestType, raw.sub_type != null ? String(raw.sub_type) : undefined);
56
+ const flag = raw.flag != null ? String(raw.flag) : '';
57
+ const id = flag || resolveSideEventDedupeKey(raw, 'request');
58
+ const actor = senderFromId(raw.user_id);
59
+ if (!actor)
60
+ return null;
61
+ const approve = input.approve;
62
+ const reject = input.reject;
63
+ let active = true;
64
+ const actions = new Set();
65
+ const requireActiveAction = () => {
66
+ if (!active)
67
+ throw new Error('Request action port expired with its generation operation');
68
+ };
69
+ const runAction = (action) => {
70
+ requireActiveAction();
71
+ const operation = Promise.resolve().then(action);
72
+ actions.add(operation);
73
+ void operation.then(() => actions.delete(operation), () => actions.delete(operation));
74
+ return operation;
75
+ };
76
+ const request = buildRequest(raw, {
77
+ $id: id,
78
+ $adapter: input.adapter,
79
+ $endpoint: input.endpointKey,
80
+ $type: 'request',
81
+ $scene_id: String(raw.group_id ?? raw.user_id ?? input.endpointKey),
82
+ $scene_type: parts.scene_type,
83
+ $sub_type: parts.sub_type,
84
+ $actor: actor,
85
+ $comment: raw.comment != null ? String(raw.comment) : undefined,
86
+ $timestamp: toMillis(raw.time),
87
+ async $approve(remark) {
88
+ if (!approve || !flag)
89
+ throw new Error('Request approve is not available');
90
+ await runAction(() => approve(flag, remark));
91
+ },
92
+ async $reject(reason) {
93
+ if (!reject || !flag)
94
+ throw new Error('Request reject is not available');
95
+ await runAction(() => reject(flag, reason));
96
+ },
97
+ });
98
+ try {
99
+ await gateway.receiveRequest(request);
100
+ }
101
+ finally {
102
+ active = false;
103
+ await Promise.allSettled([...actions]);
104
+ }
105
+ return 'request';
106
+ }
107
+ if (postType === 'meta_event'
108
+ || postType === 'meta'
109
+ || postType.startsWith('meta.')
110
+ || postType.startsWith('system.')) {
111
+ const metaType = String(raw.meta_event_type
112
+ ?? raw.detail_type
113
+ ?? (postType.replace(/^(meta_event|meta|system)\.?/, '') || 'unknown'));
114
+ const subType = raw.sub_type != null ? String(raw.sub_type) : undefined;
115
+ const sceneType = postType.startsWith('system.')
116
+ ? (postType.split('.')[1] ?? 'system')
117
+ : metaType === 'lifecycle' || metaType === 'heartbeat'
118
+ ? metaType
119
+ : 'meta';
120
+ const system = buildSystem(raw, {
121
+ $id: `system:${toMillis(raw.time)}_${metaType}_${subType ?? ''}`,
122
+ $adapter: input.adapter,
123
+ $endpoint: input.endpointKey,
124
+ $type: 'system',
125
+ $scene_id: String(raw.self_id ?? input.endpointKey),
126
+ $scene_type: sceneType,
127
+ $sub_type: subType ?? metaType,
128
+ $timestamp: toMillis(raw.time),
129
+ });
130
+ await gateway.receiveSystem(system);
131
+ return 'system';
132
+ }
133
+ return null;
134
+ }
135
+ function toMillis(time) {
136
+ const seconds = Number(time);
137
+ return Number.isFinite(seconds) && seconds > 0
138
+ ? (seconds < 1e12 ? seconds * 1000 : seconds)
139
+ : Date.now();
140
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './base.js';
2
2
  export * from './types.js';
3
3
  export * from './normalize.js';
4
+ export * from './dispatch.js';
@@ -1,3 +1,4 @@
1
1
  export * from './base.js';
2
2
  export * from './types.js';
3
3
  export * from './normalize.js';
4
+ export * from './dispatch.js';
@@ -1,5 +1,6 @@
1
1
  import { Notice, type NoticeBase } from '../notice.js';
2
2
  import { Request, type RequestBase } from '../request.js';
3
+ import { SystemEvent, type SystemEventBase } from '../system-event.js';
3
4
  import { type ComposedNoticeName, type ComposedRequestName, type SideEventParts } from './types.js';
4
5
  export type SideEventPlatform = 'onebot' | 'icqq' | 'napcat' | 'kook' | (string & {});
5
6
  export interface OneBotLikeRawEvent {
@@ -38,3 +39,4 @@ export declare function senderFromId(id: unknown, name?: string): {
38
39
  } | undefined;
39
40
  export declare function buildNotice<T extends object>(input: T, format: NoticeBase): Notice<T>;
40
41
  export declare function buildRequest<T extends object>(input: T, format: RequestBase): Request<T>;
42
+ export declare function buildSystem<T extends object>(input: T, format: SystemEventBase): SystemEvent<T>;
@@ -1,5 +1,6 @@
1
1
  import { Notice } from '../notice.js';
2
2
  import { Request } from '../request.js';
3
+ import { SystemEvent } from '../system-event.js';
3
4
  import { composeSideEventName, parseSideEventName, } from './base.js';
4
5
  import { KOOK_NOTICE_PARTS_MAP, ONEBOT_NOTICE_PARTS_MAP, SLACK_NOTICE_PARTS_MAP } from './types.js';
5
6
  /** 去重用:notice / request 无稳定 id 时组合键 */
@@ -115,3 +116,6 @@ export function buildNotice(input, format) {
115
116
  export function buildRequest(input, format) {
116
117
  return Request.from(input, format);
117
118
  }
119
+ export function buildSystem(input, format) {
120
+ return SystemEvent.from(input, format);
121
+ }
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Side Event 标准类型。
3
- * 运行时 `$type` 仅存命名空间(`notice` / `request`);
3
+ * 运行时 `$type` 仅存命名空间(`notice` / `request` / `system`);
4
4
  * 完整名由 `${$type}.${$scene_type}.${$sub_type}` 组合。
5
5
  */
6
- export type SideEventKind = 'notice' | 'request';
6
+ export type SideEventKind = 'notice' | 'request' | 'system';
7
7
  export type NoticeKind = 'notice';
8
8
  export type RequestKind = 'request';
9
+ export type SystemKind = 'system';
9
10
  /** 完整组合名(测试 / 文档 / 消费者匹配用) */
10
11
  export type ComposedNoticeName = 'notice.group.member_increase' | 'notice.group.member_decrease' | 'notice.group.admin_change' | 'notice.group.ban' | 'notice.group.recall' | 'notice.group.poke' | 'notice.group.transfer' | 'notice.group.upload' | 'notice.group.emoji_reaction' | 'notice.group.card_change' | 'notice.group.essence_add' | 'notice.group.essence_delete' | 'notice.friend.recall' | 'notice.friend.increase' | 'notice.friend.poke' | 'notice.endpoint.lifecycle' | (string & {});
11
12
  export type ComposedRequestName = 'request.friend.add' | 'request.group.add' | 'request.group.invite' | (string & {});
13
+ export type ComposedSystemName = 'system.login.qrcode' | 'system.login.slider' | 'system.login.device' | 'system.login.error' | 'system.login.auth' | 'system.offline' | 'system.offline.network' | 'system.offline.kickoff' | 'system.online' | (string & {});
12
14
  export interface SideEventParts {
13
15
  scene_type: string;
14
16
  sub_type: string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Side Event 标准类型。
3
- * 运行时 `$type` 仅存命名空间(`notice` / `request`);
3
+ * 运行时 `$type` 仅存命名空间(`notice` / `request` / `system`);
4
4
  * 完整名由 `${$type}.${$scene_type}.${$sub_type}` 组合。
5
5
  */
6
6
  /** OneBot / icqq 等平台 notice_type 原始值 → 标准 scene_type + sub_type */
@@ -0,0 +1,15 @@
1
+ import type { Adapters } from './adapter.js';
2
+ import type { SideEventBase } from './side-event/base.js';
3
+ /**
4
+ * 系统/登录侧事件(扫码、滑块、掉线等)。
5
+ * `$scene_type`:`login` / `offline` / `online` 等;
6
+ * `$sub_type`:`qrcode` / `slider` / `device` / `kickoff` / `network` 等。
7
+ */
8
+ export interface SystemEventBase extends SideEventBase {
9
+ $adapter: keyof Adapters;
10
+ $type: 'system';
11
+ }
12
+ export type SystemEvent<T extends object = {}> = SystemEventBase & T;
13
+ export declare namespace SystemEvent {
14
+ function from<T extends object>(input: T, format: SystemEventBase): SystemEvent<T>;
15
+ }
@@ -0,0 +1,7 @@
1
+ export var SystemEvent;
2
+ (function (SystemEvent) {
3
+ function from(input, format) {
4
+ return Object.assign(input, format);
5
+ }
6
+ SystemEvent.from = from;
7
+ })(SystemEvent || (SystemEvent = {}));
package/lib/types.d.ts CHANGED
@@ -61,21 +61,6 @@ export type MaybeArray<T> = T | T[];
61
61
  * 消息发送内容类型
62
62
  */
63
63
  export type SendContent = MaybeArray<string | MessageElement>;
64
- /** $getMsg 返回的被引用消息载荷 */
65
- export interface QuotedMessagePayload {
66
- messageId: string;
67
- sender?: {
68
- id?: string;
69
- name?: string;
70
- };
71
- content: MessageElement[] | string;
72
- raw?: string;
73
- time?: number;
74
- }
75
- /** 支持按 message_id 拉取历史消息的 Bot(可选实现) */
76
- export interface QuotableEndpoint {
77
- $getMsg(messageId: string): Promise<QuotedMessagePayload>;
78
- }
79
64
  /** 可选:编辑已发送消息(交互式棋盘更新) */
80
65
  export interface EditMessageOptions {
81
66
  messageId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/core",
3
- "version": "1.5.11",
3
+ "version": "1.5.13",
4
4
  "description": "Zhin机器人核心框架",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -75,17 +75,18 @@
75
75
  "segment-matcher": "^1.0.5",
76
76
  "smol-toml": "^1.7.1",
77
77
  "yaml": "^2.9.0",
78
- "@zhin.js/adapter": "1.1.10",
79
- "@zhin.js/command": "1.0.14",
80
- "@zhin.js/component": "1.0.11",
78
+ "@zhin.js/adapter": "1.2.0",
79
+ "@zhin.js/component": "1.0.12",
80
+ "@zhin.js/command": "1.0.15",
81
81
  "@zhin.js/database": "1.0.79",
82
- "@zhin.js/handler": "1.0.2",
83
- "@zhin.js/im-contract": "1.0.3",
82
+ "@zhin.js/handler": "1.0.3",
83
+ "@zhin.js/im-contract": "1.0.4",
84
+ "@zhin.js/interaction": "1.0.1",
84
85
  "@zhin.js/kernel": "1.0.7",
85
86
  "@zhin.js/logger": "1.0.76",
86
- "@zhin.js/middleware": "1.0.11",
87
- "@zhin.js/permission": "1.0.2",
88
- "@zhin.js/plugin-runtime": "1.1.6",
87
+ "@zhin.js/middleware": "1.0.12",
88
+ "@zhin.js/plugin-runtime": "1.1.7",
89
+ "@zhin.js/permission": "1.0.3",
89
90
  "@zhin.js/schema": "1.0.73"
90
91
  },
91
92
  "peerDependencies": {
@@ -101,7 +102,7 @@
101
102
  "@types/qrcode": "^1.5.6",
102
103
  "ajv": "8.18.0",
103
104
  "typescript": "^6.0.3",
104
- "@zhin.js/ai": "1.5.5"
105
+ "@zhin.js/ai": "1.5.6"
105
106
  },
106
107
  "repository": {
107
108
  "type": "git",