@zhin.js/core 1.5.2 → 1.5.4

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 (97) hide show
  1. package/README.md +2 -2
  2. package/lib/adapter.d.ts +7 -1
  3. package/lib/adapter.js +28 -24
  4. package/lib/agent-prompt.d.ts +2 -3
  5. package/lib/agent-prompt.js +5 -0
  6. package/lib/built/ai-outbound/parse.d.ts +1 -1
  7. package/lib/built/ai-outbound/parse.js +2 -6
  8. package/lib/built/ai-outbound/plain-mention-rewrite.js +4 -4
  9. package/lib/built/ai-outbound/resolve.js +8 -8
  10. package/lib/built/ai-outbound/types.d.ts +3 -3
  11. package/lib/built/ai-trigger.d.ts +5 -0
  12. package/lib/built/ai-trigger.js +40 -13
  13. package/lib/built/authorization.js +4 -4
  14. package/lib/built/command.d.ts +2 -2
  15. package/lib/built/command.js +2 -2
  16. package/lib/built/common-adapter-tools.d.ts +11 -11
  17. package/lib/built/common-adapter-tools.js +13 -13
  18. package/lib/built/config.d.ts +0 -4
  19. package/lib/built/config.js +0 -4
  20. package/lib/built/dispatcher.d.ts +2 -0
  21. package/lib/built/dispatcher.js +1 -1
  22. package/lib/built/endpoint-commands.js +2 -2
  23. package/lib/built/endpoint-lifecycle-service.js +13 -13
  24. package/lib/built/endpoint-lifecycle.d.ts +3 -3
  25. package/lib/built/endpoint-lifecycle.js +2 -2
  26. package/lib/built/endpoint-manager.d.ts +1 -1
  27. package/lib/built/interactive-segments/index.d.ts +1 -1
  28. package/lib/built/interactive-segments/index.js +1 -1
  29. package/lib/built/interactive-segments/keyboard-segment.d.ts +0 -1
  30. package/lib/built/interactive-segments/keyboard-segment.js +0 -1
  31. package/lib/built/interactive-segments/onebot-keyboard.d.ts +0 -3
  32. package/lib/built/interactive-segments/onebot-keyboard.js +0 -2
  33. package/lib/built/interactive-segments/resolve.d.ts +0 -3
  34. package/lib/built/interactive-segments/resolve.js +0 -3
  35. package/lib/built/interactive-segments/types.d.ts +8 -20
  36. package/lib/built/interactive-segments/types.js +0 -4
  37. package/lib/built/login-assist.d.ts +2 -2
  38. package/lib/built/login-assist.js +2 -2
  39. package/lib/built/message-enrich.d.ts +1 -1
  40. package/lib/built/message-enrich.js +2 -7
  41. package/lib/built/permission.d.ts +0 -4
  42. package/lib/built/permission.js +0 -4
  43. package/lib/built/prepend-quote-context.js +5 -5
  44. package/lib/built/rich-segments/capabilities.js +0 -1
  45. package/lib/built/rich-segments/html-segment.js +1 -4
  46. package/lib/built/rich-segments/markdown-segment.js +1 -4
  47. package/lib/built/rich-segments/types.d.ts +0 -5
  48. package/lib/built/schema-endpoint-manager.js +6 -6
  49. package/lib/built/tool.d.ts +5 -3
  50. package/lib/built/tool.js +14 -23
  51. package/lib/command.d.ts +4 -4
  52. package/lib/command.js +8 -11
  53. package/lib/endpoint-capabilities.d.ts +2 -3
  54. package/lib/endpoint-capabilities.js +4 -4
  55. package/lib/endpoint.d.ts +2 -0
  56. package/lib/endpoint.js +1 -0
  57. package/lib/errors.d.ts +2 -2
  58. package/lib/errors.js +4 -4
  59. package/lib/host-plugin-registry.d.ts +6 -5
  60. package/lib/host-plugin-registry.js +9 -4
  61. package/lib/im-scene.d.ts +1 -1
  62. package/lib/im-scene.js +5 -5
  63. package/lib/im-session-id.d.ts +2 -2
  64. package/lib/im-session-id.js +4 -4
  65. package/lib/index.d.ts +1 -2
  66. package/lib/index.js +0 -2
  67. package/lib/jsx-runtime.d.ts +1 -0
  68. package/lib/jsx.d.ts +19 -21
  69. package/lib/message.d.ts +1 -1
  70. package/lib/message.js +1 -1
  71. package/lib/notice.d.ts +2 -2
  72. package/lib/plugin-context.d.ts +11 -13
  73. package/lib/plugin-context.js +21 -18
  74. package/lib/plugin-runtime/im/contracts.d.ts +91 -12
  75. package/lib/plugin-runtime/im/contracts.js +79 -1
  76. package/lib/plugin-runtime/im/im-runtime.d.ts +33 -23
  77. package/lib/plugin-runtime/im/im-runtime.js +104 -63
  78. package/lib/plugin-runtime/im/index.d.ts +2 -0
  79. package/lib/plugin-runtime/im/index.js +2 -0
  80. package/lib/plugin-runtime/im/message-bus.d.ts +38 -0
  81. package/lib/plugin-runtime/im/message-bus.js +40 -0
  82. package/lib/plugin-runtime/im/message-dispatcher.js +13 -73
  83. package/lib/plugin-runtime/im/outbound-renderer.d.ts +3 -2
  84. package/lib/plugin-runtime/im/outbound-renderer.js +44 -8
  85. package/lib/plugin-runtime/im/service-tokens.d.ts +18 -0
  86. package/lib/plugin-runtime/im/service-tokens.js +13 -0
  87. package/lib/plugin.d.ts +4 -7
  88. package/lib/plugin.js +7 -30
  89. package/lib/request.d.ts +2 -2
  90. package/lib/side-event/types.d.ts +0 -4
  91. package/lib/types.d.ts +6 -11
  92. package/lib/utils.js +3 -1
  93. package/package.json +13 -12
  94. package/lib/built/qrcode-segment.d.ts +0 -18
  95. package/lib/built/qrcode-segment.js +0 -78
  96. package/lib/types-generator.d.ts +0 -5
  97. package/lib/types-generator.js +0 -71
@@ -12,12 +12,10 @@ export const defaultCommandPrefixResolver = (message, snapshot) => {
12
12
  const config = snapshot.config.get(ownerOfMessage(message));
13
13
  if (!config)
14
14
  return '';
15
- const endpointName = typeof message.metadata?.endpoint === 'string'
16
- ? message.metadata.endpoint
17
- : undefined;
18
- if (endpointName && Array.isArray(config.endpoints)) {
15
+ const endpointId = message.endpointId;
16
+ if (endpointId && Array.isArray(config.endpoints)) {
19
17
  const entry = config.endpoints.find((item) => !!item && typeof item === 'object'
20
- && item.name === endpointName);
18
+ && item.id === endpointId);
21
19
  if (typeof entry?.commandPrefix === 'string')
22
20
  return entry.commandPrefix;
23
21
  }
@@ -31,79 +29,35 @@ export class MessageDispatcher {
31
29
  async dispatch(message, snapshot) {
32
30
  const prefix = this.resolvePrefix(message, snapshot);
33
31
  let input = message.content.trim();
34
- logger.debug(formatCompact({
35
- op: 'command_dispatch_start',
36
- adapter: ownerOfMessage(message),
37
- endpoint: typeof message.metadata?.endpoint === 'string'
38
- ? message.metadata.endpoint
39
- : undefined,
40
- prefix: prefix || '(none)',
41
- preview: truncatePreview(input),
42
- segments: summarizeSegmentTypes(message.segments),
43
- }));
44
- if (prefix) {
45
- if (!input.startsWith(prefix)) {
46
- logger.debug(formatCompact({
47
- op: 'command_dispatch_miss',
48
- reason: 'prefix_miss',
49
- prefix,
50
- preview: truncatePreview(input),
51
- }));
52
- return Object.freeze({ matched: false });
53
- }
54
- input = input.slice(prefix.length).trim();
55
- }
56
- if (!input) {
57
- logger.debug(formatCompact({
58
- op: 'command_dispatch_miss',
59
- reason: 'empty_after_prefix',
60
- prefix: prefix || '(none)',
61
- }));
32
+ if (prefix && !input.startsWith(prefix)) {
62
33
  return Object.freeze({ matched: false });
63
34
  }
35
+ if (prefix)
36
+ input = input.slice(prefix.length).trim();
37
+ if (!input)
38
+ return Object.freeze({ matched: false });
64
39
  const commands = snapshot.projections.get(commandFeatureId);
65
- if (!isCommandIndex(commands)) {
66
- logger.debug(formatCompact({
67
- op: 'command_dispatch_miss',
68
- reason: 'no_command_index',
69
- }));
40
+ if (!isCommandIndex(commands))
70
41
  return Object.freeze({ matched: false });
71
- }
72
42
  const structuredInput = message.segments
73
43
  ? stripCommandPrefix(message.segments, prefix)
74
44
  : undefined;
75
- if (message.segments && structuredInput === undefined) {
76
- logger.debug(formatCompact({
77
- op: 'command_dispatch_fallback_text',
78
- reason: 'strip_prefix_failed',
79
- prefix: prefix || '(none)',
80
- segments: summarizeSegmentTypes(message.segments),
81
- }));
82
- }
83
45
  const matchInput = structuredInput ?? input;
84
- logger.debug(formatCompact({
85
- op: 'command_dispatch_match_input',
86
- mode: typeof matchInput === 'string' ? 'text' : 'segments',
87
- preview: typeof matchInput === 'string'
88
- ? truncatePreview(matchInput)
89
- : summarizeSegmentTypes(matchInput),
90
- }));
91
46
  const result = await commands.dispatch(matchInput, message);
92
47
  if (result.matched && result.value !== undefined) {
93
48
  if (!result.owner)
94
49
  throw new Error('Matched Command is missing its owner');
95
50
  logger.debug(formatCompact({
96
- op: 'command_dispatch_hit',
51
+ op: 'dispatch_hit',
97
52
  command: result.command,
98
53
  owner: result.owner,
99
54
  }));
100
55
  await message.$replyFrom(result.owner, result.value);
101
56
  }
102
- else {
57
+ else if (!result.matched) {
103
58
  logger.debug(formatCompact({
104
- op: 'command_dispatch_miss',
105
- reason: result.matched ? 'empty_value' : 'no_match',
106
- command: result.command,
59
+ op: 'dispatch_miss',
60
+ preview: truncatePreview(input),
107
61
  }));
108
62
  }
109
63
  return result;
@@ -151,17 +105,3 @@ function stripCommandPrefix(segments, prefix) {
151
105
  }
152
106
  return pendingPrefix ? undefined : result;
153
107
  }
154
- function summarizeSegmentTypes(segments) {
155
- if (!segments?.length)
156
- return undefined;
157
- return segments
158
- .map((segment) => {
159
- if (typeof segment.type === 'string')
160
- return segment.type;
161
- if (segment.type && typeof segment.type === 'object' && 'name' in segment.type) {
162
- return String(segment.type.name);
163
- }
164
- return '?';
165
- })
166
- .join(',');
167
- }
@@ -1,6 +1,7 @@
1
1
  import type { PluginId, RuntimeSnapshot } from '@zhin.js/plugin-runtime';
2
- import { type SendContent } from './contracts.js';
2
+ import type { ConversationRef } from '@zhin.js/im-contract';
3
+ import { type IncomingContext, type SendContent } from './contracts.js';
3
4
  export declare class OutboundRenderer {
4
5
  #private;
5
- render(content: SendContent, requester: PluginId, snapshot: RuntimeSnapshot): Promise<unknown>;
6
+ render(content: SendContent, requester: PluginId, snapshot: RuntimeSnapshot, conversation?: ConversationRef, incoming?: IncomingContext): Promise<unknown>;
6
7
  }
@@ -1,30 +1,66 @@
1
+ import { componentHostToken } from '@zhin.js/plugin-runtime';
1
2
  import { componentFeatureId, isComponentIndex, } from '@zhin.js/component';
2
3
  import { isComponentCall, isRawContent, isSegmentContent, } from './contracts.js';
3
4
  const maxComponentDepth = 32;
5
+ const TEMPLATE_MARKER = '${';
4
6
  export class OutboundRenderer {
5
- async render(content, requester, snapshot) {
6
- return this.#render(content, requester, snapshot, 0);
7
+ async render(content, requester, snapshot, conversation, incoming) {
8
+ const host = resolveComponentHost(snapshot);
9
+ const ctx = conversation ? buildTemplateContext(conversation, incoming) : undefined;
10
+ return this.#render(content, requester, snapshot, host, ctx, 0);
7
11
  }
8
- async #render(content, requester, snapshot, depth) {
12
+ async #render(content, requester, snapshot, host, ctx, depth) {
9
13
  if (depth > maxComponentDepth)
10
14
  throw new Error('Component render depth exceeded 32');
11
15
  if (typeof content === 'string')
12
- return content;
16
+ return compileText(content, host, ctx);
13
17
  if (Array.isArray(content)) {
14
- return Promise.all(content.map((item) => this.#render(item, requester, snapshot, depth)));
18
+ return Promise.all(content.map((item) => this.#render(item, requester, snapshot, host, ctx, depth)));
15
19
  }
16
20
  if (isRawContent(content))
17
21
  return content.payload;
18
- // canonical Segment 一等公民:原样透传,由下游 normalizeOutboundPayload 归一与协商
19
- if (isSegmentContent(content))
22
+ if (isSegmentContent(content)) {
23
+ if (content.type === 'text') {
24
+ const text = content.data?.text;
25
+ if (typeof text === 'string' && text.includes(TEMPLATE_MARKER) && host && ctx) {
26
+ return { ...content, data: { ...content.data, text: host.compileTemplate(text, ctx) } };
27
+ }
28
+ }
20
29
  return content;
30
+ }
21
31
  if (isComponentCall(content)) {
22
32
  const rendered = await requireComponents(snapshot).render(requester, content.name, content.props);
23
- return this.#render(rendered, requester, snapshot, depth + 1);
33
+ return this.#render(rendered, requester, snapshot, host, ctx, depth + 1);
24
34
  }
25
35
  throw new TypeError('Unsupported SendContent');
26
36
  }
27
37
  }
38
+ function compileText(text, host, ctx) {
39
+ if (!host || !ctx || !text.includes(TEMPLATE_MARKER))
40
+ return text;
41
+ return host.compileTemplate(text, ctx);
42
+ }
43
+ function buildTemplateContext(conversation, incoming) {
44
+ return {
45
+ adapter: conversation.endpoint.adapter,
46
+ endpoint: conversation.endpoint.id,
47
+ kind: conversation.kind,
48
+ conversationId: conversation.id,
49
+ sender: incoming?.sender,
50
+ content: incoming?.content,
51
+ segments: incoming?.segments,
52
+ messageId: incoming?.messageId,
53
+ timestamp: incoming?.timestamp,
54
+ endpointId: incoming?.endpointId,
55
+ mentioned: incoming?.mentioned,
56
+ };
57
+ }
58
+ function resolveComponentHost(snapshot) {
59
+ const host = snapshot.resources.get(snapshot.root)?.get(componentHostToken.id);
60
+ return host && typeof host.compileTemplate === 'function'
61
+ ? host
62
+ : undefined;
63
+ }
28
64
  function requireComponents(snapshot) {
29
65
  const projection = snapshot.projections.get(componentFeatureId);
30
66
  if (!isComponentIndex(projection)) {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Core service tokens — typed Token 替代 legacy string-based inject('x')。
3
+ *
4
+ * Slice 2:Token 指向当前 Feature 实例(双写窗口)。
5
+ * Slice 4:Feature 删除后,Token 类型改为 Runtime projection 接口。
6
+ */
7
+ import type { CommandFeature } from '../../built/command.js';
8
+ import type { ComponentFeature } from '../../built/component.js';
9
+ import type { ScheduleFeature } from '../../built/schedule.js';
10
+ import type { ConfigFeature } from '../../built/config.js';
11
+ import type { SchemaFeature } from '../../built/schema-feature.js';
12
+ import type { MessageDispatcherService } from '../../built/dispatcher.js';
13
+ export declare const commandServiceToken: import("@zhin.js/plugin-runtime").Token<CommandFeature>;
14
+ export declare const componentServiceToken: import("@zhin.js/plugin-runtime").Token<ComponentFeature>;
15
+ export declare const schedulerServiceToken: import("@zhin.js/plugin-runtime").Token<ScheduleFeature>;
16
+ export declare const configServiceToken: import("@zhin.js/plugin-runtime").Token<ConfigFeature>;
17
+ export declare const schemaServiceToken: import("@zhin.js/plugin-runtime").Token<SchemaFeature>;
18
+ export declare const dispatcherServiceToken: import("@zhin.js/plugin-runtime").Token<MessageDispatcherService>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Core service tokens — typed Token 替代 legacy string-based inject('x')。
3
+ *
4
+ * Slice 2:Token 指向当前 Feature 实例(双写窗口)。
5
+ * Slice 4:Feature 删除后,Token 类型改为 Runtime projection 接口。
6
+ */
7
+ import { createToken } from '@zhin.js/plugin-runtime';
8
+ export const commandServiceToken = createToken('zhin.im.command-service');
9
+ export const componentServiceToken = createToken('zhin.im.component-service');
10
+ export const schedulerServiceToken = createToken('zhin.im.scheduler-service');
11
+ export const configServiceToken = createToken('zhin.im.config-service');
12
+ export const schemaServiceToken = createToken('zhin.im.schema-service');
13
+ export const dispatcherServiceToken = createToken('zhin.im.dispatcher-service');
package/lib/plugin.d.ts CHANGED
@@ -10,7 +10,7 @@ import { MessageMiddleware, RegisteredAdapter, MaybePromise, ArrayItem, SendOpti
10
10
  import type { InteractiveHandler } from "./built/interactive-segments/types.js";
11
11
  import { Adapter, Adapters } from "./adapter.js";
12
12
  import { PluginBase, PluginBaseLifecycle, type PluginLike } from '@zhin.js/kernel';
13
- export { getPlugin } from "./plugin-context.js";
13
+ export { getPlugin, markPluginRuntimeActive, isPluginRuntimeActive, resetPluginRuntimeFlag } from "./plugin-context.js";
14
14
  export { storage, getCurrentFile } from "./plugin-context.js";
15
15
  export { setHostRootPlugin, getHostRootPlugin } from "./host-plugin-registry.js";
16
16
  declare const contextsKey: unique symbol;
@@ -22,11 +22,8 @@ export type DisposeFn<A> = (context: ArrayItem<A>) => MaybePromise<void>;
22
22
  export type ContextList<CS extends (keyof Plugin.Contexts)[]> = CS extends [infer L, ...infer R] ? R extends (keyof Plugin.Contexts)[] ? [ContextItem<L>, ...ContextList<R>] : never[] : never[];
23
23
  type ContextItem<L> = L extends keyof Plugin.Contexts ? Plugin.Contexts[L] : never;
24
24
  /**
25
- * 获取当前插件实例(经典 AsyncLocalStorage 路径)。
26
- * 同一上下文中同一文件多次调用返回同一实例。
27
- *
28
- * @deprecated 仅 `zhin.js/node`(`bootstrapNode`)可用;`zhin runtime start` 下不工作。
29
- * 新插件请用 `definePlugin` + 约定目录。见 `docs/contributing/public-api-surface.md`。
25
+ * @deprecated **已删除**。请使用 `definePlugin` + 约定目录(`zhin runtime start`)。
26
+ * @throws 总是抛出——仅保留签名供编译期过渡。
30
27
  */
31
28
  export declare function usePlugin(): Plugin;
32
29
  export interface Plugin extends Plugin.Extensions {
@@ -223,7 +220,7 @@ export declare namespace Plugin {
223
220
  path?: 'chat' | 'fast' | 'agent' | 'multimodal' | 'rate_limited';
224
221
  userId?: string;
225
222
  platform?: string;
226
- endpointId?: string;
223
+ endpointKey?: string;
227
224
  sceneId?: string;
228
225
  messageId?: string;
229
226
  scope?: string;
package/lib/plugin.js CHANGED
@@ -14,9 +14,8 @@ import { compose, remove, resolveEntry } from "./utils.js";
14
14
  import { ensureInteractiveMiddleware, registerInteractiveHandler as registerInteractiveHandlerCore, } from "./built/interactive-segments/handlers.js";
15
15
  import { Adapter } from "./adapter.js";
16
16
  import { Feature, PluginBase, resolvePluginResolveDir as _resolvePluginResolveDir, pluginCreateRequire as _pluginCreateRequire, getFileHash, watchFile, registerExtension, unregisterExtensions, installExtensionProxy } from '@zhin.js/kernel';
17
- import { storage, getCurrentFile } from "./plugin-context.js";
18
- // Re-export getPlugin from plugin-context for backward compatibility
19
- export { getPlugin } from "./plugin-context.js";
17
+ import { storage } from "./plugin-context.js";
18
+ export { getPlugin, markPluginRuntimeActive, isPluginRuntimeActive, resetPluginRuntimeFlag } from "./plugin-context.js";
20
19
  export { storage, getCurrentFile } from "./plugin-context.js";
21
20
  export { setHostRootPlugin, getHostRootPlugin } from "./host-plugin-registry.js";
22
21
  const contextsKey = Symbol("contexts");
@@ -27,34 +26,13 @@ const loadedModules = new Map();
27
26
  function pluginCreateRequire() {
28
27
  return _pluginCreateRequire();
29
28
  }
30
- // ============================================================================
31
- // usePlugin — 获取或创建当前插件实例
32
- // ============================================================================
33
29
  /**
34
- * 获取当前插件实例(经典 AsyncLocalStorage 路径)。
35
- * 同一上下文中同一文件多次调用返回同一实例。
36
- *
37
- * @deprecated 仅 `zhin.js/node`(`bootstrapNode`)可用;`zhin runtime start` 下不工作。
38
- * 新插件请用 `definePlugin` + 约定目录。见 `docs/contributing/public-api-surface.md`。
30
+ * @deprecated **已删除**。请使用 `definePlugin` + 约定目录(`zhin runtime start`)。
31
+ * @throws 总是抛出——仅保留签名供编译期过渡。
39
32
  */
40
33
  export function usePlugin() {
41
- // 必须传入 plugin.ts import.meta.url:getCurrentFile 默认锚定在 plugin-context.ts,
42
- // 否则会多跳一层 usePlugin 帧,把调用方误判为 plugin.ts(plugin.name === "plugin")。
43
- const callerFile = getCurrentFile(import.meta.url);
44
- // 如果当前 store 已是同一文件创建的插件,直接返回
45
- const current = storage.getStore();
46
- if (current && current.filePath.replace(/\?t=\d+$/, '') === callerFile.replace(/\?t=\d+$/, '')) {
47
- return current;
48
- }
49
- const parentPlugin = current;
50
- const newPlugin = new Plugin(callerFile, parentPlugin);
51
- try {
52
- storage.enterWith(newPlugin);
53
- }
54
- catch {
55
- // Cloudflare Workers 等环境未实现 enterWith;调用方须用 storage.run()
56
- }
57
- return newPlugin;
34
+ throw new Error('usePlugin() has been removed. Use `definePlugin` + convention directories instead. '
35
+ + 'See docs/contributing/public-api-surface.md');
58
36
  }
59
37
  /**
60
38
  * Plugin 类 - IM 特化插件,继承 PluginBase
@@ -210,10 +188,9 @@ export class Plugin extends PluginBase {
210
188
  sideEffect.finished = false;
211
189
  };
212
190
  this.on('context.dispose', disposeFn);
213
- // 确保 dispose 时清理监听器(只注册一次)
214
191
  const cleanupOnDispose = () => {
215
192
  this.off('context.dispose', disposeFn);
216
- dispose(this.inject(args[0]));
193
+ dispose(this.inject(contexts[0]));
217
194
  };
218
195
  this.once('dispose', cleanupOnDispose);
219
196
  };
package/lib/request.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { MaybePromise } from './types.js';
2
2
  import type { Adapters } from './adapter.js';
3
3
  import type { RequestKind } from './side-event/types.js';
4
4
  import type { SideEventBase } from './side-event/base.js';
5
- export type { RequestKind, ComposedRequestName, RequestType } from './side-event/types.js';
5
+ export type { RequestKind, ComposedRequestName } from './side-event/types.js';
6
6
  /**
7
7
  * 请求基础结构 — 继承 SideEventBase,附加审批方法。
8
8
  *
@@ -11,7 +11,7 @@ export type { RequestKind, ComposedRequestName, RequestType } from './side-event
11
11
  * const request = Request.from(rawEvent, {
12
12
  * $id: rawEvent.flag,
13
13
  * $adapter: 'icqq',
14
- * $endpoint: endpointId,
14
+ * $endpoint: endpointKey,
15
15
  * $type: 'request',
16
16
  * $scene_id: groupId,
17
17
  * $scene_type: 'group',
@@ -9,10 +9,6 @@ export type RequestKind = 'request';
9
9
  /** 完整组合名(测试 / 文档 / 消费者匹配用) */
10
10
  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
11
  export type ComposedRequestName = 'request.friend.add' | 'request.group.add' | 'request.group.invite' | (string & {});
12
- /** @deprecated 使用 ComposedNoticeName + formatSideEventName */
13
- export type NoticeType = ComposedNoticeName;
14
- /** @deprecated 使用 ComposedRequestName + formatSideEventName */
15
- export type RequestType = ComposedRequestName;
16
12
  export interface SideEventParts {
17
13
  scene_type: string;
18
14
  sub_type: string;
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MessageChannel, Message, MessageComponent } from './message.js';
1
+ import type { MessageChannel, Message, MessageComponent } from './message.js';
2
2
  import { Adapter, Adapters } from './adapter.js';
3
3
  import { Endpoint } from './endpoint.js';
4
4
  import { SystemLog } from './models/system-log.js';
@@ -85,9 +85,6 @@ export interface EditMessageOptions {
85
85
  type: 'private' | 'group' | 'channel';
86
86
  content: SendContent;
87
87
  }
88
- export interface EditableEndpoint {
89
- $editMessage?(options: EditMessageOptions): Promise<void>;
90
- }
91
88
  /** 出站回复来源(指令 / AI / proactive),仅当经 MessageDispatcher.replyWithPolish 或 runWithOutboundPolish 发出时由框架填入异步上下文 */
92
89
  export type OutboundReplySource = 'command' | 'ai' | 'proactive';
93
90
  export type OutboundReplyTrigger = 'inbound' | 'proactive';
@@ -125,7 +122,7 @@ export interface MessageSender {
125
122
  /**
126
123
  * 通用字典类型
127
124
  */
128
- export type Dict<V = any, K extends string | symbol = string> = Record<K, V>;
125
+ export type { Dict } from '@zhin.js/kernel';
129
126
  /**
130
127
  * 用户信息结构
131
128
  */
@@ -180,7 +177,7 @@ export type BeforeSendHandler = (options: SendOptions) => MaybePromise<SendOptio
180
177
  * JSON Schema 定义,用于描述工具参数
181
178
  */
182
179
  /** Message 通讯上下文上可经 resolveContextKey 自动注入的字段名 */
183
- export type ContextInjectableKey = 'platform' | 'endpointId' | 'sceneId' | 'senderId' | 'scope';
180
+ export type ContextInjectableKey = 'platform' | 'endpointKey' | 'sceneId' | 'senderId' | 'scope';
184
181
  export interface ToolJsonSchema {
185
182
  type: string;
186
183
  properties?: Record<string, ToolJsonSchema & {
@@ -195,7 +192,7 @@ export interface ToolJsonSchema {
195
192
  /**
196
193
  * 自动从 Message 通讯上下文注入的字段名(经 resolveContextKey 映射 $ 字段)。
197
194
  * 设置后该参数对 AI 隐藏,执行时自动从上下文填充。
198
- * 例如: contextKey: 'endpointId' → 执行时自动填入 message.$endpoint
195
+ * 例如: contextKey: 'endpointKey' → 执行时自动填入 message.$endpoint
199
196
  */
200
197
  contextKey?: ContextInjectableKey;
201
198
  [key: string]: any;
@@ -340,6 +337,8 @@ export interface Tool<TArgs extends Record<string, any> = Record<string, any>> {
340
337
  * 执行此工具需要的权限列表
341
338
  */
342
339
  permissions?: string[];
340
+ /** Per-tool approval policy; `on-risk` remains fail-closed at Agent boundaries. */
341
+ approval?: 'always' | 'once' | 'never' | 'on-risk';
343
342
  /**
344
343
  * 支持的平台列表
345
344
  * 例如:['qq', 'telegram', 'discord']
@@ -372,10 +371,6 @@ export interface Tool<TArgs extends Record<string, any> = Record<string, any>> {
372
371
  */
373
372
  command?: ToolCommandConfig | false;
374
373
  }
375
- /**
376
- * @deprecated 使用 `Tool<TArgs>` 替代。Tool 已原生支持泛型。
377
- */
378
- export type ToolDefinition<TArgs extends Record<string, any> = Record<string, any>> = Tool<TArgs>;
379
374
  export declare namespace Tool {
380
375
  /**
381
376
  * 参数信息
package/lib/utils.js CHANGED
@@ -4,6 +4,8 @@
4
4
  * Generic utilities are re-exported from @zhin.js/kernel.
5
5
  * IM-specific utilities (compose, segment) remain here.
6
6
  */
7
+ import { getLogger } from '@zhin.js/logger';
8
+ const logger = getLogger('Middleware');
7
9
  // ── Re-export generic utils from kernel ──
8
10
  export { evaluate, execute, clearEvalCache, getEvalCacheStats, getValueWithRuntime, compiler, remove, isEmpty, Time, supportedPluginExtensions, resolveEntry, sleep, } from '@zhin.js/kernel';
9
11
  import { formatSegmentPreview } from "./built/segment-contract/preview.js";
@@ -39,7 +41,7 @@ export function compose(middlewares) {
39
41
  return await fn(message, () => dispatch(i + 1));
40
42
  }
41
43
  catch (error) {
42
- console.error("Middleware error:", error);
44
+ logger.error("Middleware error:", error);
43
45
  throw error;
44
46
  }
45
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/core",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Zhin机器人核心框架",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -70,16 +70,17 @@
70
70
  "segment-matcher": "^1.0.5",
71
71
  "smol-toml": "^1.7.1",
72
72
  "yaml": "^2.9.0",
73
- "@zhin.js/adapter": "1.1.5",
74
- "@zhin.js/command": "1.0.7",
75
- "@zhin.js/component": "1.0.6",
76
- "@zhin.js/database": "1.0.78",
77
- "@zhin.js/im-contract": "1.0.1",
78
- "@zhin.js/kernel": "1.0.5",
79
- "@zhin.js/logger": "1.0.75",
80
- "@zhin.js/middleware": "1.0.6",
81
- "@zhin.js/plugin-runtime": "1.1.3",
82
- "@zhin.js/schema": "1.0.72"
73
+ "@zhin.js/command": "1.0.9",
74
+ "@zhin.js/adapter": "1.1.7",
75
+ "@zhin.js/component": "1.0.8",
76
+ "@zhin.js/database": "1.0.79",
77
+ "@zhin.js/im-contract": "1.0.3",
78
+ "@zhin.js/kernel": "1.0.7",
79
+ "@zhin.js/logger": "1.0.76",
80
+ "@zhin.js/middleware": "1.0.8",
81
+ "@zhin.js/permission": "1.0.1",
82
+ "@zhin.js/plugin-runtime": "1.1.5",
83
+ "@zhin.js/schema": "1.0.73"
83
84
  },
84
85
  "peerDependencies": {
85
86
  "zod": "^4.0.0"
@@ -94,7 +95,7 @@
94
95
  "@types/qrcode": "^1.5.6",
95
96
  "ajv": "8.18.0",
96
97
  "typescript": "^6.0.3",
97
- "@zhin.js/ai": "1.5.0"
98
+ "@zhin.js/ai": "1.5.2"
98
99
  },
99
100
  "repository": {
100
101
  "type": "git",
@@ -1,18 +0,0 @@
1
- /**
2
- * 出站 `qrcode` 消息段(legacy 导出;新代码请用 rich-segments)。
3
- * @deprecated 出站渲染由 Adapter.renderSendMessage → resolveRichSegments 统一处理
4
- */
5
- import type { SendContent } from '../types.js';
6
- import { QrcodeSegment, readQrcodeSegmentData, type QrcodeSegmentData } from './rich-segments/qrcode-segment.js';
7
- export type { QrcodeSegmentData };
8
- export { QrcodeSegment, readQrcodeSegmentData };
9
- export type QrcodeOutboundMode = 'image' | 'terminal' | 'text';
10
- export declare function hasQrcodeSegment(content: SendContent | undefined): boolean;
11
- /** 创建 qrcode 出站段 */
12
- export declare function qrcodeSegment(text: string, options?: Omit<QrcodeSegmentData, 'text'>): QrcodeSegment;
13
- /** IM 适配器:qrcode → image(data URL + base64) */
14
- export declare function resolveQrcodeSegmentsToImages(content: SendContent | undefined): Promise<SendContent | undefined>;
15
- /** process 适配器:终端 ASCII + 文本占位(legacy;process endpoint 现解释 origin qrcode) */
16
- export declare function resolveQrcodeSegmentsToTerminal(content: SendContent | undefined): Promise<SendContent | undefined>;
17
- /** 不支持图片的平台:仅输出链接文本 */
18
- export declare function coerceQrcodeSegmentsToText(content: SendContent): SendContent;
@@ -1,78 +0,0 @@
1
- import { segment } from '../utils.js';
2
- import { GeneratedQrCode } from './generated-qrcode.js';
3
- import { QrcodeSegment, readQrcodeSegmentData, } from './rich-segments/qrcode-segment.js';
4
- export { QrcodeSegment, readQrcodeSegmentData };
5
- function asArray(content) {
6
- return Array.isArray(content) ? content : [content];
7
- }
8
- function packSegments(out) {
9
- if (out.length === 0)
10
- return segment.text('');
11
- if (out.length === 1)
12
- return out[0];
13
- return out;
14
- }
15
- export function hasQrcodeSegment(content) {
16
- if (content == null)
17
- return false;
18
- return asArray(content).some((item) => typeof item !== 'string' && item?.type === 'qrcode');
19
- }
20
- /** 创建 qrcode 出站段 */
21
- export function qrcodeSegment(text, options = {}) {
22
- return new QrcodeSegment({ text, ...options });
23
- }
24
- const LEGACY_QRCODE_ONLY_POLICY = {
25
- qrcode: 'image',
26
- html: 'origin',
27
- markdown: 'origin',
28
- };
29
- /** IM 适配器:qrcode → image(data URL + base64) */
30
- export async function resolveQrcodeSegmentsToImages(content) {
31
- if (content == null)
32
- return content;
33
- const { resolveRichSegments } = await import('./rich-segments/index.js');
34
- return resolveRichSegments(content, LEGACY_QRCODE_ONLY_POLICY);
35
- }
36
- /** process 适配器:终端 ASCII + 文本占位(legacy;process endpoint 现解释 origin qrcode) */
37
- export async function resolveQrcodeSegmentsToTerminal(content) {
38
- if (content == null)
39
- return content;
40
- if (!hasQrcodeSegment(content))
41
- return content;
42
- const items = asArray(content);
43
- const out = [];
44
- for (const item of items) {
45
- if (typeof item === 'string') {
46
- out.push(item);
47
- continue;
48
- }
49
- if (item.type !== 'qrcode') {
50
- out.push(item);
51
- continue;
52
- }
53
- const payload = readQrcodeSegmentData(item.data ?? {});
54
- const qr = await GeneratedQrCode.generate(payload.text, payload);
55
- await qr.printToTerminal({ small: payload.small ?? true });
56
- out.push(segment.text(`[二维码] ${payload.text}`));
57
- }
58
- return packSegments(out);
59
- }
60
- /** 不支持图片的平台:仅输出链接文本 */
61
- export function coerceQrcodeSegmentsToText(content) {
62
- const items = asArray(content);
63
- const out = [];
64
- for (const item of items) {
65
- if (typeof item === 'string') {
66
- out.push(item);
67
- continue;
68
- }
69
- if (item?.type === 'qrcode') {
70
- const text = typeof item.data?.text === 'string' ? item.data.text : '';
71
- if (text)
72
- out.push(segment.text(`[二维码] ${text}`));
73
- continue;
74
- }
75
- out.push(item);
76
- }
77
- return packSegments(out);
78
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * 更新 tsconfig.json 的类型声明
3
- * @param cwd 项目根目录
4
- */
5
- export declare function generateEnvTypes(cwd: string): Promise<void>;