@zhin.js/adapter-telegram 7.0.3 → 7.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,95 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [2d0a622]
8
+ - @zhin.js/command@1.0.10
9
+ - @zhin.js/adapter@1.1.7
10
+ - @zhin.js/core@1.5.5
11
+ - @zhin.js/agent@1.1.6
12
+ - zhin.js@6.0.5
13
+
14
+ ## 7.0.5
15
+
16
+ ### Patch Changes
17
+
18
+ - 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
19
+
20
+ All adapter platform-permit checkers now accept PermissionSubject
21
+ (duck-typed from Message/CommandSession) instead of Message directly.
22
+ registerPlatformPermitChecker is replaced by host.registerPlatform in
23
+ the adapter plugin setup. activity-feedback service updated for new
24
+ PermissionHost integration.
25
+
26
+ - Updated dependencies [6f9c366]
27
+ - Updated dependencies [373a56b]
28
+ - Updated dependencies [0de46a8]
29
+ - Updated dependencies [c106ecc]
30
+ - Updated dependencies [ca92e03]
31
+ - Updated dependencies [b0f37ae]
32
+ - Updated dependencies [ba08a2f]
33
+ - Updated dependencies [b08f7fe]
34
+ - Updated dependencies [daffd4c]
35
+ - Updated dependencies [36c7400]
36
+ - Updated dependencies [a9fa72e]
37
+ - Updated dependencies [c8de3ef]
38
+ - Updated dependencies [60f0fc8]
39
+ - Updated dependencies [574c990]
40
+ - Updated dependencies [d096f16]
41
+ - Updated dependencies [3f29623]
42
+ - Updated dependencies [2916852]
43
+ - Updated dependencies [d047869]
44
+ - Updated dependencies [162fa34]
45
+ - Updated dependencies [e62561e]
46
+ - Updated dependencies [3eeeb46]
47
+ - Updated dependencies [85d0f82]
48
+ - Updated dependencies [61bfc1c]
49
+ - Updated dependencies [04bad47]
50
+ - Updated dependencies [e40b048]
51
+ - Updated dependencies [5a5b1bb]
52
+ - Updated dependencies [f1708c3]
53
+ - Updated dependencies [d254a81]
54
+ - Updated dependencies [7123c47]
55
+ - Updated dependencies [05befc1]
56
+ - Updated dependencies [0663b6a]
57
+ - Updated dependencies [f28f9b3]
58
+ - Updated dependencies [9f340f7]
59
+ - Updated dependencies [e53444f]
60
+ - Updated dependencies [5eedd26]
61
+ - Updated dependencies [92b0dd7]
62
+ - Updated dependencies [f919b6f]
63
+ - Updated dependencies [098e411]
64
+ - Updated dependencies [e1b7c01]
65
+ - Updated dependencies [9b94f87]
66
+ - Updated dependencies [a7df753]
67
+ - @zhin.js/agent@1.1.5
68
+ - @zhin.js/host-http@1.0.8
69
+ - @zhin.js/permission@1.0.1
70
+ - @zhin.js/im-contract@1.0.3
71
+ - @zhin.js/adapter@1.1.7
72
+ - @zhin.js/plugin-runtime@1.1.5
73
+ - @zhin.js/command@1.0.9
74
+ - @zhin.js/core@1.5.4
75
+ - zhin.js@6.0.4
76
+
77
+ ## 7.0.4
78
+
79
+ ### Patch Changes
80
+
81
+ - f8c7a54: fix: im
82
+ - Updated dependencies [f8c7a54]
83
+ - @zhin.js/logger@1.0.76
84
+ - @zhin.js/host-http@1.0.7
85
+ - @zhin.js/adapter@1.1.6
86
+ - @zhin.js/agent@1.1.4
87
+ - @zhin.js/command@1.0.8
88
+ - @zhin.js/core@1.5.3
89
+ - @zhin.js/im-contract@1.0.2
90
+ - @zhin.js/plugin-runtime@1.1.4
91
+ - zhin.js@6.0.3
92
+
3
93
  ## 7.0.3
4
94
 
5
95
  ### Patch Changes
@@ -21,8 +21,8 @@ export default defineAdapter({
21
21
  create(context) {
22
22
  const config = resolveTelegramConfig(context.config);
23
23
  // 注册到插件运行时状态(telegram.endpoint list 的"运行中"数据源)
24
- context.use(telegramRuntimeStateToken).endpoints.set(config.name, {
25
- name: config.name,
24
+ context.use(telegramRuntimeStateToken).endpoints.set(config.id, {
25
+ id: config.id,
26
26
  mode: config.mode,
27
27
  });
28
28
  return new TelegramEndpoint({
@@ -26,8 +26,8 @@ export default defineAdapter<TelegramAdapterConfig>({
26
26
  create(context) {
27
27
  const config = resolveTelegramConfig(context.config);
28
28
  // 注册到插件运行时状态(telegram.endpoint list 的"运行中"数据源)
29
- context.use(telegramRuntimeStateToken).endpoints.set(config.name, {
30
- name: config.name,
29
+ context.use(telegramRuntimeStateToken).endpoints.set(config.id, {
30
+ id: config.id,
31
31
  mode: config.mode,
32
32
  });
33
33
  return new TelegramEndpoint({
package/lib/endpoint.js CHANGED
@@ -3,13 +3,12 @@
3
3
  */
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import { formatLegacyMessageRef, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
6
- import { formatCompact, getLogger } from '@zhin.js/logger';
6
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
7
  import { runTelegramPollLoop } from './polling.js';
8
8
  import { normalizeTelegramChatMember } from './platform-permit.js';
9
9
  import { botApiUrl, buildWebhookUrl, formatCallbackContent, formatCallbackSegments, formatInboundContent, formatInboundSegments, formatOutboundPlan, resolveTelegramChannelType, senderDisplayName, telegramInboundConversation, } from './protocol.js';
10
10
  import { registerTelegramAgentEndpoint } from './telegram-agent-deps.js';
11
11
  import { registerTelegramWebhookRoutes } from './webhook.js';
12
- const logger = getLogger('telegram');
13
12
  const CHAT_MEMBER_CACHE_TTL_MS = 60_000;
14
13
  const CHAT_MEMBER_CACHE_MAX = 2_000;
15
14
  /**
@@ -18,6 +17,7 @@ const CHAT_MEMBER_CACHE_MAX = 2_000;
18
17
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
19
18
  */
20
19
  export class TelegramEndpoint {
20
+ #logger;
21
21
  #options;
22
22
  #fetch;
23
23
  #pollAbort;
@@ -34,6 +34,7 @@ export class TelegramEndpoint {
34
34
  recall: (messageId) => this.recallMessage(messageId),
35
35
  });
36
36
  constructor(options) {
37
+ this.#logger = getAdapterLogger('telegram', options.config.id);
37
38
  this.#options = options;
38
39
  this.#fetch = options.fetch ?? globalThis.fetch;
39
40
  }
@@ -58,7 +59,7 @@ export class TelegramEndpoint {
58
59
  return;
59
60
  this.#started = true;
60
61
  try {
61
- this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.name, this);
62
+ this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.id, this);
62
63
  const me = await this.callApi('getMe');
63
64
  this.#botUserId = me.id;
64
65
  this.#botUsername = me.username;
@@ -70,9 +71,9 @@ export class TelegramEndpoint {
70
71
  const webhook = this.#options.config.webhook;
71
72
  if (!webhook.secretToken) {
72
73
  // 未配 secretToken 时 webhook 无鉴权:任何人知道 path 即可注入假 update。
73
- logger.warn(formatCompact({
74
+ this.#logger.warn(formatCompact({
74
75
  op: 'webhook_no_secret',
75
- endpoint: this.#options.config.name,
76
+ endpoint: this.#options.config.id,
76
77
  path: webhook.path,
77
78
  hint: 'set webhook.secretToken to authenticate Telegram callbacks',
78
79
  }));
@@ -83,9 +84,9 @@ export class TelegramEndpoint {
83
84
  allowed_updates: this.#options.config.allowedUpdates,
84
85
  ...(webhook.secretToken ? { secret_token: webhook.secretToken } : {}),
85
86
  });
86
- logger.info(formatCompact({
87
+ this.#logger.info(formatCompact({
87
88
  op: 'connect',
88
- endpoint: this.#options.config.name,
89
+ endpoint: this.#options.config.id,
89
90
  mode: 'webhook',
90
91
  path: webhook.path,
91
92
  username: me.username,
@@ -95,16 +96,16 @@ export class TelegramEndpoint {
95
96
  await this.callApi('deleteWebhook', { drop_pending_updates: false });
96
97
  this.#pollAbort = new AbortController();
97
98
  this.#pollPromise = runTelegramPollLoop(this, this.#pollAbort.signal);
98
- logger.info(formatCompact({
99
+ this.#logger.info(formatCompact({
99
100
  op: 'connect',
100
- endpoint: this.#options.config.name,
101
+ endpoint: this.#options.config.id,
101
102
  mode: 'polling',
102
103
  username: me.username,
103
104
  }));
104
105
  }
105
106
  catch (error) {
106
107
  await this.stop();
107
- logger.error('Failed to connect Telegram bot:', error);
108
+ this.#logger.error('Failed to connect Telegram bot:', error);
108
109
  throw error;
109
110
  }
110
111
  }
@@ -129,7 +130,7 @@ export class TelegramEndpoint {
129
130
  this.#unregisterAgent = undefined;
130
131
  this.#chatMemberCache.clear();
131
132
  this.#started = false;
132
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
133
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
133
134
  }
134
135
  async send({ conversation, payload }) {
135
136
  const plan = formatOutboundPlan(conversation.id, payload);
@@ -189,7 +190,7 @@ export class TelegramEndpoint {
189
190
  return;
190
191
  const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
191
192
  void this.#admitWithSenderRole(msg, conversation).catch((err) => {
192
- logger.warn(formatCompact({
193
+ this.#logger.warn(formatCompact({
193
194
  op: 'telegram_gateway_receive_failed',
194
195
  target: `${conversation.kind}:${conversation.id}`,
195
196
  error: err instanceof Error ? err.message : String(err),
@@ -205,18 +206,20 @@ export class TelegramEndpoint {
205
206
  message: { conversation, id: String(msg.message_id) },
206
207
  content: formatInboundContent(msg),
207
208
  segments: formatInboundSegments(msg),
208
- sender: senderDisplayName(msg.from),
209
+ sender: {
210
+ id: String(msg.from?.id ?? ''),
211
+ name: senderDisplayName(msg.from) || undefined,
212
+ ...(permit?.role ? { roles: [permit.role] } : {}),
213
+ },
214
+ endpointId: this.#options.config.id,
215
+ ...(mentioned ? { mentioned: true } : {}),
209
216
  metadata: Object.freeze({
210
- endpoint: this.#options.config.name,
211
- // Preserve Telegram's native value below, but publish the canonical
212
- // scene kind so every Runtime consumer sees the same conversation type.
213
217
  channelType: resolveTelegramChannelType(msg.chat.type),
214
218
  chatType: msg.chat.type,
215
219
  userId: msg.from?.id,
216
220
  date: msg.date,
217
221
  ...(permit?.role ? { senderRole: permit.role } : {}),
218
222
  ...(permit?.permissions.length ? { senderPermissions: [...permit.permissions] } : {}),
219
- ...(mentioned ? { mentioned: true } : {}),
220
223
  }),
221
224
  });
222
225
  }
@@ -285,26 +288,26 @@ export class TelegramEndpoint {
285
288
  admitCallback(query) {
286
289
  if (!this.#open)
287
290
  return;
288
- const endpointId = String(this.#options.id);
291
+ const endpointKey = String(this.#options.id);
289
292
  const msg = query.message;
290
293
  // 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
291
294
  const conversation = msg
292
- ? telegramInboundConversation(endpointId, msg.chat)
293
- : telegramInboundConversation(endpointId, { id: query.from.id, type: 'private' });
295
+ ? telegramInboundConversation(endpointKey, msg.chat)
296
+ : telegramInboundConversation(endpointKey, { id: query.from.id, type: 'private' });
294
297
  void this.#options.gateway.receive({
295
298
  conversation,
296
299
  message: { conversation, id: query.id },
297
300
  content: formatCallbackContent(query),
298
301
  segments: formatCallbackSegments(query),
299
- sender: senderDisplayName(query.from),
302
+ sender: { id: String(query.from?.id ?? ''), name: senderDisplayName(query.from) || undefined },
303
+ endpointId: this.#options.config.id,
300
304
  metadata: Object.freeze({
301
- endpoint: this.#options.config.name,
302
305
  eventType: 'callback_query',
303
306
  payload: query.data,
304
307
  sourceMessageId: msg ? String(msg.message_id) : undefined,
305
308
  }),
306
309
  }).catch((err) => {
307
- logger.warn(formatCompact({
310
+ this.#logger.warn(formatCompact({
308
311
  op: 'telegram_gateway_receive_failed',
309
312
  target: `${conversation.kind}:${conversation.id}`,
310
313
  error: err instanceof Error ? err.message : String(err),
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { TelegramEndpoint, type TelegramEndpointOptions, type TelegramFetch, } from './endpoint.js';
2
2
  export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveTelegramConfig, senderDisplayName, type ResolvedTelegramConfig, type TelegramAdapterConfig, type TelegramCallbackQuery, type TelegramChat, type TelegramChatMember, type TelegramMessage, type TelegramOutboundAction, type TelegramOutboundPlan, type TelegramOutboundUpload, type TelegramUpdate, type TelegramUser, type TelegramWireSegment, } from './protocol.js';
3
3
  export { getTelegramAgentDeps, registerTelegramAgentEndpoint, setTelegramAgentDeps, type TelegramAgentDeps, type TelegramAgentEndpoint, } from './telegram-agent-deps.js';
4
- export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, registerTelegramPlatformPermitChecker, telegramGroupPermitResolver, } from './platform-permit.js';
4
+ export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, telegramGroupPermitResolver, } from './platform-permit.js';
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { TelegramEndpoint, } from './endpoint.js';
2
2
  export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveTelegramConfig, senderDisplayName, } from './protocol.js';
3
3
  export { getTelegramAgentDeps, registerTelegramAgentEndpoint, setTelegramAgentDeps, } from './telegram-agent-deps.js';
4
- export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, registerTelegramPlatformPermitChecker, telegramGroupPermitResolver, } from './platform-permit.js';
4
+ export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, telegramGroupPermitResolver, } from './platform-permit.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Telegram platform permit — ChatMember 状态 + 细粒度权限
3
3
  */
4
- import { type Message } from '@zhin.js/core';
4
+ import type { PermissionSubject } from '@zhin.js/permission';
5
5
  export declare function platformPermit(perm: string): string;
6
6
  export declare function telegramGroupPermitResolver(logicalPerm: string): string;
7
7
  export declare function normalizeTelegramChatMember(member: {
@@ -14,5 +14,4 @@ export declare function normalizeTelegramChatMember(member: {
14
14
  role?: string;
15
15
  permissions: string[];
16
16
  };
17
- export declare function checkTelegramPlatformPermit(perm: string, message: Message<any>): boolean;
18
- export declare function registerTelegramPlatformPermitChecker(): () => void;
17
+ export declare function checkTelegramPlatformPermit(perm: string, subject: PermissionSubject): boolean;
@@ -1,7 +1,3 @@
1
- /**
2
- * Telegram platform permit — ChatMember 状态 + 细粒度权限
3
- */
4
- import { registerPlatformPermitChecker } from '@zhin.js/core';
5
1
  const ADAPTER = 'telegram';
6
2
  export function platformPermit(perm) {
7
3
  return `platform(${ADAPTER},${perm})`;
@@ -34,12 +30,9 @@ export function normalizeTelegramChatMember(member) {
34
30
  }
35
31
  return { role: 'member', permissions };
36
32
  }
37
- function senderPermits(message) {
38
- const sender = message.$sender;
39
- return { role: sender.role, permissions: sender.permissions ?? [] };
40
- }
41
- export function checkTelegramPlatformPermit(perm, message) {
42
- const { role, permissions } = senderPermits(message);
33
+ export function checkTelegramPlatformPermit(perm, subject) {
34
+ const role = subject.sender?.role?.[0];
35
+ const permissions = subject.sender?.permissions ?? [];
43
36
  const has = (t) => permissions.includes(t);
44
37
  switch (perm) {
45
38
  case 'chat_creator':
@@ -56,6 +49,3 @@ export function checkTelegramPlatformPermit(perm, message) {
56
49
  return false;
57
50
  }
58
51
  }
59
- export function registerTelegramPlatformPermitChecker() {
60
- return registerPlatformPermitChecker(ADAPTER, checkTelegramPlatformPermit);
61
- }
package/lib/protocol.d.ts CHANGED
@@ -7,7 +7,7 @@ import type { Segment } from '@zhin.js/core/runtime';
7
7
  import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
8
8
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
9
9
  export interface TelegramAdapterConfig {
10
- readonly name?: string;
10
+ readonly id?: string;
11
11
  readonly token?: string;
12
12
  /** Default true. `false` selects webhook mode (requires httpHostToken). */
13
13
  readonly polling?: boolean;
@@ -29,7 +29,7 @@ export interface TelegramAdapterConfig {
29
29
  }
30
30
  export interface ResolvedTelegramConfig {
31
31
  readonly context: 'telegram';
32
- readonly name: string;
32
+ readonly id: string;
33
33
  readonly token: string;
34
34
  readonly mode: 'polling' | 'webhook';
35
35
  readonly allowedUpdates: readonly string[];
@@ -243,7 +243,7 @@ export declare function resolveTelegramChannelType(chatType: TelegramChat['type'
243
243
  * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
244
244
  * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
245
245
  */
246
- export declare function telegramInboundConversation(endpointId: string, chat: Pick<TelegramChat, 'id' | 'type'>): ConversationRef;
246
+ export declare function telegramInboundConversation(endpointKey: string, chat: Pick<TelegramChat, 'id' | 'type'>): ConversationRef;
247
247
  export declare function senderDisplayName(user?: TelegramUser): string;
248
248
  /** Build inbound text for MessageGateway.receive. */
249
249
  export declare function formatInboundContent(msg: TelegramMessage): string;
package/lib/protocol.js CHANGED
@@ -14,8 +14,8 @@ export function resolveTelegramConfig(config = {}) {
14
14
  if (!token) {
15
15
  throw new TypeError('Telegram adapter requires token (plugins.<key>.token or endpoints with context: telegram)');
16
16
  }
17
- const name = (typeof config.name === 'string' && config.name)
18
- || (typeof entry?.name === 'string' && entry.name)
17
+ const id = (typeof config.id === 'string' && config.id)
18
+ || (typeof entry?.id === 'string' && entry.id)
19
19
  || process.env.TELEGRAM_BOT_NAME
20
20
  || 'telegram-bot';
21
21
  const polling = config.polling ?? entry?.polling;
@@ -39,7 +39,7 @@ export function resolveTelegramConfig(config = {}) {
39
39
  : undefined;
40
40
  return {
41
41
  context: 'telegram',
42
- name,
42
+ id,
43
43
  token,
44
44
  mode,
45
45
  allowedUpdates: [...allowedUpdates],
@@ -88,9 +88,9 @@ export function resolveTelegramChannelType(chatType) {
88
88
  * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
89
89
  * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
90
90
  */
91
- export function telegramInboundConversation(endpointId, chat) {
91
+ export function telegramInboundConversation(endpointKey, chat) {
92
92
  return {
93
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
93
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
94
94
  kind: resolveTelegramChannelType(chat.type),
95
95
  id: String(chat.id),
96
96
  };
@@ -20,9 +20,9 @@ export interface TelegramAgentEndpoint {
20
20
  }>;
21
21
  }
22
22
  export interface TelegramAgentDeps {
23
- getEndpoint: (endpointId: string) => TelegramAgentEndpoint;
23
+ getEndpoint: (endpointKey: string) => TelegramAgentEndpoint;
24
24
  }
25
- export declare function registerTelegramAgentEndpoint(endpointId: string, endpoint: TelegramAgentEndpoint): () => void;
25
+ export declare function registerTelegramAgentEndpoint(endpointKey: string, endpoint: TelegramAgentEndpoint): () => void;
26
26
  /** Optional override used by tests / transitional callers. Pass `null` to clear. */
27
27
  export declare function setTelegramAgentDeps(deps: TelegramAgentDeps | null): void;
28
28
  export declare function getTelegramAgentDeps(): TelegramAgentDeps;
@@ -4,11 +4,11 @@
4
4
  */
5
5
  const endpoints = new Map();
6
6
  let override = null;
7
- export function registerTelegramAgentEndpoint(endpointId, endpoint) {
8
- endpoints.set(endpointId, endpoint);
7
+ export function registerTelegramAgentEndpoint(endpointKey, endpoint) {
8
+ endpoints.set(endpointKey, endpoint);
9
9
  return () => {
10
- if (endpoints.get(endpointId) === endpoint) {
11
- endpoints.delete(endpointId);
10
+ if (endpoints.get(endpointKey) === endpoint) {
11
+ endpoints.delete(endpointKey);
12
12
  }
13
13
  };
14
14
  }
@@ -20,10 +20,10 @@ export function getTelegramAgentDeps() {
20
20
  if (override)
21
21
  return override;
22
22
  return {
23
- getEndpoint(endpointId) {
24
- const registered = endpoints.get(endpointId);
23
+ getEndpoint(endpointKey) {
24
+ const registered = endpoints.get(endpointKey);
25
25
  if (!registered)
26
- throw new Error(`Endpoint ${endpointId} 不存在`);
26
+ throw new Error(`Endpoint ${endpointKey} 不存在`);
27
27
  return registered;
28
28
  },
29
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-telegram",
3
- "version": "7.0.3",
3
+ "version": "7.0.6",
4
4
  "description": "Zhin.js Telegram Bot API adapter for Plugin Runtime (long-poll getUpdates)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -32,21 +32,22 @@
32
32
  "directory": "plugins/adapters/telegram"
33
33
  },
34
34
  "dependencies": {
35
- "@zhin.js/adapter": "1.1.5",
36
- "@zhin.js/command": "1.0.7",
37
- "@zhin.js/core": "1.5.2",
38
- "@zhin.js/host-http": "1.0.6",
39
- "@zhin.js/im-contract": "1.0.1",
40
- "@zhin.js/logger": "1.0.75",
41
- "@zhin.js/plugin-runtime": "1.1.3"
35
+ "@zhin.js/adapter": "1.1.7",
36
+ "@zhin.js/command": "1.0.10",
37
+ "@zhin.js/core": "1.5.5",
38
+ "@zhin.js/host-http": "1.0.8",
39
+ "@zhin.js/im-contract": "1.0.3",
40
+ "@zhin.js/logger": "1.0.76",
41
+ "@zhin.js/plugin-runtime": "1.1.5"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.5",
46
- "@zhin.js/agent": "1.1.3",
47
- "@zhin.js/core": "1.5.2",
48
- "@zhin.js/plugin-runtime": "1.1.3",
49
- "zhin.js": "6.0.2"
45
+ "@zhin.js/adapter": "1.1.7",
46
+ "@zhin.js/agent": "1.1.6",
47
+ "@zhin.js/core": "1.5.5",
48
+ "@zhin.js/permission": "1.0.1",
49
+ "@zhin.js/plugin-runtime": "1.1.5",
50
+ "zhin.js": "6.0.5"
50
51
  },
51
52
  "peerDependenciesMeta": {
52
53
  "zhin.js": {
@@ -64,9 +65,9 @@
64
65
  "typescript": "^6.0.3",
65
66
  "vitest": "^4.1.10",
66
67
  "zod": "^4.4.3",
67
- "@zhin.js/agent": "1.1.3",
68
- "@zhin.js/host-http": "1.0.6",
69
- "zhin.js": "6.0.2"
68
+ "@zhin.js/agent": "1.1.6",
69
+ "@zhin.js/host-http": "1.0.8",
70
+ "zhin.js": "6.0.5"
70
71
  },
71
72
  "files": [
72
73
  "adapters",
package/plugin.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { createEndpointRuntimeState } from '@zhin.js/adapter';
3
3
  import { definePlugin } from '@zhin.js/plugin-runtime';
4
- import { registerTelegramPlatformPermitChecker } from "./lib/platform-permit.js";
4
+ import { permissionHostToken } from '@zhin.js/permission';
5
+ import { checkTelegramPlatformPermit } from "./lib/platform-permit.js";
5
6
  import { telegramRuntimeStateToken } from "./lib/telegram-runtime-state.js";
6
7
  export default definePlugin({
7
8
  name: 'telegram',
@@ -9,9 +10,10 @@ export default definePlugin({
9
10
  displayName: 'Telegram Bot API Adapter',
10
11
  },
11
12
  setup(context) {
12
- // 运行中 endpoint 注册表(telegram.endpoint list 的"运行中"数据源)
13
13
  context.resources.provide(telegramRuntimeStateToken, createEndpointRuntimeState());
14
- // 平台权限门禁:chat_creator / chat_administrator / pin_messages 等(agent 工具 platformPermit)
15
- return registerTelegramPlatformPermitChecker();
14
+ if (context.resources.has(permissionHostToken)) {
15
+ const host = context.resources.use(permissionHostToken);
16
+ return host.registerPlatform('telegram', checkTelegramPlatformPermit);
17
+ }
16
18
  },
17
19
  });
package/schema.json CHANGED
@@ -40,24 +40,60 @@
40
40
  "type": "string",
41
41
  "default": "https://api.telegram.org"
42
42
  },
43
+ "master": {
44
+ "type": [
45
+ "string",
46
+ "number"
47
+ ],
48
+ "description": "框架 master(Telegram user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
49
+ },
50
+ "trusted": {
51
+ "type": "array",
52
+ "items": {
53
+ "type": [
54
+ "string",
55
+ "number"
56
+ ],
57
+ "description": "Trusted Telegram user id"
58
+ },
59
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
60
+ },
43
61
  "endpoints": {
44
62
  "type": "array",
45
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
63
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
46
64
  "items": {
47
65
  "type": "object",
48
66
  "additionalProperties": true,
49
67
  "properties": {
50
- "name": {
51
- "type": "string",
52
- "description": "Telegram bot name"
68
+ "master": {
69
+ "type": [
70
+ "string",
71
+ "number"
72
+ ],
73
+ "description": "本 endpoint 的框架 master(Telegram user id);覆盖顶层 master"
74
+ },
75
+ "trusted": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": [
79
+ "string",
80
+ "number"
81
+ ],
82
+ "description": "Trusted Telegram user id"
83
+ },
84
+ "description": "本 endpoint 的 trusted 列表"
53
85
  },
54
86
  "token": {
55
87
  "type": "string",
56
88
  "description": "Telegram bot token"
89
+ },
90
+ "id": {
91
+ "type": "string",
92
+ "description": "Telegram bot name"
57
93
  }
58
94
  },
59
95
  "required": [
60
- "name",
96
+ "id",
61
97
  "token"
62
98
  ]
63
99
  }
package/src/endpoint.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  parseLegacyMessageReference,
12
12
  type ConversationRef,
13
13
  } from '@zhin.js/im-contract';
14
- import { formatCompact, getLogger } from '@zhin.js/logger';
14
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
15
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
16
16
  import { runTelegramPollLoop } from './polling.js';
17
17
  import { normalizeTelegramChatMember } from './platform-permit.js';
@@ -36,8 +36,6 @@ import {
36
36
  import { registerTelegramAgentEndpoint } from './telegram-agent-deps.js';
37
37
  import { registerTelegramWebhookRoutes } from './webhook.js';
38
38
 
39
- const logger = getLogger('telegram');
40
-
41
39
  const CHAT_MEMBER_CACHE_TTL_MS = 60_000;
42
40
  const CHAT_MEMBER_CACHE_MAX = 2_000;
43
41
 
@@ -87,6 +85,8 @@ interface TelegramApiErr {
87
85
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
88
86
  */
89
87
  export class TelegramEndpoint implements EndpointInstance {
88
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
89
+
90
90
  readonly #options: TelegramEndpointOptions;
91
91
  readonly #fetch: TelegramFetch;
92
92
  #pollAbort?: AbortController;
@@ -104,6 +104,7 @@ export class TelegramEndpoint implements EndpointInstance {
104
104
  });
105
105
 
106
106
  constructor(options: TelegramEndpointOptions) {
107
+ this.#logger = getAdapterLogger('telegram', options.config.id);
107
108
  this.#options = options;
108
109
  this.#fetch = options.fetch ?? globalThis.fetch;
109
110
  }
@@ -133,7 +134,7 @@ export class TelegramEndpoint implements EndpointInstance {
133
134
  if (this.#started) return;
134
135
  this.#started = true;
135
136
  try {
136
- this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.name, this);
137
+ this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.id, this);
137
138
  const me = await this.callApi<{ id?: number; username?: string; first_name?: string }>('getMe');
138
139
  this.#botUserId = me.id;
139
140
  this.#botUsername = me.username;
@@ -146,9 +147,9 @@ export class TelegramEndpoint implements EndpointInstance {
146
147
  const webhook = this.#options.config.webhook!;
147
148
  if (!webhook.secretToken) {
148
149
  // 未配 secretToken 时 webhook 无鉴权:任何人知道 path 即可注入假 update。
149
- logger.warn(formatCompact({
150
+ this.#logger.warn(formatCompact({
150
151
  op: 'webhook_no_secret',
151
- endpoint: this.#options.config.name,
152
+ endpoint: this.#options.config.id,
152
153
  path: webhook.path,
153
154
  hint: 'set webhook.secretToken to authenticate Telegram callbacks',
154
155
  }));
@@ -159,9 +160,9 @@ export class TelegramEndpoint implements EndpointInstance {
159
160
  allowed_updates: this.#options.config.allowedUpdates,
160
161
  ...(webhook.secretToken ? { secret_token: webhook.secretToken } : {}),
161
162
  });
162
- logger.info(formatCompact({
163
+ this.#logger.info(formatCompact({
163
164
  op: 'connect',
164
- endpoint: this.#options.config.name,
165
+ endpoint: this.#options.config.id,
165
166
  mode: 'webhook',
166
167
  path: webhook.path,
167
168
  username: me.username,
@@ -172,15 +173,15 @@ export class TelegramEndpoint implements EndpointInstance {
172
173
  await this.callApi('deleteWebhook', { drop_pending_updates: false });
173
174
  this.#pollAbort = new AbortController();
174
175
  this.#pollPromise = runTelegramPollLoop(this, this.#pollAbort.signal);
175
- logger.info(formatCompact({
176
+ this.#logger.info(formatCompact({
176
177
  op: 'connect',
177
- endpoint: this.#options.config.name,
178
+ endpoint: this.#options.config.id,
178
179
  mode: 'polling',
179
180
  username: me.username,
180
181
  }));
181
182
  } catch (error) {
182
183
  await this.stop();
183
- logger.error('Failed to connect Telegram bot:', error);
184
+ this.#logger.error('Failed to connect Telegram bot:', error);
184
185
  throw error;
185
186
  }
186
187
  }
@@ -206,7 +207,7 @@ export class TelegramEndpoint implements EndpointInstance {
206
207
  this.#unregisterAgent = undefined;
207
208
  this.#chatMemberCache.clear();
208
209
  this.#started = false;
209
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
210
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
210
211
  }
211
212
 
212
213
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
@@ -269,7 +270,7 @@ export class TelegramEndpoint implements EndpointInstance {
269
270
  if (!this.#open) return;
270
271
  const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
271
272
  void this.#admitWithSenderRole(msg, conversation).catch((err) => {
272
- logger.warn(formatCompact({
273
+ this.#logger.warn(formatCompact({
273
274
  op: 'telegram_gateway_receive_failed',
274
275
  target: `${conversation.kind}:${conversation.id}`,
275
276
  error: err instanceof Error ? err.message : String(err),
@@ -286,18 +287,20 @@ export class TelegramEndpoint implements EndpointInstance {
286
287
  message: { conversation, id: String(msg.message_id) },
287
288
  content: formatInboundContent(msg),
288
289
  segments: formatInboundSegments(msg),
289
- sender: senderDisplayName(msg.from),
290
+ sender: {
291
+ id: String(msg.from?.id ?? ''),
292
+ name: senderDisplayName(msg.from) || undefined,
293
+ ...(permit?.role ? { roles: [permit.role] } : {}),
294
+ },
295
+ endpointId: this.#options.config.id,
296
+ ...(mentioned ? { mentioned: true } : {}),
290
297
  metadata: Object.freeze({
291
- endpoint: this.#options.config.name,
292
- // Preserve Telegram's native value below, but publish the canonical
293
- // scene kind so every Runtime consumer sees the same conversation type.
294
298
  channelType: resolveTelegramChannelType(msg.chat.type),
295
299
  chatType: msg.chat.type,
296
300
  userId: msg.from?.id,
297
301
  date: msg.date,
298
302
  ...(permit?.role ? { senderRole: permit.role } : {}),
299
303
  ...(permit?.permissions.length ? { senderPermissions: [...permit.permissions] } : {}),
300
- ...(mentioned ? { mentioned: true } : {}),
301
304
  }),
302
305
  });
303
306
  }
@@ -360,26 +363,26 @@ export class TelegramEndpoint implements EndpointInstance {
360
363
  /** Test / internal: admit a callback query when open. */
361
364
  admitCallback(query: TelegramCallbackQuery): void {
362
365
  if (!this.#open) return;
363
- const endpointId = String(this.#options.id);
366
+ const endpointKey = String(this.#options.id);
364
367
  const msg = query.message;
365
368
  // 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
366
369
  const conversation = msg
367
- ? telegramInboundConversation(endpointId, msg.chat)
368
- : telegramInboundConversation(endpointId, { id: query.from.id, type: 'private' });
370
+ ? telegramInboundConversation(endpointKey, msg.chat)
371
+ : telegramInboundConversation(endpointKey, { id: query.from.id, type: 'private' });
369
372
  void this.#options.gateway.receive({
370
373
  conversation,
371
374
  message: { conversation, id: query.id },
372
375
  content: formatCallbackContent(query),
373
376
  segments: formatCallbackSegments(query),
374
- sender: senderDisplayName(query.from),
377
+ sender: { id: String(query.from?.id ?? ''), name: senderDisplayName(query.from) || undefined },
378
+ endpointId: this.#options.config.id,
375
379
  metadata: Object.freeze({
376
- endpoint: this.#options.config.name,
377
380
  eventType: 'callback_query',
378
381
  payload: query.data,
379
382
  sourceMessageId: msg ? String(msg.message_id) : undefined,
380
383
  }),
381
384
  }).catch((err) => {
382
- logger.warn(formatCompact({
385
+ this.#logger.warn(formatCompact({
383
386
  op: 'telegram_gateway_receive_failed',
384
387
  target: `${conversation.kind}:${conversation.id}`,
385
388
  error: err instanceof Error ? err.message : String(err),
package/src/index.ts CHANGED
@@ -39,6 +39,5 @@ export {
39
39
  checkTelegramPlatformPermit,
40
40
  normalizeTelegramChatMember,
41
41
  platformPermit,
42
- registerTelegramPlatformPermitChecker,
43
42
  telegramGroupPermitResolver,
44
43
  } from './platform-permit.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Telegram platform permit — ChatMember 状态 + 细粒度权限
3
3
  */
4
- import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
4
+ import type { PermissionSubject } from '@zhin.js/permission';
5
5
 
6
6
  const ADAPTER = 'telegram';
7
7
 
@@ -42,13 +42,9 @@ export function normalizeTelegramChatMember(member: {
42
42
  return { role: 'member', permissions };
43
43
  }
44
44
 
45
- function senderPermits(message: Message<any>): { role?: string; permissions: string[] } {
46
- const sender = message.$sender as { role?: string; permissions?: string[] };
47
- return { role: sender.role, permissions: sender.permissions ?? [] };
48
- }
49
-
50
- export function checkTelegramPlatformPermit(perm: string, message: Message<any>): boolean {
51
- const { role, permissions } = senderPermits(message);
45
+ export function checkTelegramPlatformPermit(perm: string, subject: PermissionSubject): boolean {
46
+ const role = subject.sender?.role?.[0];
47
+ const permissions = subject.sender?.permissions ?? [];
52
48
  const has = (t: string) => permissions.includes(t);
53
49
 
54
50
  switch (perm) {
@@ -67,6 +63,3 @@ export function checkTelegramPlatformPermit(perm: string, message: Message<any>)
67
63
  }
68
64
  }
69
65
 
70
- export function registerTelegramPlatformPermitChecker(): () => void {
71
- return registerPlatformPermitChecker(ADAPTER, checkTelegramPlatformPermit);
72
- }
package/src/protocol.ts CHANGED
@@ -13,7 +13,7 @@ const logger = getLogger('telegram');
13
13
 
14
14
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
15
15
  export interface TelegramAdapterConfig {
16
- readonly name?: string;
16
+ readonly id?: string;
17
17
  readonly token?: string;
18
18
  /** Default true. `false` selects webhook mode (requires httpHostToken). */
19
19
  readonly polling?: boolean;
@@ -36,7 +36,7 @@ export interface TelegramAdapterConfig {
36
36
 
37
37
  export interface ResolvedTelegramConfig {
38
38
  readonly context: 'telegram';
39
- readonly name: string;
39
+ readonly id: string;
40
40
  readonly token: string;
41
41
  readonly mode: 'polling' | 'webhook';
42
42
  readonly allowedUpdates: readonly string[];
@@ -250,8 +250,8 @@ export function resolveTelegramConfig(config: TelegramAdapterConfig = {}): Resol
250
250
  'Telegram adapter requires token (plugins.<key>.token or endpoints with context: telegram)',
251
251
  );
252
252
  }
253
- const name = (typeof config.name === 'string' && config.name)
254
- || (typeof entry?.name === 'string' && entry.name)
253
+ const id = (typeof config.id === 'string' && config.id)
254
+ || (typeof entry?.id === 'string' && entry.id)
255
255
  || process.env.TELEGRAM_BOT_NAME
256
256
  || 'telegram-bot';
257
257
  const polling = config.polling ?? entry?.polling;
@@ -277,7 +277,7 @@ export function resolveTelegramConfig(config: TelegramAdapterConfig = {}): Resol
277
277
  : undefined;
278
278
  return {
279
279
  context: 'telegram',
280
- name,
280
+ id,
281
281
  token,
282
282
  mode,
283
283
  allowedUpdates: [...allowedUpdates],
@@ -336,11 +336,11 @@ export function resolveTelegramChannelType(
336
336
  * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
337
337
  */
338
338
  export function telegramInboundConversation(
339
- endpointId: string,
339
+ endpointKey: string,
340
340
  chat: Pick<TelegramChat, 'id' | 'type'>,
341
341
  ): ConversationRef {
342
342
  return {
343
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
343
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
344
344
  kind: resolveTelegramChannelType(chat.type),
345
345
  id: String(chat.id),
346
346
  };
@@ -28,20 +28,20 @@ export interface TelegramAgentEndpoint {
28
28
  }
29
29
 
30
30
  export interface TelegramAgentDeps {
31
- getEndpoint: (endpointId: string) => TelegramAgentEndpoint;
31
+ getEndpoint: (endpointKey: string) => TelegramAgentEndpoint;
32
32
  }
33
33
 
34
34
  const endpoints = new Map<string, TelegramAgentEndpoint>();
35
35
  let override: TelegramAgentDeps | null = null;
36
36
 
37
37
  export function registerTelegramAgentEndpoint(
38
- endpointId: string,
38
+ endpointKey: string,
39
39
  endpoint: TelegramAgentEndpoint,
40
40
  ): () => void {
41
- endpoints.set(endpointId, endpoint);
41
+ endpoints.set(endpointKey, endpoint);
42
42
  return () => {
43
- if (endpoints.get(endpointId) === endpoint) {
44
- endpoints.delete(endpointId);
43
+ if (endpoints.get(endpointKey) === endpoint) {
44
+ endpoints.delete(endpointKey);
45
45
  }
46
46
  };
47
47
  }
@@ -54,9 +54,9 @@ export function setTelegramAgentDeps(deps: TelegramAgentDeps | null): void {
54
54
  export function getTelegramAgentDeps(): TelegramAgentDeps {
55
55
  if (override) return override;
56
56
  return {
57
- getEndpoint(endpointId: string): TelegramAgentEndpoint {
58
- const registered = endpoints.get(endpointId);
59
- if (!registered) throw new Error(`Endpoint ${endpointId} 不存在`);
57
+ getEndpoint(endpointKey: string): TelegramAgentEndpoint {
58
+ const registered = endpoints.get(endpointKey);
59
+ if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
60
60
  return registered;
61
61
  },
62
62
  };
File without changes
File without changes
File without changes
File without changes