@zhin.js/adapter-telegram 7.0.2 → 7.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,100 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
8
+
9
+ All adapter platform-permit checkers now accept PermissionSubject
10
+ (duck-typed from Message/CommandSession) instead of Message directly.
11
+ registerPlatformPermitChecker is replaced by host.registerPlatform in
12
+ the adapter plugin setup. activity-feedback service updated for new
13
+ PermissionHost integration.
14
+
15
+ - Updated dependencies [6f9c366]
16
+ - Updated dependencies [373a56b]
17
+ - Updated dependencies [0de46a8]
18
+ - Updated dependencies [c106ecc]
19
+ - Updated dependencies [ca92e03]
20
+ - Updated dependencies [b0f37ae]
21
+ - Updated dependencies [ba08a2f]
22
+ - Updated dependencies [b08f7fe]
23
+ - Updated dependencies [daffd4c]
24
+ - Updated dependencies [36c7400]
25
+ - Updated dependencies [a9fa72e]
26
+ - Updated dependencies [c8de3ef]
27
+ - Updated dependencies [60f0fc8]
28
+ - Updated dependencies [574c990]
29
+ - Updated dependencies [d096f16]
30
+ - Updated dependencies [3f29623]
31
+ - Updated dependencies [2916852]
32
+ - Updated dependencies [d047869]
33
+ - Updated dependencies [162fa34]
34
+ - Updated dependencies [e62561e]
35
+ - Updated dependencies [3eeeb46]
36
+ - Updated dependencies [85d0f82]
37
+ - Updated dependencies [61bfc1c]
38
+ - Updated dependencies [04bad47]
39
+ - Updated dependencies [e40b048]
40
+ - Updated dependencies [5a5b1bb]
41
+ - Updated dependencies [f1708c3]
42
+ - Updated dependencies [d254a81]
43
+ - Updated dependencies [7123c47]
44
+ - Updated dependencies [05befc1]
45
+ - Updated dependencies [0663b6a]
46
+ - Updated dependencies [f28f9b3]
47
+ - Updated dependencies [9f340f7]
48
+ - Updated dependencies [e53444f]
49
+ - Updated dependencies [5eedd26]
50
+ - Updated dependencies [92b0dd7]
51
+ - Updated dependencies [f919b6f]
52
+ - Updated dependencies [098e411]
53
+ - Updated dependencies [e1b7c01]
54
+ - Updated dependencies [9b94f87]
55
+ - Updated dependencies [a7df753]
56
+ - @zhin.js/agent@1.1.5
57
+ - @zhin.js/host-http@1.0.8
58
+ - @zhin.js/permission@1.0.1
59
+ - @zhin.js/im-contract@1.0.3
60
+ - @zhin.js/adapter@1.1.7
61
+ - @zhin.js/plugin-runtime@1.1.5
62
+ - @zhin.js/command@1.0.9
63
+ - @zhin.js/core@1.5.4
64
+ - zhin.js@6.0.4
65
+
66
+ ## 7.0.4
67
+
68
+ ### Patch Changes
69
+
70
+ - f8c7a54: fix: im
71
+ - Updated dependencies [f8c7a54]
72
+ - @zhin.js/logger@1.0.76
73
+ - @zhin.js/host-http@1.0.7
74
+ - @zhin.js/adapter@1.1.6
75
+ - @zhin.js/agent@1.1.4
76
+ - @zhin.js/command@1.0.8
77
+ - @zhin.js/core@1.5.3
78
+ - @zhin.js/im-contract@1.0.2
79
+ - @zhin.js/plugin-runtime@1.1.4
80
+ - zhin.js@6.0.3
81
+
82
+ ## 7.0.3
83
+
84
+ ### Patch Changes
85
+
86
+ - Updated dependencies [afc0e66]
87
+ - Updated dependencies [2e41ad5]
88
+ - Updated dependencies [9f57124]
89
+ - @zhin.js/core@1.5.2
90
+ - @zhin.js/adapter@1.1.5
91
+ - @zhin.js/im-contract@1.0.1
92
+ - @zhin.js/plugin-runtime@1.1.3
93
+ - @zhin.js/command@1.0.7
94
+ - @zhin.js/host-http@1.0.6
95
+ - @zhin.js/agent@1.1.3
96
+ - zhin.js@6.0.2
97
+
3
98
  ## 7.0.2
4
99
 
5
100
  ### Patch Changes
package/README.md CHANGED
@@ -7,7 +7,7 @@ Zhin.js Telegram Bot API 适配器(Plugin Runtime),默认通过 **长轮
7
7
  - 长轮询 `getUpdates` 入站(默认;无需公网 IP / host-http)
8
8
  - 解析 text / image / video / audio / voice / document / sticker / location / callback_query
9
9
  - 支持私聊与群组
10
- - 出站 `send({ target, payload })` → Bot API(text / media / keyboard)
10
+ - 出站 `send({ conversation, payload })` → Bot API(text / media / keyboard)
11
11
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
12
  - Webhook 模式延期(需 `httpHostToken`);配置 `polling: false` 会明确报错
13
13
 
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-telegram
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
  - **无需** `@zhin.js/host-http`(polling 路径)
27
27
 
28
- 入站:`gateway.receive({ adapter, target: chatId, content: text, sender, metadata })`
29
- 出站:`send({ target, payload })` → Telegram Bot API
28
+ 入站:`gateway.receive({ conversation, message, content, sender, metadata })`(`conversation` 为 ConversationRef,由 `telegramInboundConversation` 归一化)
29
+ 出站:`send({ conversation, payload })` → Telegram Bot API
30
30
 
31
31
  ### 平台权限(platform permit)
32
32
 
@@ -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.d.ts CHANGED
@@ -40,7 +40,7 @@ export declare class TelegramEndpoint implements EndpointInstance {
40
40
  open(): void;
41
41
  close(): void;
42
42
  stop(): Promise<void>;
43
- send({ target, conversation, payload }: EndpointSendRequest): Promise<string>;
43
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
44
44
  recallMessage(messageId: string): Promise<void>;
45
45
  /** Test / internal: admit a message when open. */
46
46
  admit(msg: TelegramMessage): void;
package/lib/endpoint.js CHANGED
@@ -2,14 +2,13 @@
2
2
  * TelegramEndpoint — lifecycle, outbound, admit, Bot API helpers for agent tools.
3
3
  */
4
4
  import { readFile } from 'node:fs/promises';
5
- import { formatLegacyMessageReference, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
6
- import { formatCompact, getLogger } from '@zhin.js/logger';
5
+ import { formatLegacyMessageRef, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
6
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
7
  import { runTelegramPollLoop } from './polling.js';
8
8
  import { normalizeTelegramChatMember } from './platform-permit.js';
9
- import { botApiUrl, buildWebhookUrl, formatCallbackContent, formatCallbackSegments, formatInboundContent, formatInboundSegments, formatOutboundPlan, resolveChannel, senderDisplayName, } from './protocol.js';
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,11 +130,10 @@ 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
- async send({ target, conversation, payload }) {
135
- const chatId = nativeConversationId(target, conversation);
136
- const plan = formatOutboundPlan(chatId, payload);
135
+ async send({ conversation, payload }) {
136
+ const plan = formatOutboundPlan(conversation.id, payload);
137
137
  let lastId = '';
138
138
  for (const action of plan.actions) {
139
139
  const form = await this.#buildUploadForm(action.params, plan.uploads);
@@ -145,7 +145,7 @@ export class TelegramEndpoint {
145
145
  }
146
146
  if (!lastId)
147
147
  return `telegram-${Date.now()}`;
148
- return formatLegacyMessageReference({ target, messageId: lastId });
148
+ return formatLegacyMessageRef({ conversation, id: lastId });
149
149
  }
150
150
  async recallMessage(messageId) {
151
151
  if (!messageId || messageId.startsWith('telegram-'))
@@ -188,37 +188,38 @@ export class TelegramEndpoint {
188
188
  admit(msg) {
189
189
  if (!this.#open)
190
190
  return;
191
- const { channelId } = resolveChannel(msg);
192
- void this.#admitWithSenderRole(msg, channelId).catch((err) => {
193
- logger.warn(formatCompact({
191
+ const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
192
+ void this.#admitWithSenderRole(msg, conversation).catch((err) => {
193
+ this.#logger.warn(formatCompact({
194
194
  op: 'telegram_gateway_receive_failed',
195
- target: channelId,
195
+ target: `${conversation.kind}:${conversation.id}`,
196
196
  error: err instanceof Error ? err.message : String(err),
197
197
  }));
198
198
  });
199
199
  }
200
- async #admitWithSenderRole(msg, channelId) {
200
+ async #admitWithSenderRole(msg, conversation) {
201
201
  const permit = await this.#resolveGroupSenderPermit(msg);
202
202
  // 新 Runtime Message.content 为纯文本:@ 本机只能经 metadata 传递
203
203
  const mentioned = this.#isBotMentioned(msg);
204
204
  await this.#options.gateway.receive({
205
- adapter: this.#options.id,
206
- target: channelId,
205
+ conversation,
206
+ message: { conversation, id: String(msg.message_id) },
207
207
  content: formatInboundContent(msg),
208
208
  segments: formatInboundSegments(msg),
209
- sender: senderDisplayName(msg.from),
210
- id: String(msg.message_id),
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 } : {}),
211
216
  metadata: Object.freeze({
212
- endpoint: this.#options.config.name,
213
- // Preserve Telegram's native value below, but publish the canonical
214
- // scene kind so every Runtime consumer sees the same conversation type.
215
217
  channelType: resolveTelegramChannelType(msg.chat.type),
216
218
  chatType: msg.chat.type,
217
219
  userId: msg.from?.id,
218
220
  date: msg.date,
219
221
  ...(permit?.role ? { senderRole: permit.role } : {}),
220
222
  ...(permit?.permissions.length ? { senderPermissions: [...permit.permissions] } : {}),
221
- ...(mentioned ? { mentioned: true } : {}),
222
223
  }),
223
224
  });
224
225
  }
@@ -287,25 +288,28 @@ export class TelegramEndpoint {
287
288
  admitCallback(query) {
288
289
  if (!this.#open)
289
290
  return;
291
+ const endpointKey = String(this.#options.id);
290
292
  const msg = query.message;
291
- const channelId = msg ? resolveChannel(msg).channelId : String(query.from.id);
293
+ // 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
294
+ const conversation = msg
295
+ ? telegramInboundConversation(endpointKey, msg.chat)
296
+ : telegramInboundConversation(endpointKey, { id: query.from.id, type: 'private' });
292
297
  void this.#options.gateway.receive({
293
- adapter: this.#options.id,
294
- target: channelId,
298
+ conversation,
299
+ message: { conversation, id: query.id },
295
300
  content: formatCallbackContent(query),
296
301
  segments: formatCallbackSegments(query),
297
- sender: senderDisplayName(query.from),
298
- id: query.id,
302
+ sender: { id: String(query.from?.id ?? ''), name: senderDisplayName(query.from) || undefined },
303
+ endpointId: this.#options.config.id,
299
304
  metadata: Object.freeze({
300
- endpoint: this.#options.config.name,
301
305
  eventType: 'callback_query',
302
306
  payload: query.data,
303
307
  sourceMessageId: msg ? String(msg.message_id) : undefined,
304
308
  }),
305
309
  }).catch((err) => {
306
- logger.warn(formatCompact({
310
+ this.#logger.warn(formatCompact({
307
311
  op: 'telegram_gateway_receive_failed',
308
- target: channelId,
312
+ target: `${conversation.kind}:${conversation.id}`,
309
313
  error: err instanceof Error ? err.message : String(err),
310
314
  }));
311
315
  });
@@ -413,10 +417,3 @@ export class TelegramEndpoint {
413
417
  });
414
418
  }
415
419
  }
416
- function resolveTelegramChannelType(chatType) {
417
- if (chatType === 'private')
418
- return 'private';
419
- if (chatType === 'channel')
420
- return 'channel';
421
- return 'group';
422
- }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { TelegramEndpoint, type TelegramEndpointOptions, type TelegramFetch, } from './endpoint.js';
2
- export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveChannel, 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';
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
- export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveChannel, resolveTelegramConfig, senderDisplayName, } from './protocol.js';
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
@@ -4,9 +4,10 @@
4
4
  */
5
5
  import type { IncomingMessage } from 'node:http';
6
6
  import type { Segment } from '@zhin.js/core/runtime';
7
+ import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
7
8
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
8
9
  export interface TelegramAdapterConfig {
9
- readonly name?: string;
10
+ readonly id?: string;
10
11
  readonly token?: string;
11
12
  /** Default true. `false` selects webhook mode (requires httpHostToken). */
12
13
  readonly polling?: boolean;
@@ -28,7 +29,7 @@ export interface TelegramAdapterConfig {
28
29
  }
29
30
  export interface ResolvedTelegramConfig {
30
31
  readonly context: 'telegram';
31
- readonly name: string;
32
+ readonly id: string;
32
33
  readonly token: string;
33
34
  readonly mode: 'polling' | 'webhook';
34
35
  readonly allowedUpdates: readonly string[];
@@ -236,10 +237,13 @@ export declare function readTextBody(request: IncomingMessage, options?: {
236
237
  readonly limit?: number;
237
238
  }): Promise<string>;
238
239
  export declare function botApiUrl(config: Pick<ResolvedTelegramConfig, 'apiBaseUrl' | 'token'>, method: string): string;
239
- export declare function resolveChannel(msg: Pick<TelegramMessage, 'chat'>): {
240
- readonly channelType: 'private' | 'group';
241
- readonly channelId: string;
242
- };
240
+ /** Telegram chat.type canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
241
+ export declare function resolveTelegramChannelType(chatType: TelegramChat['type']): ConversationKind;
242
+ /**
243
+ * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
244
+ * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
245
+ */
246
+ export declare function telegramInboundConversation(endpointKey: string, chat: Pick<TelegramChat, 'id' | 'type'>): ConversationRef;
243
247
  export declare function senderDisplayName(user?: TelegramUser): string;
244
248
  /** Build inbound text for MessageGateway.receive. */
245
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],
@@ -76,10 +76,23 @@ export async function readTextBody(request, options = {}) {
76
76
  export function botApiUrl(config, method) {
77
77
  return `${config.apiBaseUrl}/bot${config.token}/${method}`;
78
78
  }
79
- export function resolveChannel(msg) {
79
+ /** Telegram chat.type → canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
80
+ export function resolveTelegramChannelType(chatType) {
81
+ if (chatType === 'private')
82
+ return 'private';
83
+ if (chatType === 'channel')
84
+ return 'channel';
85
+ return 'group';
86
+ }
87
+ /**
88
+ * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
89
+ * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
90
+ */
91
+ export function telegramInboundConversation(endpointKey, chat) {
80
92
  return {
81
- channelType: msg.chat.type === 'private' ? 'private' : 'group',
82
- channelId: String(msg.chat.id),
93
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
94
+ kind: resolveTelegramChannelType(chat.type),
95
+ id: String(chat.id),
83
96
  };
84
97
  }
85
98
  export function senderDisplayName(user) {
@@ -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.2",
3
+ "version": "7.0.5",
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.4",
36
- "@zhin.js/command": "1.0.6",
37
- "@zhin.js/core": "1.5.1",
38
- "@zhin.js/host-http": "1.0.5",
39
- "@zhin.js/im-contract": "1.0.0",
40
- "@zhin.js/logger": "1.0.75",
41
- "@zhin.js/plugin-runtime": "1.1.2"
35
+ "@zhin.js/adapter": "1.1.7",
36
+ "@zhin.js/command": "1.0.9",
37
+ "@zhin.js/core": "1.5.4",
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.4",
46
- "@zhin.js/agent": "1.1.2",
47
- "@zhin.js/core": "1.5.1",
48
- "@zhin.js/plugin-runtime": "1.1.2",
49
- "zhin.js": "6.0.1"
45
+ "@zhin.js/adapter": "1.1.7",
46
+ "@zhin.js/agent": "1.1.5",
47
+ "@zhin.js/core": "1.5.4",
48
+ "@zhin.js/permission": "1.0.1",
49
+ "@zhin.js/plugin-runtime": "1.1.5",
50
+ "zhin.js": "6.0.4"
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.2",
68
- "@zhin.js/host-http": "1.0.5",
69
- "zhin.js": "6.0.1"
68
+ "@zhin.js/agent": "1.1.5",
69
+ "@zhin.js/host-http": "1.0.8",
70
+ "zhin.js": "6.0.4"
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
@@ -6,11 +6,12 @@ import type { EndpointControl, EndpointInstance, EndpointSendRequest } from '@zh
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
8
  import {
9
- formatLegacyMessageReference,
9
+ formatLegacyMessageRef,
10
10
  nativeConversationId,
11
11
  parseLegacyMessageReference,
12
+ type ConversationRef,
12
13
  } from '@zhin.js/im-contract';
13
- import { formatCompact, getLogger } from '@zhin.js/logger';
14
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
14
15
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
15
16
  import { runTelegramPollLoop } from './polling.js';
16
17
  import { normalizeTelegramChatMember } from './platform-permit.js';
@@ -22,8 +23,9 @@ import {
22
23
  formatInboundContent,
23
24
  formatInboundSegments,
24
25
  formatOutboundPlan,
25
- resolveChannel,
26
+ resolveTelegramChannelType,
26
27
  senderDisplayName,
28
+ telegramInboundConversation,
27
29
  type ResolvedTelegramConfig,
28
30
  type TelegramCallbackQuery,
29
31
  type TelegramChatMember,
@@ -34,8 +36,6 @@ import {
34
36
  import { registerTelegramAgentEndpoint } from './telegram-agent-deps.js';
35
37
  import { registerTelegramWebhookRoutes } from './webhook.js';
36
38
 
37
- const logger = getLogger('telegram');
38
-
39
39
  const CHAT_MEMBER_CACHE_TTL_MS = 60_000;
40
40
  const CHAT_MEMBER_CACHE_MAX = 2_000;
41
41
 
@@ -85,6 +85,8 @@ interface TelegramApiErr {
85
85
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
86
86
  */
87
87
  export class TelegramEndpoint implements EndpointInstance {
88
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
89
+
88
90
  readonly #options: TelegramEndpointOptions;
89
91
  readonly #fetch: TelegramFetch;
90
92
  #pollAbort?: AbortController;
@@ -102,6 +104,7 @@ export class TelegramEndpoint implements EndpointInstance {
102
104
  });
103
105
 
104
106
  constructor(options: TelegramEndpointOptions) {
107
+ this.#logger = getAdapterLogger('telegram', options.config.id);
105
108
  this.#options = options;
106
109
  this.#fetch = options.fetch ?? globalThis.fetch;
107
110
  }
@@ -131,7 +134,7 @@ export class TelegramEndpoint implements EndpointInstance {
131
134
  if (this.#started) return;
132
135
  this.#started = true;
133
136
  try {
134
- this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.name, this);
137
+ this.#unregisterAgent = registerTelegramAgentEndpoint(this.#options.config.id, this);
135
138
  const me = await this.callApi<{ id?: number; username?: string; first_name?: string }>('getMe');
136
139
  this.#botUserId = me.id;
137
140
  this.#botUsername = me.username;
@@ -144,9 +147,9 @@ export class TelegramEndpoint implements EndpointInstance {
144
147
  const webhook = this.#options.config.webhook!;
145
148
  if (!webhook.secretToken) {
146
149
  // 未配 secretToken 时 webhook 无鉴权:任何人知道 path 即可注入假 update。
147
- logger.warn(formatCompact({
150
+ this.#logger.warn(formatCompact({
148
151
  op: 'webhook_no_secret',
149
- endpoint: this.#options.config.name,
152
+ endpoint: this.#options.config.id,
150
153
  path: webhook.path,
151
154
  hint: 'set webhook.secretToken to authenticate Telegram callbacks',
152
155
  }));
@@ -157,9 +160,9 @@ export class TelegramEndpoint implements EndpointInstance {
157
160
  allowed_updates: this.#options.config.allowedUpdates,
158
161
  ...(webhook.secretToken ? { secret_token: webhook.secretToken } : {}),
159
162
  });
160
- logger.info(formatCompact({
163
+ this.#logger.info(formatCompact({
161
164
  op: 'connect',
162
- endpoint: this.#options.config.name,
165
+ endpoint: this.#options.config.id,
163
166
  mode: 'webhook',
164
167
  path: webhook.path,
165
168
  username: me.username,
@@ -170,15 +173,15 @@ export class TelegramEndpoint implements EndpointInstance {
170
173
  await this.callApi('deleteWebhook', { drop_pending_updates: false });
171
174
  this.#pollAbort = new AbortController();
172
175
  this.#pollPromise = runTelegramPollLoop(this, this.#pollAbort.signal);
173
- logger.info(formatCompact({
176
+ this.#logger.info(formatCompact({
174
177
  op: 'connect',
175
- endpoint: this.#options.config.name,
178
+ endpoint: this.#options.config.id,
176
179
  mode: 'polling',
177
180
  username: me.username,
178
181
  }));
179
182
  } catch (error) {
180
183
  await this.stop();
181
- logger.error('Failed to connect Telegram bot:', error);
184
+ this.#logger.error('Failed to connect Telegram bot:', error);
182
185
  throw error;
183
186
  }
184
187
  }
@@ -204,12 +207,11 @@ export class TelegramEndpoint implements EndpointInstance {
204
207
  this.#unregisterAgent = undefined;
205
208
  this.#chatMemberCache.clear();
206
209
  this.#started = false;
207
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
210
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
208
211
  }
209
212
 
210
- async send({ target, conversation, payload }: EndpointSendRequest): Promise<string> {
211
- const chatId = nativeConversationId(target, conversation);
212
- const plan = formatOutboundPlan(chatId, payload);
213
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
214
+ const plan = formatOutboundPlan(conversation.id, payload);
213
215
  let lastId = '';
214
216
  for (const action of plan.actions) {
215
217
  const form = await this.#buildUploadForm(action.params, plan.uploads);
@@ -219,7 +221,7 @@ export class TelegramEndpoint implements EndpointInstance {
219
221
  if (result.message_id != null) lastId = String(result.message_id);
220
222
  }
221
223
  if (!lastId) return `telegram-${Date.now()}`;
222
- return formatLegacyMessageReference({ target, messageId: lastId });
224
+ return formatLegacyMessageRef({ conversation, id: lastId });
223
225
  }
224
226
 
225
227
  async recallMessage(messageId: string): Promise<void> {
@@ -266,38 +268,39 @@ export class TelegramEndpoint implements EndpointInstance {
266
268
  /** Test / internal: admit a message when open. */
267
269
  admit(msg: TelegramMessage): void {
268
270
  if (!this.#open) return;
269
- const { channelId } = resolveChannel(msg);
270
- void this.#admitWithSenderRole(msg, channelId).catch((err) => {
271
- logger.warn(formatCompact({
271
+ const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
272
+ void this.#admitWithSenderRole(msg, conversation).catch((err) => {
273
+ this.#logger.warn(formatCompact({
272
274
  op: 'telegram_gateway_receive_failed',
273
- target: channelId,
275
+ target: `${conversation.kind}:${conversation.id}`,
274
276
  error: err instanceof Error ? err.message : String(err),
275
277
  }));
276
278
  });
277
279
  }
278
280
 
279
- async #admitWithSenderRole(msg: TelegramMessage, channelId: string): Promise<void> {
281
+ async #admitWithSenderRole(msg: TelegramMessage, conversation: ConversationRef): Promise<void> {
280
282
  const permit = await this.#resolveGroupSenderPermit(msg);
281
283
  // 新 Runtime Message.content 为纯文本:@ 本机只能经 metadata 传递
282
284
  const mentioned = this.#isBotMentioned(msg);
283
285
  await this.#options.gateway.receive({
284
- adapter: this.#options.id,
285
- target: channelId,
286
+ conversation,
287
+ message: { conversation, id: String(msg.message_id) },
286
288
  content: formatInboundContent(msg),
287
289
  segments: formatInboundSegments(msg),
288
- sender: senderDisplayName(msg.from),
289
- id: String(msg.message_id),
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,25 +363,28 @@ 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;
366
+ const endpointKey = String(this.#options.id);
363
367
  const msg = query.message;
364
- const channelId = msg ? resolveChannel(msg).channelId : String(query.from.id);
368
+ // 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
369
+ const conversation = msg
370
+ ? telegramInboundConversation(endpointKey, msg.chat)
371
+ : telegramInboundConversation(endpointKey, { id: query.from.id, type: 'private' });
365
372
  void this.#options.gateway.receive({
366
- adapter: this.#options.id,
367
- target: channelId,
373
+ conversation,
374
+ message: { conversation, id: query.id },
368
375
  content: formatCallbackContent(query),
369
376
  segments: formatCallbackSegments(query),
370
- sender: senderDisplayName(query.from),
371
- id: query.id,
377
+ sender: { id: String(query.from?.id ?? ''), name: senderDisplayName(query.from) || undefined },
378
+ endpointId: this.#options.config.id,
372
379
  metadata: Object.freeze({
373
- endpoint: this.#options.config.name,
374
380
  eventType: 'callback_query',
375
381
  payload: query.data,
376
382
  sourceMessageId: msg ? String(msg.message_id) : undefined,
377
383
  }),
378
384
  }).catch((err) => {
379
- logger.warn(formatCompact({
385
+ this.#logger.warn(formatCompact({
380
386
  op: 'telegram_gateway_receive_failed',
381
- target: channelId,
387
+ target: `${conversation.kind}:${conversation.id}`,
382
388
  error: err instanceof Error ? err.message : String(err),
383
389
  }));
384
390
  });
@@ -521,11 +527,3 @@ export class TelegramEndpoint implements EndpointInstance {
521
527
  });
522
528
  }
523
529
  }
524
-
525
- function resolveTelegramChannelType(
526
- chatType: TelegramMessage['chat']['type'],
527
- ): 'private' | 'group' | 'channel' {
528
- if (chatType === 'private') return 'private';
529
- if (chatType === 'channel') return 'channel';
530
- return 'group';
531
- }
package/src/index.ts CHANGED
@@ -11,7 +11,6 @@ export {
11
11
  formatOutboundActions,
12
12
  formatOutboundPlan,
13
13
  normalizeWebhookPath,
14
- resolveChannel,
15
14
  resolveTelegramConfig,
16
15
  senderDisplayName,
17
16
  type ResolvedTelegramConfig,
@@ -40,6 +39,5 @@ export {
40
39
  checkTelegramPlatformPermit,
41
40
  normalizeTelegramChatMember,
42
41
  platformPermit,
43
- registerTelegramPlatformPermitChecker,
44
42
  telegramGroupPermitResolver,
45
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
@@ -6,13 +6,14 @@
6
6
  import type { IncomingMessage } from 'node:http';
7
7
  import { isMediaRef } from '@zhin.js/core';
8
8
  import type { Segment } from '@zhin.js/core/runtime';
9
+ import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
9
10
  import { formatCompact, getLogger } from '@zhin.js/logger';
10
11
 
11
12
  const logger = getLogger('telegram');
12
13
 
13
14
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
14
15
  export interface TelegramAdapterConfig {
15
- readonly name?: string;
16
+ readonly id?: string;
16
17
  readonly token?: string;
17
18
  /** Default true. `false` selects webhook mode (requires httpHostToken). */
18
19
  readonly polling?: boolean;
@@ -35,7 +36,7 @@ export interface TelegramAdapterConfig {
35
36
 
36
37
  export interface ResolvedTelegramConfig {
37
38
  readonly context: 'telegram';
38
- readonly name: string;
39
+ readonly id: string;
39
40
  readonly token: string;
40
41
  readonly mode: 'polling' | 'webhook';
41
42
  readonly allowedUpdates: readonly string[];
@@ -249,8 +250,8 @@ export function resolveTelegramConfig(config: TelegramAdapterConfig = {}): Resol
249
250
  'Telegram adapter requires token (plugins.<key>.token or endpoints with context: telegram)',
250
251
  );
251
252
  }
252
- const name = (typeof config.name === 'string' && config.name)
253
- || (typeof entry?.name === 'string' && entry.name)
253
+ const id = (typeof config.id === 'string' && config.id)
254
+ || (typeof entry?.id === 'string' && entry.id)
254
255
  || process.env.TELEGRAM_BOT_NAME
255
256
  || 'telegram-bot';
256
257
  const polling = config.polling ?? entry?.polling;
@@ -276,7 +277,7 @@ export function resolveTelegramConfig(config: TelegramAdapterConfig = {}): Resol
276
277
  : undefined;
277
278
  return {
278
279
  context: 'telegram',
279
- name,
280
+ id,
280
281
  token,
281
282
  mode,
282
283
  allowedUpdates: [...allowedUpdates],
@@ -321,13 +322,27 @@ export function botApiUrl(config: Pick<ResolvedTelegramConfig, 'apiBaseUrl' | 't
321
322
  return `${config.apiBaseUrl}/bot${config.token}/${method}`;
322
323
  }
323
324
 
324
- export function resolveChannel(msg: Pick<TelegramMessage, 'chat'>): {
325
- readonly channelType: 'private' | 'group';
326
- readonly channelId: string;
327
- } {
325
+ /** Telegram chat.type canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
326
+ export function resolveTelegramChannelType(
327
+ chatType: TelegramChat['type'],
328
+ ): ConversationKind {
329
+ if (chatType === 'private') return 'private';
330
+ if (chatType === 'channel') return 'channel';
331
+ return 'group';
332
+ }
333
+
334
+ /**
335
+ * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
336
+ * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
337
+ */
338
+ export function telegramInboundConversation(
339
+ endpointKey: string,
340
+ chat: Pick<TelegramChat, 'id' | 'type'>,
341
+ ): ConversationRef {
328
342
  return {
329
- channelType: msg.chat.type === 'private' ? 'private' : 'group',
330
- channelId: String(msg.chat.id),
343
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
344
+ kind: resolveTelegramChannelType(chat.type),
345
+ id: String(chat.id),
331
346
  };
332
347
  }
333
348
 
@@ -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
  };