@zhin.js/core 1.3.3 → 1.3.5

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 (50) hide show
  1. package/README.md +12 -0
  2. package/lib/adapter.js +2 -2
  3. package/lib/built/ai-outbound/parse.js +26 -12
  4. package/lib/built/authorization.js +2 -2
  5. package/lib/built/component.d.ts +1 -2
  6. package/lib/built/component.js +1 -1
  7. package/lib/built/database.d.ts +1 -2
  8. package/lib/built/database.js +1 -1
  9. package/lib/built/dispatcher.d.ts +1 -1
  10. package/lib/built/endpoint-lifecycle-service.js +2 -1
  11. package/lib/built/endpoint-lifecycle.js +4 -3
  12. package/lib/built/html-to-text.js +15 -14
  13. package/lib/built/inbound-pipeline.d.ts +1 -1
  14. package/lib/built/inbound-pipeline.js +1 -1
  15. package/lib/built/interactive-segments/action.js +16 -1
  16. package/lib/built/message-enrich.d.ts +1 -1
  17. package/lib/built/permit-check.d.ts +1 -1
  18. package/lib/built/platform-permit.js +35 -10
  19. package/lib/built/schedule.d.ts +1 -2
  20. package/lib/built/schedule.js +1 -1
  21. package/lib/component.js +28 -11
  22. package/lib/index.d.ts +1 -0
  23. package/lib/index.js +1 -0
  24. package/lib/notice.d.ts +15 -58
  25. package/lib/notice.js +1 -3
  26. package/lib/plugin-runtime/im/contracts.d.ts +71 -0
  27. package/lib/plugin-runtime/im/contracts.js +55 -0
  28. package/lib/plugin-runtime/im/im-runtime.d.ts +70 -0
  29. package/lib/plugin-runtime/im/im-runtime.js +299 -0
  30. package/lib/plugin-runtime/im/index.d.ts +5 -0
  31. package/lib/plugin-runtime/im/index.js +5 -0
  32. package/lib/plugin-runtime/im/message-dispatcher.d.ts +7 -0
  33. package/lib/plugin-runtime/im/message-dispatcher.js +26 -0
  34. package/lib/plugin-runtime/im/outbound-renderer.d.ts +6 -0
  35. package/lib/plugin-runtime/im/outbound-renderer.js +31 -0
  36. package/lib/plugin-runtime/im/outbound-segments.d.ts +22 -0
  37. package/lib/plugin-runtime/im/outbound-segments.js +59 -0
  38. package/lib/plugin.d.ts +2 -4
  39. package/lib/plugin.js +12 -8
  40. package/lib/request.d.ts +16 -59
  41. package/lib/request.js +0 -3
  42. package/lib/side-event/base.d.ts +45 -0
  43. package/lib/side-event/base.js +30 -0
  44. package/lib/side-event/index.d.ts +3 -0
  45. package/lib/side-event/index.js +3 -0
  46. package/lib/side-event/normalize.d.ts +40 -0
  47. package/lib/side-event/normalize.js +117 -0
  48. package/lib/side-event/types.d.ts +25 -0
  49. package/lib/side-event/types.js +55 -0
  50. package/package.json +15 -5
package/README.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @zhin.js/core
2
2
 
3
+ ## Plugin Runtime 子路径
4
+
5
+ 新的 owner-aware IM 能力已经并入 Core,作为 Plugin Runtime 的正式领域接口:
6
+
7
+ - `@zhin.js/core/runtime`
8
+
9
+ `@zhin.js/adapter`、`@zhin.js/command`、`@zhin.js/component` 与
10
+ `@zhin.js/middleware` 提供纯 definition、约定发现 provider 和 generation projection;
11
+ Core Runtime 只消费它们发布的 snapshot。
12
+ 旧根入口的 `addCommand`、`addComponent`、`addMiddleware` 暂时作为作者兼容接口保留,后续
13
+ 只向 RuntimeSnapshot 投影,不再维护第二套运行时权威。
14
+
3
15
  Zhin.js **IM/多通道运行时**包:Plugin、Adapter、**Endpoint**、MessageDispatcher 与统一出站链。**AI 编排(ZhinAgent、工具安全、MCP)在 [`@zhin.js/agent`](../agent/README.md)**;本包仅 selective re-export `@zhin.js/ai` 的 Provider / Agent 原语供插件直接使用。
4
16
 
5
17
  领域词汇见 [CONTEXT.md](./CONTEXT.md);入站/出站流程见 [消息如何流转](../../docs/essentials/message-flow.md)。
package/lib/adapter.js CHANGED
@@ -194,7 +194,7 @@ export class Adapter extends EventEmitter {
194
194
  if (!endpoint)
195
195
  throw new Error(`Endpoint ${options.endpoint} not found`);
196
196
  assertOutbound(endpoint);
197
- this.logger.info(formatCompact({
197
+ this.logger.debug(formatCompact({
198
198
  send: `${options.type}(${options.id})`,
199
199
  endpoint: options.endpoint,
200
200
  preview: truncatePreview(segment.raw(options.content)),
@@ -231,7 +231,7 @@ export class Adapter extends EventEmitter {
231
231
  content: options.content,
232
232
  });
233
233
  await editable.$editMessage({ ...options, content: rendered.content });
234
- this.logger.info(formatCompact({
234
+ this.logger.debug(formatCompact({
235
235
  edit: `${options.type}(${options.id})`,
236
236
  endpoint: options.endpoint,
237
237
  messageId: options.messageId,
@@ -21,19 +21,17 @@ export function parseOutboundSegment(raw) {
21
21
  /** 去掉嵌入 JSON 候选末尾的 markdown 围栏(模型常见 ```json … ``` 误输出)。 */
22
22
  export function trimTrailingMarkdownFence(raw) {
23
23
  let s = raw.trim();
24
- const trailingFence = s.match(/^([\s\S]*?)\s*```\s*$/);
25
- if (trailingFence)
26
- s = trailingFence[1].trim();
24
+ if (s.endsWith('```'))
25
+ s = s.slice(0, -3).trimEnd();
27
26
  return s;
28
27
  }
29
28
  export function unwrapAiOutboundJsonCandidate(raw) {
30
29
  const trimmed = raw.trim();
31
- const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)```\s*$/i);
32
- if (fenced)
33
- return fenced[1].trim();
34
- const embeddedFence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
35
- if (embeddedFence && trimmed.indexOf('{') >= 0) {
36
- return trimTrailingMarkdownFence(embeddedFence[1].trim());
30
+ const fenced = readMarkdownFence(trimmed);
31
+ if (fenced && fenced.full)
32
+ return fenced.body.trim();
33
+ if (fenced && trimmed.indexOf('{') >= 0) {
34
+ return trimTrailingMarkdownFence(fenced.body.trim());
37
35
  }
38
36
  return trimTrailingMarkdownFence(trimmed);
39
37
  }
@@ -42,12 +40,12 @@ export function extractEmbeddedAiOutboundJson(plain) {
42
40
  const trimmed = plain.trim();
43
41
  if (!trimmed.includes('{'))
44
42
  return null;
45
- const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
43
+ const fenced = readMarkdownFence(trimmed);
46
44
  if (fenced) {
47
- const jsonRaw = trimTrailingMarkdownFence(fenced[1].trim());
45
+ const jsonRaw = trimTrailingMarkdownFence(fenced.body.trim());
48
46
  const payload = parseAiOutboundJson(jsonRaw);
49
47
  if (payload?.mentions?.length && payload.text?.trim()) {
50
- const prose = trimmed.slice(0, fenced.index).trim();
48
+ const prose = trimmed.slice(0, fenced.start).trim();
51
49
  return { prose, jsonRaw };
52
50
  }
53
51
  }
@@ -62,6 +60,22 @@ export function extractEmbeddedAiOutboundJson(plain) {
62
60
  }
63
61
  return null;
64
62
  }
63
+ function readMarkdownFence(text) {
64
+ const start = text.indexOf('```');
65
+ if (start < 0)
66
+ return null;
67
+ let bodyStart = start + 3;
68
+ const langEnd = text.indexOf('\n', bodyStart);
69
+ if (langEnd >= 0) {
70
+ const lang = text.slice(bodyStart, langEnd).trim().toLowerCase();
71
+ if (lang === '' || lang === 'json')
72
+ bodyStart = langEnd + 1;
73
+ }
74
+ const end = text.indexOf('```', bodyStart);
75
+ if (end < 0)
76
+ return null;
77
+ return { start, body: text.slice(bodyStart, end), full: start === 0 && text.slice(end + 3).trim() === '' };
78
+ }
65
79
  export function parseAiOutboundJson(raw) {
66
80
  const candidate = unwrapAiOutboundJsonCandidate(raw);
67
81
  if (!candidate.startsWith('{'))
@@ -1,6 +1,6 @@
1
1
  import { mergeAITriggerConfig, resolveSenderRoles, } from './ai-trigger.js';
2
- import { formatCompact, Logger } from '@zhin.js/logger';
3
- const logger = new Logger(null, 'Authorization');
2
+ import { formatCompact, getLogger } from '@zhin.js/logger';
3
+ const logger = getLogger('Authorization');
4
4
  function findEndpointEntryFromConfig(config, adapter, endpointId) {
5
5
  const endpoints = config.endpoints;
6
6
  if (!Array.isArray(endpoints))
@@ -2,9 +2,8 @@
2
2
  * ComponentFeature
3
3
  * 管理所有插件注册的组件,继承自 Feature 抽象类
4
4
  */
5
- import { Feature, FeatureJSON } from "@zhin.js/kernel";
5
+ import { Feature, FeatureJSON, type PluginLike } from '@zhin.js/kernel';
6
6
  import { Component } from "../component.js";
7
- import type { PluginLike } from '@zhin.js/kernel';
8
7
  /**
9
8
  * ComponentContext 扩展方法类型
10
9
  */
@@ -2,7 +2,7 @@
2
2
  * ComponentFeature
3
3
  * 管理所有插件注册的组件,继承自 Feature 抽象类
4
4
  */
5
- import { Feature } from "@zhin.js/kernel";
5
+ import { Feature } from '@zhin.js/kernel';
6
6
  import { renderComponents } from "../component.js";
7
7
  /**
8
8
  * 组件服务 Feature
@@ -4,8 +4,7 @@
4
4
  */
5
5
  import { Definition, Database } from "@zhin.js/database";
6
6
  import { DatabaseConfig, Models } from "../types.js";
7
- import { Feature, FeatureJSON } from "@zhin.js/kernel";
8
- import type { PluginLike } from '@zhin.js/kernel';
7
+ import { Feature, FeatureJSON, type PluginLike } from '@zhin.js/kernel';
9
8
  /**
10
9
  * 模型定义记录
11
10
  */
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { Registry } from "@zhin.js/database";
6
6
  import { formatCompact } from '@zhin.js/logger';
7
- import { Feature } from "@zhin.js/kernel";
7
+ import { Feature } from '@zhin.js/kernel';
8
8
  import { SystemLogDefinition } from "../models/system-log.js";
9
9
  import { UserDefinition } from "../models/user.js";
10
10
  const databaseAfterStartHooks = [];
@@ -27,8 +27,8 @@
27
27
  * 默认路由为 exclusive(命令与 AI 互斥);需双轨时请显式 dualRoute.mode: 'dual'。
28
28
  */
29
29
  import { Message } from '../message.js';
30
+ import { type Context } from '../plugin.js';
30
31
  import type { MessageMiddleware, RegisteredAdapter, MaybePromise, SendContent, OutboundReplySource, OutboundPolishMiddleware, OutboundReplyStore } from '../types.js';
31
- import type { Context } from '../plugin.js';
32
32
  export declare function getOutboundReplyStore(): OutboundReplyStore | undefined;
33
33
  /**
34
34
  * 路由判定结果(互斥模式 legacy)
@@ -1,3 +1,4 @@
1
+ import { formatDisplayPath } from '@zhin.js/logger';
1
2
  import { Adapter } from '../adapter.js';
2
3
  import { segment } from '../utils.js';
3
4
  import { connectEndpointInstance, disconnectEndpointInstance } from './connect-endpoint-instance.js';
@@ -81,7 +82,7 @@ export class EndpointLifecycleService {
81
82
  const loader = configService.configs.get(configService.primaryFile);
82
83
  const endpoints = readAllEndpoints(this.root);
83
84
  await this.persistEndpoints(endpoints);
84
- const configPath = loader.resolvedPath;
85
+ const configPath = formatDisplayPath(loader.resolvedPath);
85
86
  const names = endpoints.map((e) => `${e.context}/${e.name}`).join(', ') || '(无)';
86
87
  return {
87
88
  message: `✅ 已将 ${endpoints.length} 个 endpoint 写入 \`${configPath}\`:${names}`,
@@ -26,9 +26,10 @@ export async function emitEndpointLifecycle(plugin, adapter, endpoint, kind, det
26
26
  $id: `endpoint-lifecycle:${endpoint.$id}:${kind}:${Date.now()}`,
27
27
  $adapter: adapter.name,
28
28
  $endpoint: endpoint.$id,
29
- $type: 'endpoint.lifecycle',
30
- $subType: kind,
31
- $channel: { id: endpoint.$id, type: 'private' },
29
+ $type: 'notice',
30
+ $scene_id: endpoint.$id,
31
+ $scene_type: 'endpoint',
32
+ $sub_type: kind,
32
33
  $timestamp: Date.now(),
33
34
  });
34
35
  adapter.emit('notice.receive', notice);
@@ -79,20 +79,21 @@ function stripRemainingTags(html) {
79
79
  return out;
80
80
  }
81
81
  function decodeCommonEntities(text) {
82
- return text
83
- .replace(/&nbsp;/gi, ' ')
84
- .replace(/&amp;/g, '&')
85
- .replace(/&lt;/g, '<')
86
- .replace(/&gt;/g, '>')
87
- .replace(/&quot;/g, '"')
88
- .replace(/&#39;/g, "'")
89
- .replace(/&apos;/g, "'")
90
- .replace(/&#(\d+);/g, (_, code) => {
91
- const n = Number(code);
92
- return Number.isFinite(n) && n >= 0 && n <= 0x10ffff ? String.fromCodePoint(n) : '';
93
- })
94
- .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
95
- const n = parseInt(hex, 16);
82
+ return text.replace(/&(#x[0-9a-fA-F]+|#\d+|nbsp|amp|lt|gt|quot|apos);/gi, (entity) => {
83
+ const key = entity.slice(1, -1).toLowerCase();
84
+ if (key === 'nbsp')
85
+ return ' ';
86
+ if (key === 'amp')
87
+ return '&';
88
+ if (key === 'lt')
89
+ return '<';
90
+ if (key === 'gt')
91
+ return '>';
92
+ if (key === 'quot')
93
+ return '"';
94
+ if (key === 'apos')
95
+ return "'";
96
+ const n = key.startsWith('#x') ? parseInt(key.slice(2), 16) : Number(key.slice(1));
96
97
  return Number.isFinite(n) && n >= 0 && n <= 0x10ffff ? String.fromCodePoint(n) : '';
97
98
  });
98
99
  }
@@ -4,7 +4,7 @@
4
4
  * Extracted from Adapter.emit() override to improve testability and decoupling.
5
5
  * Handles: event routing, logging, backpressure, pipeline execution, observer emission.
6
6
  */
7
- import type { Logger } from '@zhin.js/logger';
7
+ import { type Logger } from '@zhin.js/logger';
8
8
  import { type Message as MessageType } from '../message.js';
9
9
  import type { Plugin } from '../plugin.js';
10
10
  export interface InboundPipelineOptions {
@@ -54,7 +54,7 @@ export class InboundMessagePipeline {
54
54
  return result;
55
55
  }
56
56
  logIncoming(message) {
57
- this.options.logger.info(formatCompact({
57
+ this.options.logger.debug(formatCompact({
58
58
  recv: `${message.$channel.type}(${message.$channel.id})`,
59
59
  endpoint: message.$endpoint,
60
60
  preview: truncatePreview(segment.raw(message.$content)),
@@ -19,11 +19,26 @@ const PAYLOAD_PATTERN = /^[a-z0-9_]+:[^:\s]+:[a-z0-9_-]+$/i;
19
19
  /** 去掉 @bot、XML at 段、首尾空白(QQ 指令预填文本归一化) */
20
20
  export function stripInteractiveCommandText(raw) {
21
21
  let text = raw.trim();
22
- text = text.replace(/<at\s[^>]*\/>/gi, ' ');
22
+ text = stripXmlAtSegments(text);
23
23
  text = text.replace(/^@\S+\s+/u, '');
24
24
  text = text.replace(/\s+/g, ' ').trim();
25
25
  return text;
26
26
  }
27
+ function stripXmlAtSegments(text) {
28
+ let out = '';
29
+ let cursor = 0;
30
+ while (cursor < text.length) {
31
+ const start = text.toLowerCase().indexOf('<at ', cursor);
32
+ if (start < 0)
33
+ break;
34
+ const end = text.indexOf('/>', start + 4);
35
+ if (end < 0)
36
+ break;
37
+ out += text.slice(cursor, start) + ' ';
38
+ cursor = end + 2;
39
+ }
40
+ return out + text.slice(cursor);
41
+ }
27
42
  /** 从文本降级输入解析 payload(需在 active session 的 fallback.map 中查找) */
28
43
  export function resolveTextFallbackPayload(raw, map) {
29
44
  const trimmed = raw.trim();
@@ -5,7 +5,7 @@ import type { Plugin } from '../plugin.js';
5
5
  import { Message, type MessageChannel } from '../message.js';
6
6
  import type { Adapters } from '../adapter.js';
7
7
  import type { SendContent } from '../types.js';
8
- import type { SenderRole } from './roles.js';
8
+ import { type SenderRole } from './roles.js';
9
9
  /** Agent turn 可挂载在 Message 扩展字段上的元数据 */
10
10
  export type AgentTurnMessage = Message<{
11
11
  extra?: Record<string, unknown>;
@@ -2,6 +2,6 @@
2
2
  * 内置 permit 同步校验(adapter/group/private/channel/user/role)
3
3
  */
4
4
  import type { Message } from '../message.js';
5
- import type { SenderRole } from './roles.js';
5
+ import { type SenderRole } from './roles.js';
6
6
  export declare function checkBuiltinPermit(name: string, message: Message<any>, roles: readonly SenderRole[]): boolean;
7
7
  export declare function checkBuiltinPermitList(permits: readonly string[], message: Message<any>, roles: readonly SenderRole[]): boolean;
@@ -1,18 +1,25 @@
1
1
  import { parsePlatformPermitName, isPlatformPermit } from './permit-parse.js';
2
2
  const checkers = new Map();
3
- const registeredDefaultSceneCheckers = new Set();
3
+ const defaultSceneRegistrations = new Map();
4
4
  export function registerPlatformPermitChecker(adapter, checker) {
5
5
  const key = String(adapter);
6
- checkers.set(key, checker);
6
+ const registrations = checkers.get(key) ?? [];
7
+ registrations.push(checker);
8
+ checkers.set(key, registrations);
7
9
  return () => {
8
- if (checkers.get(key) === checker) {
10
+ const current = checkers.get(key);
11
+ if (!current)
12
+ return;
13
+ const index = current.lastIndexOf(checker);
14
+ if (index >= 0)
15
+ current.splice(index, 1);
16
+ if (current.length === 0)
9
17
  checkers.delete(key);
10
- }
11
18
  };
12
19
  }
13
20
  export function clearPlatformPermitCheckers() {
14
21
  checkers.clear();
15
- registeredDefaultSceneCheckers.clear();
22
+ defaultSceneRegistrations.clear();
16
23
  }
17
24
  export function checkPlatformPermit(name, message) {
18
25
  const parsed = parsePlatformPermitName(name);
@@ -20,7 +27,8 @@ export function checkPlatformPermit(name, message) {
20
27
  return false;
21
28
  if (String(message.$adapter) !== parsed.adapter)
22
29
  return false;
23
- const checker = checkers.get(parsed.adapter);
30
+ const registrations = checkers.get(parsed.adapter);
31
+ const checker = registrations?.[registrations.length - 1];
24
32
  if (!checker)
25
33
  return false;
26
34
  return checker(parsed.perm, message);
@@ -58,9 +66,26 @@ export function createSceneRolePlatformChecker() {
58
66
  /** 为适配器注册默认场景治理 platform checker(幂等) */
59
67
  export function registerDefaultScenePlatformPermitChecker(adapter) {
60
68
  const key = String(adapter);
61
- if (registeredDefaultSceneCheckers.has(key)) {
62
- return () => { };
69
+ const existing = defaultSceneRegistrations.get(key);
70
+ if (existing) {
71
+ existing.references += 1;
72
+ return () => releaseDefaultSceneChecker(key, existing);
63
73
  }
64
- registeredDefaultSceneCheckers.add(key);
65
- return registerPlatformPermitChecker(key, createSceneRolePlatformChecker());
74
+ const checker = createSceneRolePlatformChecker();
75
+ const registration = {
76
+ checker,
77
+ dispose: registerPlatformPermitChecker(key, checker),
78
+ references: 1,
79
+ };
80
+ defaultSceneRegistrations.set(key, registration);
81
+ return () => releaseDefaultSceneChecker(key, registration);
82
+ }
83
+ function releaseDefaultSceneChecker(key, registration) {
84
+ if (defaultSceneRegistrations.get(key) !== registration)
85
+ return;
86
+ registration.references -= 1;
87
+ if (registration.references > 0)
88
+ return;
89
+ defaultSceneRegistrations.delete(key);
90
+ registration.dispose();
66
91
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * ScheduleFeature — 管理插件注册的内存定时任务
3
3
  */
4
- import { Feature, FeatureJSON, type ScheduleKind } from '@zhin.js/kernel';
5
- import type { HolidayInput, ScatterInput } from '@zhin.js/kernel';
4
+ import { Feature, FeatureJSON, type ScheduleKind, type HolidayInput, type ScatterInput } from '@zhin.js/kernel';
6
5
  export interface ScheduleDescriptor {
7
6
  kind: ScheduleKind | 'every' | 'at';
8
7
  /** 6 段 cron(solar/lunar/workday/freeDay/holiday) */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ScheduleFeature — 管理插件注册的内存定时任务
3
3
  */
4
- import { Feature, ScheduleEngine, getScheduleEngine, setScheduleEngine, } from '@zhin.js/kernel';
4
+ import { Feature, ScheduleEngine, getScheduleEngine, setScheduleEngine } from '@zhin.js/kernel';
5
5
  function ensureEngine() {
6
6
  let engine = getScheduleEngine();
7
7
  if (!engine) {
package/lib/component.js CHANGED
@@ -176,24 +176,41 @@ function parseAttributeValue(str, startIndex, context) {
176
176
  function evaluateQuotedExpression(quotedValue, context) {
177
177
  if (!context)
178
178
  return quotedValue;
179
- // 处理引号内的表达式,将 {expr} 格式转换为 ${expr} 格式
180
- let result = quotedValue;
181
- const expressionRegex = /\{([^}]+)\}/g;
182
- let match;
183
- while ((match = expressionRegex.exec(quotedValue)) !== null) {
184
- const expr = match[1];
179
+ let result = '';
180
+ let cursor = 0;
181
+ for (const match of readBraceExpressions(quotedValue)) {
182
+ result += quotedValue.slice(cursor, match.start);
185
183
  try {
186
- const value = context.getValue(expr);
187
- if (value !== undefined && value !== expr) {
184
+ const value = context.getValue(match.expr);
185
+ if (value !== undefined && value !== match.expr) {
188
186
  const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
189
- result = result.replace(match[0], stringValue);
187
+ result += stringValue;
188
+ }
189
+ else {
190
+ result += quotedValue.slice(match.start, match.end);
190
191
  }
191
192
  }
192
193
  catch (error) {
193
- // 如果求值失败,保持原始表达式
194
+ result += quotedValue.slice(match.start, match.end);
194
195
  }
196
+ cursor = match.end;
195
197
  }
196
- return result;
198
+ return result + quotedValue.slice(cursor);
199
+ }
200
+ function readBraceExpressions(value) {
201
+ const matches = [];
202
+ let cursor = 0;
203
+ while (cursor < value.length) {
204
+ const start = value.indexOf('{', cursor);
205
+ if (start < 0)
206
+ break;
207
+ const end = value.indexOf('}', start + 1);
208
+ if (end < 0)
209
+ break;
210
+ matches.push({ start, end: end + 1, expr: value.slice(start + 1, end) });
211
+ cursor = end + 1;
212
+ }
213
+ return matches;
197
214
  }
198
215
  /**
199
216
  * 解析表达式值
package/lib/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './message.js';
8
8
  export * from './im-scene.js';
9
9
  export * from './notice.js';
10
10
  export * from './request.js';
11
+ export * from './side-event/index.js';
11
12
  export * from './prompt.js';
12
13
  export * from './types.js';
13
14
  export * from './agent-prompt.js';
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ export * from './message.js';
9
9
  export * from './im-scene.js';
10
10
  export * from './notice.js';
11
11
  export * from './request.js';
12
+ export * from './side-event/index.js';
12
13
  export * from './prompt.js';
13
14
  export * from './types.js';
14
15
  export * from './agent-prompt.js';
package/lib/notice.d.ts CHANGED
@@ -1,81 +1,38 @@
1
- import { Adapters } from './adapter.js';
2
- import type { MessageSender } from './types.js';
1
+ import type { Adapters } from './adapter.js';
2
+ import type { NoticeKind } from './side-event/types.js';
3
+ import type { SideEventBase } from './side-event/base.js';
4
+ export type { NoticeKind, ComposedNoticeName, NoticeType } from './side-event/types.js';
5
+ export type { SideEventBase } from './side-event/base.js';
6
+ export { composeSideEventName, formatSideEventName, matchesSideEventName, parseSideEventName, sideEventSendChannel, } from './side-event/base.js';
3
7
  /**
4
- * 通知类型枚举
5
- *
6
- * 常见 IM 通知事件分类:
7
- * - group_member_increase: 群成员增加(入群)
8
- * - group_member_decrease: 群成员减少(退群/被踢)
9
- * - group_admin_change: 群管理员变动
10
- * - group_ban: 群禁言
11
- * - group_recall: 群消息撤回
12
- * - friend_recall: 好友消息撤回
13
- * - friend_add: 新增好友
14
- * - group_poke: 群戳一戳
15
- * - friend_poke: 好友戳一戳
16
- * - group_transfer: 群转让
17
- * - endpoint.lifecycle: Endpoint 连接/断开/错误($subType: connect | disconnect | error)
18
- *
19
- * 适配器可自行扩展更多子类型
20
- */
21
- export type NoticeType = 'group_member_increase' | 'group_member_decrease' | 'group_admin_change' | 'group_ban' | 'group_recall' | 'friend_recall' | 'friend_add' | 'group_poke' | 'friend_poke' | 'group_transfer' | (string & {});
22
- /**
23
- * 通知频道信息
24
- */
25
- export interface NoticeChannel {
26
- id: string;
27
- type: 'group' | 'private' | 'channel';
28
- }
29
- /**
30
- * 通知基础结构
31
- *
32
- * 与 MessageBase 同构设计,所有通知共享以下字段。
33
- * 适配器通过 `Notice.from(raw, base)` 合并平台原始数据和标准字段。
8
+ * 通知基础结构 — 继承 SideEventBase。
34
9
  *
35
10
  * @example
36
11
  * ```typescript
37
- * // 适配器中格式化通知
38
12
  * const notice = Notice.from(rawEvent, {
39
13
  * $id: rawEvent.id,
40
14
  * $adapter: 'icqq',
41
15
  * $endpoint: endpointId,
42
- * $type: 'group_member_decrease',
43
- * $subType: 'kick',
44
- * $channel: { id: groupId, type: 'group' },
45
- * $operator: { id: operatorId, name: '管理员' },
16
+ * $type: 'notice',
17
+ * $scene_id: groupId,
18
+ * $scene_type: 'group',
19
+ * $sub_type: 'member_decrease',
20
+ * $actor: { id: operatorId, name: '管理员' },
46
21
  * $target: { id: userId, name: '被踢者' },
47
22
  * $timestamp: Date.now(),
48
23
  * });
24
+ * // formatSideEventName(notice) === 'notice.group.member_decrease'
49
25
  * this.adapter.emit('notice.receive', notice);
50
26
  * ```
51
27
  */
52
- export interface NoticeBase {
53
- /** 通知唯一 ID(平台提供或自生成) */
54
- $id: string;
55
- /** 适配器名称 */
28
+ export interface NoticeBase extends SideEventBase {
56
29
  $adapter: keyof Adapters;
57
- /** Endpoint 名称 */
58
- $endpoint: string;
59
- /** 通知类型 */
60
- $type: NoticeType;
61
- /** 通知子类型(如 leave/kick、set/unset) */
62
- $subType?: string;
63
- /** 通知发生的频道/群/会话 */
64
- $channel: NoticeChannel;
65
- /** 操作者信息(如管理员) */
66
- $operator?: MessageSender;
67
- /** 被操作目标信息(如被踢的成员) */
68
- $target?: MessageSender;
69
- /** 通知时间戳 */
70
- $timestamp: number;
30
+ $type: NoticeKind;
71
31
  }
72
32
  /**
73
33
  * 完整通知类型,支持平台原始数据扩展
74
34
  */
75
35
  export type Notice<T extends object = {}> = NoticeBase & T;
76
36
  export declare namespace Notice {
77
- /**
78
- * 工具方法:合并自定义字段与基础通知结构
79
- */
80
37
  function from<T extends object>(input: T, format: NoticeBase): Notice<T>;
81
38
  }
package/lib/notice.js CHANGED
@@ -1,8 +1,6 @@
1
+ export { composeSideEventName, formatSideEventName, matchesSideEventName, parseSideEventName, sideEventSendChannel, } from './side-event/base.js';
1
2
  export var Notice;
2
3
  (function (Notice) {
3
- /**
4
- * 工具方法:合并自定义字段与基础通知结构
5
- */
6
4
  function from(input, format) {
7
5
  return Object.assign(input, format);
8
6
  }
@@ -0,0 +1,71 @@
1
+ import type { CapabilityId, PluginId } from '@zhin.js/plugin-runtime';
2
+ declare const componentCallBrand: "zhin.component-call/1";
3
+ declare const rawContentBrand: "zhin.raw-content/1";
4
+ export interface ComponentCall<TProps = unknown> {
5
+ readonly $content: typeof componentCallBrand;
6
+ readonly name: string;
7
+ readonly props: TProps;
8
+ }
9
+ export interface RawContent<TPayload = unknown> {
10
+ readonly $content: typeof rawContentBrand;
11
+ readonly payload: TPayload;
12
+ }
13
+ export type SendContent = string | ComponentCall | RawContent | readonly SendContent[];
14
+ export declare function component<TProps>(name: string, props: TProps): ComponentCall<TProps>;
15
+ export declare function raw<TPayload>(payload: TPayload): RawContent<TPayload>;
16
+ export declare function isComponentCall(value: SendContent): value is ComponentCall;
17
+ export declare function isRawContent(value: SendContent): value is RawContent;
18
+ export interface IncomingMessage {
19
+ readonly adapter: CapabilityId;
20
+ readonly target: string;
21
+ readonly content: string;
22
+ readonly id?: string;
23
+ readonly sender?: string;
24
+ readonly metadata?: Readonly<Record<string, unknown>>;
25
+ }
26
+ export interface SendRequest {
27
+ readonly adapter: CapabilityId;
28
+ readonly target: string;
29
+ readonly requester: PluginId;
30
+ readonly content: SendContent;
31
+ readonly parent?: ChannelParent;
32
+ }
33
+ /** Console 通道的来源场景(群临时会话 parent.group / QQ 子频道 parent.guild)。 */
34
+ export interface ChannelParent {
35
+ readonly type?: string;
36
+ readonly id?: string;
37
+ readonly name?: string;
38
+ }
39
+ export interface OutboundEnvelope {
40
+ readonly adapter: CapabilityId;
41
+ readonly target: string;
42
+ readonly requester: PluginId;
43
+ readonly generation: number;
44
+ readonly payload: unknown;
45
+ readonly parent?: ChannelParent;
46
+ replace(payload: unknown): void;
47
+ }
48
+ export interface MessageGateway {
49
+ receive(input: IncomingMessage): Promise<MessageDispatchResult>;
50
+ send(request: SendRequest): Promise<unknown>;
51
+ }
52
+ export interface MessageDispatchResult {
53
+ readonly matched: boolean;
54
+ readonly command?: string;
55
+ readonly owner?: PluginId;
56
+ readonly value?: unknown;
57
+ }
58
+ export declare class Message {
59
+ readonly adapter: CapabilityId;
60
+ readonly target: string;
61
+ readonly content: string;
62
+ readonly generation: number;
63
+ readonly id?: string | undefined;
64
+ readonly sender?: string | undefined;
65
+ readonly metadata: Readonly<Record<string, unknown>>;
66
+ constructor(adapter: CapabilityId, target: string, content: string, generation: number, reply: (content: SendContent, requester?: PluginId) => Promise<unknown>, id?: string | undefined, sender?: string | undefined, metadata?: Readonly<Record<string, unknown>>);
67
+ readonly $reply: (content: SendContent) => Promise<unknown>;
68
+ readonly $replyFrom: (requester: PluginId, content: SendContent) => Promise<unknown>;
69
+ }
70
+ export declare function createOutboundEnvelope(request: Omit<OutboundEnvelope, 'payload' | 'replace'>, initialPayload: unknown): OutboundEnvelope;
71
+ export {};