opencode-tbot 0.1.33 → 0.1.34

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/README.zh-CN.md CHANGED
@@ -4,27 +4,28 @@
4
4
 
5
5
  [English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md)
6
6
 
7
- > 本项目并非由 OpenCode 官方团队开发。
7
+ > 本项目并非由 OpenCode 官方团队开发,也不隶属于 OpenCode 官方。
8
8
 
9
9
  ## 概览
10
10
 
11
- `opencode-tbot` 允许你直接在 Telegram 中操作 OpenCode
11
+ `opencode-tbot` 让你可以在 Telegram 中直接操作 OpenCode,并为每个聊天维护一份绑定状态。
12
12
 
13
- - 文本消息会转发到当前 OpenCode 会话。
14
- - Telegram 图片会作为 OpenCode 文件片段上传。
15
- - 图片轮次会在临时 fork 会话中执行,避免后续纯文本上下文被污染。
16
- - Telegram 语音消息会被明确拒绝。
17
- - OpenCode 的权限请求可以直接在 Telegram 中审批。
13
+ - 纯文本消息会转发到当前 OpenCode 会话。
14
+ - Telegram 照片和图片文档会作为 OpenCode 文件片段上传。
15
+ - 图片轮次会在临时 fork 会话中执行,避免后续纯文本对话继承图片上下文。
16
+ - OpenCode 发起的权限请求可以直接在 Telegram 内联按钮里审批或拒绝。
18
17
  - 会话错误事件可以回推到已绑定的 Telegram chat。
19
- - 聊天绑定状态会保存在 JSON 状态文件中。
18
+ - 语音消息会被明确拒绝,并返回本地化提示。
19
+ - 聊天绑定和会话选择状态会保存在 JSON 状态文件中。
20
20
 
21
21
  ## 环境要求
22
22
 
23
23
  - 一个正在运行、并会加载该插件的 OpenCode Host 进程。
24
24
  - 一个 Telegram bot token。
25
- - Node.js `>=22.12.0`。
25
+ - Node.js `>=22.12.0`,用于 CLI 和本地开发。
26
+ - `pnpm`,用于仓库开发。
26
27
 
27
- ## 安装
28
+ ## 安装与更新
28
29
 
29
30
  推荐安装方式:
30
31
 
@@ -32,22 +33,33 @@
32
33
  npm exec --package opencode-tbot@latest opencode-tbot -- install
33
34
  ```
34
35
 
35
- 安装器会全局注册插件、更新实际生效的 OpenCode 全局配置文件、写入默认运行时配置,并打印 OpenCode 配置路径、插件配置路径与默认日志目录。
36
+ 安装器会:
37
+
38
+ - 在全局 OpenCode 插件列表中注册 `opencode-tbot@latest`
39
+ - 更新实际生效的 OpenCode 全局配置文件
40
+ - 写入或合并全局插件运行时配置
41
+ - 打印 OpenCode 配置路径、插件配置路径和默认插件日志目录
36
42
 
37
43
  如果 `~/.config/opencode/opencode.jsonc` 已存在,安装器会优先更新它;否则会使用 `~/.config/opencode/opencode.json`。
38
44
 
39
- 查看 CLI 版本:
45
+ 查看已安装 CLI 版本:
40
46
 
41
47
  ```bash
42
48
  npm exec --package opencode-tbot@latest opencode-tbot -- --version
43
49
  ```
44
50
 
45
- 更新 OpenCode 中注册的 npm 插件 spec:
51
+ 只更新 OpenCode 中注册的 npm 插件 spec,而不改 bot token
46
52
 
47
53
  ```bash
48
54
  npm exec --package opencode-tbot@latest opencode-tbot -- update
49
55
  ```
50
56
 
57
+ `install` 是默认命令。例如,下面这种写法也适合非交互安装:
58
+
59
+ ```bash
60
+ npm exec --package opencode-tbot@latest opencode-tbot -- --bot-token <token>
61
+ ```
62
+
51
63
  ### CLI 参数
52
64
 
53
65
  `install` 支持:
@@ -55,7 +67,7 @@ npm exec --package opencode-tbot@latest opencode-tbot -- update
55
67
  - `--bot-token <token>` 非交互写入 Telegram bot token
56
68
  - `--telegram-api-root <url>` 覆盖 Telegram Bot API 根地址
57
69
  - `--plugin-spec <spec>` 注册自定义 npm 插件 spec
58
- - `--skip-register` 只重写插件配置,不改 OpenCode 插件注册
70
+ - `--skip-register` 只重写插件配置,不修改 OpenCode 插件注册
59
71
  - `--home-dir <path>` 使用自定义 home 目录
60
72
 
61
73
  `update` 支持:
@@ -65,15 +77,23 @@ npm exec --package opencode-tbot@latest opencode-tbot -- update
65
77
 
66
78
  ## 配置
67
79
 
68
- 运行时配置只从 `~/.config/opencode/opencode-tbot/config.json` 加载。
80
+ 运行时配置只会从以下路径加载:
69
81
 
70
- OpenCode 的插件注册信息保存在全局 OpenCode 配置中:优先使用 `~/.config/opencode/opencode.jsonc`,否则使用 `~/.config/opencode/opencode.json`。
82
+ - `~/.config/opencode/opencode-tbot/config.json`
71
83
 
72
- 遗留的 `<worktree>/tbot.config.json` 会在运行时被忽略;如果检测到,插件会记录一条警告,提示你把配置迁移到全局文件。
84
+ OpenCode 的插件注册信息保存在全局 OpenCode 配置中:
73
85
 
74
- 遗留的 `openrouter` 语音转写配置会在运行时被忽略;安装器重写配置时也会自动移除它们。
86
+ - 如果存在,使用 `~/.config/opencode/opencode.jsonc`
87
+ - 否则使用 `~/.config/opencode/opencode.json`
75
88
 
76
- ### 全局 `config.json` 示例
89
+ 本插件不会从 `.env` 读取运行时配置,请使用全局 JSON 配置文件。
90
+
91
+ 遗留行为说明:
92
+
93
+ - `<worktree>/tbot.config.json` 会在运行时被忽略;如果检测到,插件会记录警告,提示你迁移配置
94
+ - 遗留的 `openrouter` 语音转写配置会在运行时被忽略;安装器重写配置时也会自动移除
95
+
96
+ ### `config.json` 示例
77
97
 
78
98
  ```json
79
99
  {
@@ -112,69 +132,77 @@ OpenCode 的插件注册信息保存在全局 OpenCode 配置中:优先使用
112
132
 
113
133
  | 字段 | 必填 | 默认值 | 说明 |
114
134
  | --- | --- | --- | --- |
115
- | `telegram.botToken` | 是 | - | Telegram bot token。通常由安装器写入。 |
135
+ | `telegram.botToken` | 是 | - | Telegram bot token。通常由安装器写入全局插件配置。 |
116
136
  | `telegram.allowedChatIds` | 否 | `[]` | 允许访问的 Telegram chat ID。为空时接受任意 chat。 |
117
- | `telegram.apiRoot` | 否 | `https://api.telegram.org` | Telegram Bot API 根地址。 |
137
+ | `telegram.apiRoot` | 否 | `https://api.telegram.org` | Telegram Bot API 根地址,适合测试或自建网关。 |
118
138
  | `state.path` | 否 | `./data/opencode-tbot.state.json` | JSON 状态文件路径,相对当前 OpenCode worktree 解析。 |
119
- | `prompt.waitTimeoutMs` | 否 | `1800000` | 单次异步 prompt 生命周期的最大等待时间。 |
120
- | `prompt.pollRequestTimeoutMs` | 否 | `15000` | 每次恢复轮询请求的超时时间。 |
121
- | `prompt.recoveryInactivityTimeoutMs` | 否 | `120000` | 仅在 OpenCode 长时间没有新进展时生效的恢复超时。 |
122
- | `logging.level` | 否 | `info` | 结构化日志级别。 |
139
+ | `prompt.waitTimeoutMs` | 否 | `1800000` | 单次异步 prompt 生命周期的总等待上限。 |
140
+ | `prompt.pollRequestTimeoutMs` | 否 | `15000` | 每次向 OpenCode 发起恢复轮询请求时的超时时间。 |
141
+ | `prompt.recoveryInactivityTimeoutMs` | 否 | `120000` | 仅在长时间没有新进展时生效的恢复超时。 |
142
+ | `logging.level` | 否 | `info` | Host 和文件日志共用的结构化日志级别,会规范化为 `debug`、`info`、`warn` 或 `error`。 |
123
143
  | `logging.sinks.host` | 否 | `true` | 是否通过 `client.app.log({ body: ... })` 写入 OpenCode Host 日志。 |
124
144
  | `logging.sinks.file` | 否 | `true` | 是否写入插件自己的 JSONL 文件日志。 |
125
- | `logging.file.dir` | 否 | `%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot` | 插件日志目录。 |
145
+ | `logging.file.dir` | 否 | `%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot` | 插件 JSONL 日志目录。相对路径会基于当前 OpenCode worktree 解析。 |
126
146
  | `logging.file.retention.maxFiles` | 否 | `30` | 最多保留多少个插件日志文件。 |
127
147
  | `logging.file.retention.maxTotalBytes` | 否 | `314572800` | 插件日志文件总大小上限。 |
128
- | `logLevel` | 否 | `info` | `logging.level` 的兼容别名。 |
148
+ | `logLevel` | 否 | `info` | `logging.level` 的兼容别名。仍然可用。 |
129
149
 
130
150
  ### 说明
131
151
 
132
152
  - `state.path` 会相对当前 OpenCode worktree 解析。
133
- - Telegram prompt 采用 async-first:插件先提交异步 prompt,再通过消息与状态恢复最终回复。
153
+ - `logging.file.dir` 如果是相对路径,也会相对当前 worktree 解析。
154
+ - Telegram prompt 采用 async-first:插件先提交 `session.promptAsync()`,再通过消息与状态恢复最终回复。
134
155
  - 如果 `telegram.allowedChatIds` 为空,bot 会接受任意 chat 的消息;生产环境建议显式限制。
135
156
  - 权限审批与会话错误通知通过插件 hooks 处理。
136
- - `/language` 当前支持 English、简体中文、日语。
137
- - 默认会双写日志:OpenCode Host 日志 + 插件 JSONL 文件日志。
157
+ - `/language` 当前支持 English、简体中文、日语,并会同步当前 chat 的 Telegram 命令描述。
138
158
  - 文件日志默认只记录元数据,不记录 prompt 正文、附件内容、原始 URL 或 secrets。
139
159
 
140
- ## 日志
141
-
142
- - OpenCode Host 日志目录:`%USERPROFILE%/.local/share/opencode/log`
143
- - 插件日志目录:`%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot`
144
- - 文件格式:JSONL,每行一条结构化事件
145
- - 关键关联字段:`runtimeId`、`operationId`、`correlationId`、`updateId`、`chatId`、`sessionId`、`projectId`
146
- - 常见组件:`runtime`、`telegram`、`opencode`、`prompt`、`plugin-event`、`storage`
147
- - 默认保留策略:最多 30 个文件,总大小不超过 300 MB
148
-
149
160
  ## 快速开始
150
161
 
151
162
  1. 运行 `npm exec --package opencode-tbot@latest opencode-tbot -- install`。
152
- 2. 如需限制可访问 chat,在 `~/.config/opencode/opencode-tbot/config.json` 中设置 `telegram.allowedChatIds`。
153
- 3. 在目标 worktree 中启动 OpenCode
163
+ 2. 如果需要限制可访问 chat,在 `~/.config/opencode/opencode-tbot/config.json` 中设置 `telegram.allowedChatIds`。
164
+ 3. 在目标 worktree 中启动 OpenCode,让插件运行时被加载。
154
165
  4. 在 Telegram 中执行 `/status` 验证连通性。
155
- 5. 执行 `/new [title]`,或者直接发送文本/图片开始使用。
166
+ 5. 运行 `/new [title]`,或直接发送文本消息开始使用。
156
167
 
157
168
  ## 命令
158
169
 
159
- - `/start` 显示欢迎信息和快速开始说明
160
- - `/status` 显示 OpenCode 健康状态、路径、LSP、MCP 等信息
161
- - `/new [title]` 创建新会话
162
- - `/agents` `/agent` 列出并切换 agent
163
- - `/sessions` 列出并切换会话
164
- - `/cancel` 取消重命名,或中止当前正在运行的请求
165
- - `/model` `/models` 列出并切换模型
166
- - `/language` 切换显示语言
170
+ | 命令 | 作用 |
171
+ | --- | --- |
172
+ | `/start` | 显示欢迎消息和快速开始说明。 |
173
+ | `/status` | 汇总展示 OpenCode 健康状态、版本、工作区路径、插件列表、LSP 状态和 MCP 状态。 |
174
+ | `/new [title]` | 在当前项目中创建一个新的 OpenCode 会话。 |
175
+ | `/agents` 或 `/agent` | 列出可用 Agent 并切换当前 Agent。 |
176
+ | `/sessions` | 列出可用会话、切换当前会话,并提供重命名操作。 |
177
+ | `/cancel` | 取消待输入的会话重命名,或中止当前会话正在运行的请求。 |
178
+ | `/model` 或 `/models` | 列出可用模型、切换当前模型,并在可用时选择推理级别。 |
179
+ | `/language` | 切换当前 chat 的 bot 显示语言。 |
167
180
 
168
- 消息处理规则:
181
+ ### 消息行为
169
182
 
170
183
  - 非命令文本会作为 prompt 发送给 OpenCode。
171
- - Telegram 图片会作为 OpenCode 文件片段上传。
172
- - 图片消息会在临时 fork 会话中处理。
173
- - `/cancel` 会同时中止 OpenCode 会话和本地 Telegram 等待状态。
174
- - Telegram 语音消息不受支持,会收到本地化拒绝提示。
184
+ - Telegram 照片和图片文档会作为 OpenCode 文件片段转发。
185
+ - 图片附件会在当前会话的临时 fork 中处理,避免污染后续纯文本上下文。
186
+ - `/cancel` 会同时中止 OpenCode 会话请求和本地 Telegram 等待状态,让下一次 prompt 能立即开始。
187
+ - Telegram 语音消息暂不支持,会收到本地化拒绝提示。
188
+
189
+ ## 日志
190
+
191
+ - OpenCode Host 日志目录:`%USERPROFILE%/.local/share/opencode/log`
192
+ - 插件日志目录:`%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot`
193
+ - 文件格式:追加写入的 JSONL,每行一个结构化事件
194
+ - 关联字段:`runtimeId`、`operationId`、`correlationId`、`updateId`、`chatId`、`sessionId`、`projectId`
195
+ - 常见组件:`runtime`、`telegram`、`opencode`、`prompt`、`plugin-event`、`storage`
196
+ - 默认保留策略:最多保留最新 30 个文件,且总大小不超过 300 MB
175
197
 
176
198
  ## 开发
177
199
 
200
+ 安装依赖:
201
+
202
+ ```bash
203
+ pnpm install
204
+ ```
205
+
178
206
  构建:
179
207
 
180
208
  ```bash
@@ -187,12 +215,26 @@ pnpm build
187
215
  pnpm typecheck
188
216
  ```
189
217
 
190
- 运行测试:
218
+ 运行默认测试集:
191
219
 
192
220
  ```bash
193
221
  pnpm test
194
222
  ```
195
223
 
224
+ 运行最接近 CI 的集成路径:
225
+
226
+ ```bash
227
+ npm install -g opencode-ai
228
+ OPENCODE_HOST_E2E=1 pnpm test
229
+ ```
230
+
231
+ PowerShell 写法:
232
+
233
+ ```powershell
234
+ npm install -g opencode-ai
235
+ $env:OPENCODE_HOST_E2E="1"; pnpm test
236
+ ```
237
+
196
238
  正常使用应依赖“全局安装的 npm 插件 + 全局 OpenCode 配置”。本仓库不再默认提供 `.opencode/plugins` bridge。
197
239
 
198
240
  如果你在本仓库里做源码调试,需要手动创建 `.opencode/plugins/opencode-tbot.ts`:
@@ -207,8 +249,12 @@ export { default } from "../../src/plugin.ts";
207
249
 
208
250
  ### 需要一个正在运行的 OpenCode 实例吗?
209
251
 
210
- 需要。这个仓库只提供 Telegram 集成层,依赖加载它的 OpenCode Host 进程。
252
+ 需要。本仓库只提供 Telegram 集成层,依赖加载该插件的 OpenCode Host 进程。
253
+
254
+ ### 为什么 OpenCode 里会显示 `file:///.../node_modules/...` 形式的插件路径?
255
+
256
+ 这通常表示某个本地项目在自己的 `node_modules` 中安装了 `opencode-tbot`。CLI 检测到这种布局时会给出警告。请在那个项目里执行 `npm uninstall opencode-tbot` 移除本地包,然后改用推荐的 `npm exec --package ...` 安装流程。
211
257
 
212
258
  ### 这是 OpenCode 官方项目吗?
213
259
 
214
- 不是。它集成 OpenCode,但不是 OpenCode 官方项目。
260
+ 不是。它集成 OpenCode,但并非 OpenCode 官方团队开发。
package/dist/plugin.js CHANGED
@@ -3120,14 +3120,16 @@ function normalizePermissionReply(value) {
3120
3120
  function extractSessionErrorMessage(error) {
3121
3121
  if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
3122
3122
  if (!isPlainRecord(error)) return null;
3123
+ if (typeof error.message === "string" && error.message.trim().length > 0) return error.message.trim();
3123
3124
  if (isPlainRecord(error.data) && typeof error.data.message === "string" && error.data.message.trim().length > 0) return error.data.message.trim();
3124
3125
  return asNonEmptyString(error.name);
3125
3126
  }
3126
3127
  function normalizeForegroundSessionError(error) {
3127
3128
  if (error instanceof Error) return error;
3128
- if (isPlainRecord(error) && typeof error.name === "string" && error.name.trim().length > 0) {
3129
+ if (isPlainRecord(error)) {
3129
3130
  const normalized = new Error(extractSessionErrorMessage(error) ?? "Unknown session error.");
3130
- normalized.name = error.name.trim();
3131
+ const normalizedName = asNonEmptyString(error.name);
3132
+ if (normalizedName) normalized.name = normalizedName;
3131
3133
  if (isPlainRecord(error.data)) normalized.data = error.data;
3132
3134
  return normalized;
3133
3135
  }
@@ -3920,14 +3922,18 @@ function normalizeError(error, copy) {
3920
3922
  message: copy.errors.contextOverflow,
3921
3923
  cause: extractMessage(error.data) ?? null
3922
3924
  };
3923
- if (isNamedError(error, "APIError")) return {
3924
- message: copy.errors.providerRequest,
3925
- cause: joinNonEmptyParts([
3926
- extractMessage(error.data),
3927
- extractStatusCode(error.data, copy),
3928
- extractRetryable(error.data, copy)
3929
- ])
3930
- };
3925
+ if (isNamedError(error, "APIError")) {
3926
+ const providerMessage = extractMessage(error.data);
3927
+ return {
3928
+ message: copy.errors.providerRequest,
3929
+ cause: joinNonEmptyParts([
3930
+ getProviderCompatibilityHint(providerMessage),
3931
+ providerMessage,
3932
+ extractStatusCode(error.data, copy),
3933
+ extractRetryable(error.data, copy)
3934
+ ])
3935
+ };
3936
+ }
3931
3937
  if (isNamedError(error, "NotFoundError")) return {
3932
3938
  message: copy.errors.notFound,
3933
3939
  cause: extractMessage(error.data) ?? null
@@ -3945,6 +3951,10 @@ function normalizeError(error, copy) {
3945
3951
  cause: extractMessage(error) ?? stringifyUnknown(error)
3946
3952
  };
3947
3953
  }
3954
+ function getProviderCompatibilityHint(message) {
3955
+ if (!message) return null;
3956
+ return /tool_choice parameter does not support being set to required or object in thinking mode/iu.test(message) ? "Current model/reasoning mode is incompatible with tool calling. Switch to a compatible model or disable thinking mode." : null;
3957
+ }
3948
3958
  function isBadRequestError(error) {
3949
3959
  return !!error && typeof error === "object" && "success" in error && error.success === false;
3950
3960
  }
@@ -5837,14 +5847,15 @@ function getTelegramBotRuntimeRegistry() {
5837
5847
  //#region src/plugin.ts
5838
5848
  async function ensureTelegramBotPluginRuntime(options) {
5839
5849
  const runtimeStateHolder = getTelegramBotPluginRuntimeStateHolder();
5840
- const cwd = resolvePluginRuntimeCwd(options.context);
5841
- if (runtimeStateHolder.state && runtimeStateHolder.state.cwd !== cwd) {
5850
+ const explicitCwd = resolveExplicitPluginRuntimeCwd(options.context);
5851
+ if (runtimeStateHolder.state && explicitCwd === null) return runtimeStateHolder.state.runtimePromise;
5852
+ if (runtimeStateHolder.state && explicitCwd !== null && runtimeStateHolder.state.cwd !== explicitCwd) {
5842
5853
  const activeState = runtimeStateHolder.state;
5843
5854
  runtimeStateHolder.state = null;
5844
5855
  await disposeTelegramBotPluginRuntimeState(activeState);
5845
5856
  }
5846
5857
  if (!runtimeStateHolder.state) {
5847
- const runtimePromise = startPluginRuntime(options, cwd).then((runtime) => {
5858
+ const runtimePromise = startPluginRuntime(options, resolvePluginRuntimeCwd(options.context), requirePluginClient(options.context)).then((runtime) => {
5848
5859
  if (runtimeStateHolder.state?.runtimePromise === runtimePromise) runtimeStateHolder.state.runtime = runtime;
5849
5860
  return runtime;
5850
5861
  }).catch((error) => {
@@ -5852,7 +5863,7 @@ async function ensureTelegramBotPluginRuntime(options) {
5852
5863
  throw error;
5853
5864
  });
5854
5865
  runtimeStateHolder.state = {
5855
- cwd,
5866
+ cwd: explicitCwd ?? resolvePluginRuntimeCwd(options.context),
5856
5867
  runtime: null,
5857
5868
  runtimePromise
5858
5869
  };
@@ -5869,7 +5880,7 @@ async function resetTelegramBotPluginRuntimeForTests() {
5869
5880
  runtimeStateHolder.state = null;
5870
5881
  await disposeTelegramBotPluginRuntimeState(activeState);
5871
5882
  }
5872
- async function startPluginRuntime(options, cwd) {
5883
+ async function startPluginRuntime(options, cwd, client) {
5873
5884
  const bootstrapApp = options.bootstrapApp ?? bootstrapPluginApp;
5874
5885
  const prepareConfiguration = options.prepareConfiguration ?? preparePluginConfiguration;
5875
5886
  const startRuntime = options.startRuntime ?? startTelegramBotRuntime;
@@ -5877,7 +5888,7 @@ async function startPluginRuntime(options, cwd) {
5877
5888
  cwd,
5878
5889
  config: options.config
5879
5890
  });
5880
- const { config, container } = bootstrapApp(options.context.client, preparedConfiguration.config, { cwd: preparedConfiguration.cwd });
5891
+ const { config, container } = bootstrapApp(client, preparedConfiguration.config, { cwd: preparedConfiguration.cwd });
5881
5892
  try {
5882
5893
  if (preparedConfiguration.ignoredProjectConfigFilePath) container.logger.child({ component: "runtime" }).warn({
5883
5894
  cwd: preparedConfiguration.cwd,
@@ -5904,7 +5915,19 @@ async function startPluginRuntime(options, cwd) {
5904
5915
  }
5905
5916
  }
5906
5917
  function resolvePluginRuntimeCwd(context) {
5907
- return context.worktree ?? context.directory ?? process.cwd();
5918
+ return resolveExplicitPluginRuntimeCwd(context) ?? process.cwd();
5919
+ }
5920
+ function resolveExplicitPluginRuntimeCwd(context) {
5921
+ return normalizePluginRuntimePath(context?.worktree) ?? normalizePluginRuntimePath(context?.directory);
5922
+ }
5923
+ function requirePluginClient(context) {
5924
+ if (context?.client) return context.client;
5925
+ throw new Error("OpenCode plugin context.client is required.");
5926
+ }
5927
+ function normalizePluginRuntimePath(value) {
5928
+ if (typeof value !== "string") return null;
5929
+ const normalized = value.trim();
5930
+ return normalized.length > 0 ? normalized : null;
5908
5931
  }
5909
5932
  async function disposeTelegramBotPluginRuntimeState(state) {
5910
5933
  await (state.runtime ?? await state.runtimePromise.catch(() => null))?.dispose();