opencode-tbot 0.1.32 → 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/dist/plugin.js CHANGED
@@ -1,7 +1,6 @@
1
- import { i as OPENCODE_TBOT_VERSION, n as preparePluginConfiguration, o as loadAppConfig } from "./assets/plugin-config-jkAZYbFW.js";
1
+ import { i as OPENCODE_TBOT_VERSION, n as preparePluginConfiguration, o as loadAppConfig } from "./assets/plugin-config-LIr8LS0-.js";
2
2
  import { appendFile, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join } from "node:path";
4
- import { parse, printParseErrorCode } from "jsonc-parser";
5
4
  import { randomUUID } from "node:crypto";
6
5
  import { createOpencodeClient } from "@opencode-ai/sdk";
7
6
  import { run } from "@grammyjs/runner";
@@ -919,6 +918,9 @@ var OpenCodeClient = class {
919
918
  this.modelCache.promise = refreshPromise;
920
919
  return refreshPromise;
921
920
  }
921
+ async listConfiguredPlugins() {
922
+ return normalizeConfiguredPluginSpecs((await this.loadConfig()).plugin);
923
+ }
922
924
  async promptSession(input) {
923
925
  const startedAt = Date.now();
924
926
  const promptText = input.prompt?.trim() ?? "";
@@ -1539,6 +1541,19 @@ function normalizePermissionRequest(permission) {
1539
1541
  sessionID
1540
1542
  };
1541
1543
  }
1544
+ function normalizeConfiguredPluginSpecs(value) {
1545
+ if (!Array.isArray(value)) return [];
1546
+ const normalizedPlugins = [];
1547
+ const seenPlugins = /* @__PURE__ */ new Set();
1548
+ for (const item of value) {
1549
+ if (typeof item !== "string") continue;
1550
+ const normalizedItem = item.trim();
1551
+ if (normalizedItem.length === 0 || seenPlugins.has(normalizedItem)) continue;
1552
+ seenPlugins.add(normalizedItem);
1553
+ normalizedPlugins.push(normalizedItem);
1554
+ }
1555
+ return normalizedPlugins;
1556
+ }
1542
1557
  function normalizePermissionPatterns$1(permission) {
1543
1558
  if (Array.isArray(permission.patterns)) return permission.patterns.filter((value) => typeof value === "string");
1544
1559
  if (typeof permission.pattern === "string" && permission.pattern.trim().length > 0) return [permission.pattern];
@@ -1739,7 +1754,7 @@ async function readStateFile(filePath, createDefaultState) {
1739
1754
  const content = await readFile(filePath, "utf8");
1740
1755
  return JSON.parse(content);
1741
1756
  } catch (error) {
1742
- if (isMissingFileError$1(error)) return createDefaultState();
1757
+ if (isMissingFileError(error)) return createDefaultState();
1743
1758
  throw error;
1744
1759
  }
1745
1760
  }
@@ -1752,7 +1767,7 @@ async function writeStateFile(filePath, state) {
1752
1767
  function cloneState(state) {
1753
1768
  return JSON.parse(JSON.stringify(state));
1754
1769
  }
1755
- function isMissingFileError$1(error) {
1770
+ function isMissingFileError(error) {
1756
1771
  return error instanceof Error && "code" in error && error.code === "ENOENT";
1757
1772
  }
1758
1773
  //#endregion
@@ -2056,27 +2071,30 @@ var GetPathUseCase = class {
2056
2071
  //#endregion
2057
2072
  //#region src/use-cases/get-status.usecase.ts
2058
2073
  var GetStatusUseCase = class {
2059
- constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo) {
2074
+ constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, configuredPluginReader) {
2060
2075
  this.getHealthUseCase = getHealthUseCase;
2061
2076
  this.getPathUseCase = getPathUseCase;
2062
2077
  this.listLspUseCase = listLspUseCase;
2063
2078
  this.listMcpUseCase = listMcpUseCase;
2064
2079
  this.listSessionsUseCase = listSessionsUseCase;
2065
2080
  this.sessionRepo = sessionRepo;
2081
+ this.configuredPluginReader = configuredPluginReader;
2066
2082
  }
2067
2083
  async execute(input) {
2068
- const [health, path, lsp, mcp] = await Promise.allSettled([
2084
+ const [health, path, lsp, mcp, plugins] = await Promise.allSettled([
2069
2085
  this.getHealthUseCase.execute(),
2070
2086
  this.getPathUseCase.execute(),
2071
2087
  this.listLspUseCase.execute({ chatId: input.chatId }),
2072
- this.listMcpUseCase.execute({ chatId: input.chatId })
2088
+ this.listMcpUseCase.execute({ chatId: input.chatId }),
2089
+ this.configuredPluginReader.listConfiguredPlugins()
2073
2090
  ]);
2074
2091
  const pathResult = mapSettledResult(path);
2075
- const [plugins, workspace] = await Promise.all([loadConfiguredPluginsResult(pathResult), loadWorkspaceStatusResult(input.chatId, pathResult, this.listSessionsUseCase, this.sessionRepo)]);
2092
+ const pluginResult = loadConfiguredPluginsResult(plugins);
2093
+ const workspace = await loadWorkspaceStatusResult(input.chatId, pathResult, this.listSessionsUseCase, this.sessionRepo);
2076
2094
  return {
2077
2095
  health: mapSettledResult(health),
2078
2096
  path: pathResult,
2079
- plugins,
2097
+ plugins: pluginResult,
2080
2098
  workspace,
2081
2099
  lsp: mapSettledResult(lsp),
2082
2100
  mcp: mapSettledResult(mcp)
@@ -2093,49 +2111,14 @@ function mapSettledResult(result) {
2093
2111
  status: "error"
2094
2112
  };
2095
2113
  }
2096
- async function loadConfiguredPluginsResult(path) {
2097
- if (path.status === "error") return {
2098
- error: path.error,
2114
+ function loadConfiguredPluginsResult(result) {
2115
+ if (result.status === "rejected") return {
2116
+ error: result.reason,
2099
2117
  status: "error"
2100
2118
  };
2101
- try {
2102
- return {
2103
- data: await loadConfiguredPlugins(path.data.config),
2104
- status: "ok"
2105
- };
2106
- } catch (error) {
2107
- return {
2108
- error,
2109
- status: "error"
2110
- };
2111
- }
2112
- }
2113
- async function loadConfiguredPlugins(configFilePath) {
2114
- const resolvedConfigFilePath = await resolveOpenCodeConfigFilePath(configFilePath);
2115
- let content;
2116
- try {
2117
- content = await readFile(resolvedConfigFilePath, "utf8");
2118
- } catch (error) {
2119
- if (isMissingFileError(error)) return {
2120
- configFilePath: resolvedConfigFilePath,
2121
- plugins: []
2122
- };
2123
- throw error;
2124
- }
2125
- const parseErrors = [];
2126
- const parsed = parse(content, parseErrors, { allowTrailingComma: true });
2127
- if (parseErrors.length > 0) {
2128
- const errorSummary = parseErrors.map((error) => printParseErrorCode(error.error)).join(", ");
2129
- throw new Error(`Failed to parse ${resolvedConfigFilePath}: ${errorSummary}`);
2130
- }
2131
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
2132
- configFilePath: resolvedConfigFilePath,
2133
- plugins: []
2134
- };
2135
- const pluginSpecs = Array.isArray(parsed.plugin) ? parsed.plugin : [];
2136
2119
  return {
2137
- configFilePath: resolvedConfigFilePath,
2138
- plugins: [...new Set(pluginSpecs.filter((value) => typeof value === "string").map((value) => value.trim()).filter((value) => value.length > 0))]
2120
+ data: { plugins: normalizeConfiguredPluginLabels(result.value) },
2121
+ status: "ok"
2139
2122
  };
2140
2123
  }
2141
2124
  async function loadWorkspaceStatusResult(chatId, path, listSessionsUseCase, sessionRepo) {
@@ -2178,17 +2161,43 @@ function isUsableWorkspacePath(value) {
2178
2161
  const normalized = value.trim();
2179
2162
  return normalized.length > 0 && normalized !== "/" && normalized !== "\\" && isAbsolute(normalized);
2180
2163
  }
2181
- async function resolveOpenCodeConfigFilePath(configPath) {
2164
+ function normalizeConfiguredPluginLabels(plugins) {
2165
+ const normalizedPlugins = [];
2166
+ const seenPlugins = /* @__PURE__ */ new Set();
2167
+ for (const plugin of plugins) {
2168
+ const normalizedPlugin = normalizeConfiguredPluginLabel(plugin);
2169
+ if (normalizedPlugin.length === 0 || seenPlugins.has(normalizedPlugin)) continue;
2170
+ seenPlugins.add(normalizedPlugin);
2171
+ normalizedPlugins.push(normalizedPlugin);
2172
+ }
2173
+ return normalizedPlugins;
2174
+ }
2175
+ function normalizeConfiguredPluginLabel(plugin) {
2176
+ const normalizedPlugin = plugin.trim();
2177
+ const localPluginName = extractLocalPluginName(normalizedPlugin);
2178
+ return localPluginName ? `${localPluginName} (local plugin)` : normalizedPlugin;
2179
+ }
2180
+ function extractLocalPluginName(plugin) {
2181
+ const filePath = parseFilePluginPath(plugin);
2182
+ if (!filePath) return null;
2183
+ const pathSegments = filePath.split("/").filter((segment) => segment.length > 0);
2184
+ const fileName = pathSegments.at(-1);
2185
+ const parentDirectoryName = pathSegments.at(-2);
2186
+ if (!fileName || parentDirectoryName !== "plugins") return null;
2187
+ const pluginName = fileName.replace(/\.(?:[cm]?js|[cm]?ts)$/iu, "");
2188
+ return pluginName.trim().length > 0 ? pluginName.trim() : null;
2189
+ }
2190
+ function parseFilePluginPath(plugin) {
2191
+ if (!plugin.startsWith("file://")) return null;
2182
2192
  try {
2183
- return (await stat(configPath)).isDirectory() ? join(configPath, "opencode.json") : configPath;
2184
- } catch (error) {
2185
- if (isMissingFileError(error)) return configPath;
2186
- throw error;
2193
+ const fileUrl = new URL(plugin);
2194
+ if (fileUrl.protocol !== "file:") return null;
2195
+ const normalizedPath = decodeURIComponent(fileUrl.pathname).replace(/^\/([A-Za-z]:)/u, "$1").replace(/\\/gu, "/");
2196
+ return normalizedPath.length > 0 ? normalizedPath : null;
2197
+ } catch {
2198
+ return null;
2187
2199
  }
2188
2200
  }
2189
- function isMissingFileError(error) {
2190
- return error instanceof Error && "code" in error && error.code === "ENOENT";
2191
- }
2192
2201
  function isSelectableAgent(agent) {
2193
2202
  return !agent.hidden && agent.mode !== "subagent";
2194
2203
  }
@@ -2798,7 +2807,7 @@ function createContainer(config, opencodeClient, logger) {
2798
2807
  const listLspUseCase = new ListLspUseCase(sessionRepo, opencodeClient);
2799
2808
  const listMcpUseCase = new ListMcpUseCase(sessionRepo, opencodeClient);
2800
2809
  const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
2801
- const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
2810
+ const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, opencodeClient);
2802
2811
  const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
2803
2812
  const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, opencodeLogger);
2804
2813
  const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, promptLogger);
@@ -3015,11 +3024,7 @@ async function handleSessionIdle(runtime, event) {
3015
3024
  component: "plugin-event",
3016
3025
  sessionId: event.sessionId
3017
3026
  });
3018
- if (runtime.container.foregroundSessionTracker.clear(event.sessionId)) {
3019
- logPluginEvent(logger, { event: "plugin-event.session.idle.foreground_suppressed" }, "session idle notification suppressed for foreground Telegram session");
3020
- return;
3021
- }
3022
- await notifyBoundChats(runtime, event.sessionId, `Session finished.\n\nSession: ${event.sessionId}`);
3027
+ if (runtime.container.foregroundSessionTracker.clear(event.sessionId)) logPluginEvent(logger, { event: "plugin-event.session.idle.foreground_suppressed" }, "session idle notification suppressed for foreground Telegram session");
3023
3028
  }
3024
3029
  async function handleSessionStatus(runtime, event) {
3025
3030
  if (event.statusType !== "idle") return;
@@ -3115,14 +3120,16 @@ function normalizePermissionReply(value) {
3115
3120
  function extractSessionErrorMessage(error) {
3116
3121
  if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
3117
3122
  if (!isPlainRecord(error)) return null;
3123
+ if (typeof error.message === "string" && error.message.trim().length > 0) return error.message.trim();
3118
3124
  if (isPlainRecord(error.data) && typeof error.data.message === "string" && error.data.message.trim().length > 0) return error.data.message.trim();
3119
3125
  return asNonEmptyString(error.name);
3120
3126
  }
3121
3127
  function normalizeForegroundSessionError(error) {
3122
3128
  if (error instanceof Error) return error;
3123
- if (isPlainRecord(error) && typeof error.name === "string" && error.name.trim().length > 0) {
3129
+ if (isPlainRecord(error)) {
3124
3130
  const normalized = new Error(extractSessionErrorMessage(error) ?? "Unknown session error.");
3125
- normalized.name = error.name.trim();
3131
+ const normalizedName = asNonEmptyString(error.name);
3132
+ if (normalizedName) normalized.name = normalizedName;
3126
3133
  if (isPlainRecord(error.data)) normalized.data = error.data;
3127
3134
  return normalized;
3128
3135
  }
@@ -3915,14 +3922,18 @@ function normalizeError(error, copy) {
3915
3922
  message: copy.errors.contextOverflow,
3916
3923
  cause: extractMessage(error.data) ?? null
3917
3924
  };
3918
- if (isNamedError(error, "APIError")) return {
3919
- message: copy.errors.providerRequest,
3920
- cause: joinNonEmptyParts([
3921
- extractMessage(error.data),
3922
- extractStatusCode(error.data, copy),
3923
- extractRetryable(error.data, copy)
3924
- ])
3925
- };
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
+ }
3926
3937
  if (isNamedError(error, "NotFoundError")) return {
3927
3938
  message: copy.errors.notFound,
3928
3939
  cause: extractMessage(error.data) ?? null
@@ -3940,6 +3951,10 @@ function normalizeError(error, copy) {
3940
3951
  cause: extractMessage(error) ?? stringifyUnknown(error)
3941
3952
  };
3942
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
+ }
3943
3958
  function isBadRequestError(error) {
3944
3959
  return !!error && typeof error === "object" && "success" in error && error.success === false;
3945
3960
  }
@@ -4369,7 +4384,7 @@ function getStatusLayoutCopy(copy) {
4369
4384
  mcpNotesLabel: "Notes",
4370
4385
  mcpRegistrationRequiredStatus: "🟡",
4371
4386
  mcpTitle: "🔌 MCP",
4372
- noPluginsMessage: "No plugins configured in the OpenCode config.",
4387
+ noPluginsMessage: "No plugins detected in the current OpenCode setup.",
4373
4388
  noneStatus: "⚪",
4374
4389
  openCodeVersionLabel: "OpenCode Version",
4375
4390
  overviewTitle: "🖥️ Overview",
@@ -4390,7 +4405,7 @@ function getStatusLayoutCopy(copy) {
4390
4405
  mcpNotesLabel: "補足",
4391
4406
  mcpRegistrationRequiredStatus: "🟡",
4392
4407
  mcpTitle: "🔌 MCP",
4393
- noPluginsMessage: "現在の OpenCode 設定にはプラグインが設定されていません。",
4408
+ noPluginsMessage: "現在の OpenCode 構成ではプラグインが検出されていません。",
4394
4409
  noneStatus: "⚪",
4395
4410
  openCodeVersionLabel: "OpenCode バージョン",
4396
4411
  overviewTitle: "🖥️ 概要",
@@ -4411,7 +4426,7 @@ function getStatusLayoutCopy(copy) {
4411
4426
  mcpNotesLabel: "说明",
4412
4427
  mcpRegistrationRequiredStatus: "🟡",
4413
4428
  mcpTitle: "🔌 MCP",
4414
- noPluginsMessage: "当前 OpenCode 配置中未配置插件。",
4429
+ noPluginsMessage: "当前 OpenCode 环境中未检测到插件。",
4415
4430
  noneStatus: "⚪",
4416
4431
  openCodeVersionLabel: "OpenCode版本",
4417
4432
  overviewTitle: "🖥️ 概览",
@@ -5832,14 +5847,15 @@ function getTelegramBotRuntimeRegistry() {
5832
5847
  //#region src/plugin.ts
5833
5848
  async function ensureTelegramBotPluginRuntime(options) {
5834
5849
  const runtimeStateHolder = getTelegramBotPluginRuntimeStateHolder();
5835
- const cwd = resolvePluginRuntimeCwd(options.context);
5836
- 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) {
5837
5853
  const activeState = runtimeStateHolder.state;
5838
5854
  runtimeStateHolder.state = null;
5839
5855
  await disposeTelegramBotPluginRuntimeState(activeState);
5840
5856
  }
5841
5857
  if (!runtimeStateHolder.state) {
5842
- const runtimePromise = startPluginRuntime(options, cwd).then((runtime) => {
5858
+ const runtimePromise = startPluginRuntime(options, resolvePluginRuntimeCwd(options.context), requirePluginClient(options.context)).then((runtime) => {
5843
5859
  if (runtimeStateHolder.state?.runtimePromise === runtimePromise) runtimeStateHolder.state.runtime = runtime;
5844
5860
  return runtime;
5845
5861
  }).catch((error) => {
@@ -5847,7 +5863,7 @@ async function ensureTelegramBotPluginRuntime(options) {
5847
5863
  throw error;
5848
5864
  });
5849
5865
  runtimeStateHolder.state = {
5850
- cwd,
5866
+ cwd: explicitCwd ?? resolvePluginRuntimeCwd(options.context),
5851
5867
  runtime: null,
5852
5868
  runtimePromise
5853
5869
  };
@@ -5864,7 +5880,7 @@ async function resetTelegramBotPluginRuntimeForTests() {
5864
5880
  runtimeStateHolder.state = null;
5865
5881
  await disposeTelegramBotPluginRuntimeState(activeState);
5866
5882
  }
5867
- async function startPluginRuntime(options, cwd) {
5883
+ async function startPluginRuntime(options, cwd, client) {
5868
5884
  const bootstrapApp = options.bootstrapApp ?? bootstrapPluginApp;
5869
5885
  const prepareConfiguration = options.prepareConfiguration ?? preparePluginConfiguration;
5870
5886
  const startRuntime = options.startRuntime ?? startTelegramBotRuntime;
@@ -5872,7 +5888,7 @@ async function startPluginRuntime(options, cwd) {
5872
5888
  cwd,
5873
5889
  config: options.config
5874
5890
  });
5875
- const { config, container } = bootstrapApp(options.context.client, preparedConfiguration.config, { cwd: preparedConfiguration.cwd });
5891
+ const { config, container } = bootstrapApp(client, preparedConfiguration.config, { cwd: preparedConfiguration.cwd });
5876
5892
  try {
5877
5893
  if (preparedConfiguration.ignoredProjectConfigFilePath) container.logger.child({ component: "runtime" }).warn({
5878
5894
  cwd: preparedConfiguration.cwd,
@@ -5899,7 +5915,19 @@ async function startPluginRuntime(options, cwd) {
5899
5915
  }
5900
5916
  }
5901
5917
  function resolvePluginRuntimeCwd(context) {
5902
- 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;
5903
5931
  }
5904
5932
  async function disposeTelegramBotPluginRuntimeState(state) {
5905
5933
  await (state.runtime ?? await state.runtimePromise.catch(() => null))?.dispose();