opencode-tbot 0.1.1 → 0.1.3

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,6 +1,6 @@
1
1
  import { i as preparePluginConfiguration, s as loadAppConfig } from "./assets/plugin-config-BYsYAzvx.js";
2
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
- import { basename, dirname, extname } from "node:path";
2
+ import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, extname, join } from "node:path";
4
4
  import { parse, printParseErrorCode } from "jsonc-parser";
5
5
  import { z } from "zod";
6
6
  import { OpenRouter } from "@openrouter/sdk";
@@ -8,6 +8,8 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { run } from "@grammyjs/runner";
10
10
  import { Bot, InlineKeyboard } from "grammy";
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { fileURLToPath } from "node:url";
11
13
  //#region src/infra/utils/redact.ts
12
14
  var REDACTED = "[REDACTED]";
13
15
  var DEFAULT_PREVIEW_LENGTH = 160;
@@ -960,11 +962,14 @@ var GetPathUseCase = class {
960
962
  //#endregion
961
963
  //#region src/use-cases/get-status.usecase.ts
962
964
  var GetStatusUseCase = class {
963
- constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase) {
965
+ constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, voiceTranscriptionEnabled) {
964
966
  this.getHealthUseCase = getHealthUseCase;
965
967
  this.getPathUseCase = getPathUseCase;
966
968
  this.listLspUseCase = listLspUseCase;
967
969
  this.listMcpUseCase = listMcpUseCase;
970
+ this.listSessionsUseCase = listSessionsUseCase;
971
+ this.sessionRepo = sessionRepo;
972
+ this.voiceTranscriptionEnabled = voiceTranscriptionEnabled;
968
973
  }
969
974
  async execute(input) {
970
975
  const [health, path, lsp, mcp] = await Promise.allSettled([
@@ -974,10 +979,13 @@ var GetStatusUseCase = class {
974
979
  this.listMcpUseCase.execute({ chatId: input.chatId })
975
980
  ]);
976
981
  const pathResult = mapSettledResult(path);
982
+ const [plugins, workspace] = await Promise.all([loadConfiguredPluginsResult(pathResult), loadWorkspaceStatusResult(input.chatId, pathResult, this.listSessionsUseCase, this.sessionRepo)]);
977
983
  return {
978
984
  health: mapSettledResult(health),
979
985
  path: pathResult,
980
- plugins: await loadConfiguredPluginsResult(pathResult),
986
+ plugins,
987
+ voiceRecognition: { enabled: this.voiceTranscriptionEnabled },
988
+ workspace,
981
989
  lsp: mapSettledResult(lsp),
982
990
  mcp: mapSettledResult(mcp)
983
991
  };
@@ -1011,12 +1019,13 @@ async function loadConfiguredPluginsResult(path) {
1011
1019
  }
1012
1020
  }
1013
1021
  async function loadConfiguredPlugins(configFilePath) {
1022
+ const resolvedConfigFilePath = await resolveOpenCodeConfigFilePath(configFilePath);
1014
1023
  let content;
1015
1024
  try {
1016
- content = await readFile(configFilePath, "utf8");
1025
+ content = await readFile(resolvedConfigFilePath, "utf8");
1017
1026
  } catch (error) {
1018
1027
  if (isMissingFileError(error)) return {
1019
- configFilePath,
1028
+ configFilePath: resolvedConfigFilePath,
1020
1029
  plugins: []
1021
1030
  };
1022
1031
  throw error;
@@ -1025,18 +1034,52 @@ async function loadConfiguredPlugins(configFilePath) {
1025
1034
  const parsed = parse(content, parseErrors, { allowTrailingComma: true });
1026
1035
  if (parseErrors.length > 0) {
1027
1036
  const errorSummary = parseErrors.map((error) => printParseErrorCode(error.error)).join(", ");
1028
- throw new Error(`Failed to parse ${configFilePath}: ${errorSummary}`);
1037
+ throw new Error(`Failed to parse ${resolvedConfigFilePath}: ${errorSummary}`);
1029
1038
  }
1030
1039
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
1031
- configFilePath,
1040
+ configFilePath: resolvedConfigFilePath,
1032
1041
  plugins: []
1033
1042
  };
1034
1043
  const pluginSpecs = Array.isArray(parsed.plugin) ? parsed.plugin : [];
1035
1044
  return {
1036
- configFilePath,
1045
+ configFilePath: resolvedConfigFilePath,
1037
1046
  plugins: [...new Set(pluginSpecs.filter((value) => typeof value === "string").map((value) => value.trim()).filter((value) => value.length > 0))]
1038
1047
  };
1039
1048
  }
1049
+ async function loadWorkspaceStatusResult(chatId, path, listSessionsUseCase, sessionRepo) {
1050
+ if (path.status === "error") return {
1051
+ error: path.error,
1052
+ status: "error"
1053
+ };
1054
+ const binding = await sessionRepo.getByChatId(chatId);
1055
+ let currentProject = path.data.directory;
1056
+ let currentSession = binding?.sessionId ?? null;
1057
+ try {
1058
+ const sessions = await listSessionsUseCase.execute({ chatId });
1059
+ currentProject = sessions.currentDirectory;
1060
+ currentSession = sessions.currentSessionId ? formatSessionStatusLabel(sessions.sessions.find((session) => session.id === sessions.currentSessionId) ?? null, sessions.currentSessionId) : null;
1061
+ } catch {}
1062
+ return {
1063
+ data: {
1064
+ currentProject,
1065
+ currentSession
1066
+ },
1067
+ status: "ok"
1068
+ };
1069
+ }
1070
+ function formatSessionStatusLabel(session, fallbackId) {
1071
+ if (!session) return fallbackId;
1072
+ const title = session.title.trim() || session.slug || session.id;
1073
+ return title === session.slug ? title : `${title} (${session.slug})`;
1074
+ }
1075
+ async function resolveOpenCodeConfigFilePath(configPath) {
1076
+ try {
1077
+ return (await stat(configPath)).isDirectory() ? join(configPath, "opencode.json") : configPath;
1078
+ } catch (error) {
1079
+ if (isMissingFileError(error)) return configPath;
1080
+ throw error;
1081
+ }
1082
+ }
1040
1083
  function isMissingFileError(error) {
1041
1084
  return error instanceof Error && "code" in error && error.code === "ENOENT";
1042
1085
  }
@@ -1559,9 +1602,9 @@ function createContainer(config, opencodeClient, logger) {
1559
1602
  const listAgentsUseCase = new ListAgentsUseCase(sessionRepo, opencodeClient);
1560
1603
  const listLspUseCase = new ListLspUseCase(sessionRepo, opencodeClient);
1561
1604
  const listMcpUseCase = new ListMcpUseCase(sessionRepo, opencodeClient);
1562
- const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase);
1563
- const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
1564
1605
  const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
1606
+ const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, config.openrouter.configured);
1607
+ const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
1565
1608
  const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
1566
1609
  const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger, foregroundSessionTracker);
1567
1610
  const switchAgentUseCase = new SwitchAgentUseCase(sessionRepo, opencodeClient, logger);
@@ -2564,6 +2607,23 @@ function stringifyUnknown(value) {
2564
2607
  }
2565
2608
  }
2566
2609
  //#endregion
2610
+ //#region src/app/package-info.ts
2611
+ var OPENCODE_TBOT_VERSION = resolvePackageVersion();
2612
+ function resolvePackageVersion() {
2613
+ let directory = dirname(fileURLToPath(import.meta.url));
2614
+ while (true) {
2615
+ const packageFilePath = join(directory, "package.json");
2616
+ if (existsSync(packageFilePath)) try {
2617
+ const parsed = JSON.parse(readFileSync(packageFilePath, "utf8"));
2618
+ if (typeof parsed.version === "string" && parsed.version.trim().length > 0) return parsed.version;
2619
+ } catch {}
2620
+ const parentDirectory = dirname(directory);
2621
+ if (parentDirectory === directory) break;
2622
+ directory = parentDirectory;
2623
+ }
2624
+ return "unknown";
2625
+ }
2626
+ //#endregion
2567
2627
  //#region src/bot/presenters/message.presenter.ts
2568
2628
  var VARIANT_ORDER = [
2569
2629
  "minimal",
@@ -2577,26 +2637,22 @@ var VARIANT_ORDER = [
2577
2637
  function presentStatusMessage(input, copy = BOT_COPY) {
2578
2638
  const layout = getStatusLayoutCopy(copy);
2579
2639
  const sections = [
2580
- presentStatusPlainSection(layout.serverTitle, presentStatusPlainServerLines(input, copy, layout)),
2640
+ presentStatusPlainSection(layout.overviewTitle, presentStatusPlainOverviewLines(input, copy, layout)),
2581
2641
  presentStatusPlainSection(layout.workspaceTitle, presentStatusPlainWorkspaceLines(input, copy, layout)),
2582
2642
  presentStatusPlainSection(layout.pluginsTitle, presentStatusPlainPluginLines(input, copy, layout)),
2583
2643
  presentStatusPlainSection(layout.mcpTitle, presentStatusPlainMcpLines(input, copy, layout)),
2584
- presentStatusPlainSection(layout.lspTitle, presentStatusPlainLspLines(input, copy, layout)),
2585
- layout.divider,
2586
- `${layout.lastUpdatedLabel}: ${formatStatusDate(/* @__PURE__ */ new Date())}`
2644
+ presentStatusPlainSection(layout.lspTitle, presentStatusPlainLspLines(input, copy, layout))
2587
2645
  ];
2588
2646
  return presentStatusSections(layout.pageTitle, sections);
2589
2647
  }
2590
2648
  function presentStatusMarkdownMessage(input, copy = BOT_COPY) {
2591
2649
  const layout = getStatusLayoutCopy(copy);
2592
2650
  const sections = [
2593
- presentStatusMarkdownSection(layout.serverTitle, presentStatusMarkdownServerLines(input, copy, layout)),
2651
+ presentStatusMarkdownSection(layout.overviewTitle, presentStatusMarkdownOverviewLines(input, copy, layout)),
2594
2652
  presentStatusMarkdownSection(layout.workspaceTitle, presentStatusMarkdownWorkspaceLines(input, copy, layout)),
2595
2653
  presentStatusMarkdownSection(layout.pluginsTitle, presentStatusMarkdownPluginLines(input, copy, layout)),
2596
2654
  presentStatusMarkdownSection(layout.mcpTitle, presentStatusMarkdownMcpLines(input, copy, layout)),
2597
- presentStatusMarkdownSection(layout.lspTitle, presentStatusMarkdownLspLines(input, copy, layout)),
2598
- layout.divider,
2599
- `_${layout.lastUpdatedLabel}: ${formatStatusDate(/* @__PURE__ */ new Date())}_`
2655
+ presentStatusMarkdownSection(layout.lspTitle, presentStatusMarkdownLspLines(input, copy, layout))
2600
2656
  ];
2601
2657
  return presentStatusSections(`# ${layout.pageTitle}`, sections);
2602
2658
  }
@@ -2613,48 +2669,56 @@ function presentStatusPlainSection(title, lines) {
2613
2669
  function presentStatusMarkdownSection(title, lines) {
2614
2670
  return [`## ${title}`, ...lines].join("\n");
2615
2671
  }
2616
- function presentStatusPlainServerLines(input, copy, layout) {
2617
- if (input.health.status === "error") return presentStatusPlainErrorLines(input.health.error, copy, layout);
2618
- return [presentPlainStatusBullet(layout.statusLabel, formatHealthBadge(input.health.data.healthy, layout)), presentPlainStatusBullet(layout.versionLabel, input.health.data.version)];
2672
+ function presentStatusPlainOverviewLines(input, copy, layout) {
2673
+ const lines = [presentPlainStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentPlainStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition.enabled))];
2674
+ if (input.health.status === "error") return [
2675
+ ...lines,
2676
+ ...presentStatusPlainErrorDetailLines(input.health.error, copy, layout),
2677
+ presentPlainStatusBullet(layout.tbotVersionLabel, OPENCODE_TBOT_VERSION)
2678
+ ];
2679
+ return [
2680
+ ...lines,
2681
+ presentPlainStatusBullet(layout.openCodeVersionLabel, input.health.data.version),
2682
+ presentPlainStatusBullet(layout.tbotVersionLabel, OPENCODE_TBOT_VERSION)
2683
+ ];
2619
2684
  }
2620
- function presentStatusMarkdownServerLines(input, copy, layout) {
2621
- if (input.health.status === "error") return presentStatusMarkdownErrorLines(input.health.error, copy, layout);
2622
- return [presentMarkdownStatusBullet(layout.statusLabel, formatHealthBadge(input.health.data.healthy, layout)), presentMarkdownStatusBullet(layout.versionLabel, input.health.data.version)];
2685
+ function presentStatusMarkdownOverviewLines(input, copy, layout) {
2686
+ const lines = [presentMarkdownStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentMarkdownStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition.enabled))];
2687
+ if (input.health.status === "error") return [
2688
+ ...lines,
2689
+ ...presentStatusMarkdownErrorDetailLines(input.health.error, copy, layout),
2690
+ presentMarkdownStatusBullet(layout.tbotVersionLabel, OPENCODE_TBOT_VERSION)
2691
+ ];
2692
+ return [
2693
+ ...lines,
2694
+ presentMarkdownStatusBullet(layout.openCodeVersionLabel, input.health.data.version),
2695
+ presentMarkdownStatusBullet(layout.tbotVersionLabel, OPENCODE_TBOT_VERSION)
2696
+ ];
2623
2697
  }
2624
2698
  function presentStatusPlainWorkspaceLines(input, copy, layout) {
2625
- if (input.path.status === "error") return presentStatusPlainErrorLines(input.path.error, copy, layout);
2626
- return [presentPlainStatusBullet(layout.currentDirectoryLabel, input.path.data.directory)];
2699
+ if (input.workspace.status === "error") return presentStatusPlainErrorLines(input.workspace.error, copy, layout);
2700
+ return [presentPlainStatusBullet(layout.currentProjectLabel, input.workspace.data.currentProject), presentPlainStatusBullet(layout.currentSessionLabel, input.workspace.data.currentSession ?? layout.defaultSessionValue)];
2627
2701
  }
2628
2702
  function presentStatusMarkdownWorkspaceLines(input, copy, layout) {
2629
- if (input.path.status === "error") return presentStatusMarkdownErrorLines(input.path.error, copy, layout);
2630
- return [presentMarkdownStatusBullet(layout.currentDirectoryLabel, input.path.data.directory, { codeValue: true })];
2703
+ if (input.workspace.status === "error") return presentStatusMarkdownErrorLines(input.workspace.error, copy, layout);
2704
+ return [presentMarkdownStatusBullet(layout.currentProjectLabel, input.workspace.data.currentProject, { codeValue: true }), presentMarkdownStatusBullet(layout.currentSessionLabel, input.workspace.data.currentSession ?? layout.defaultSessionValue)];
2631
2705
  }
2632
2706
  function presentStatusPlainPluginLines(input, copy, layout) {
2633
2707
  if (input.plugins.status === "error") return presentStatusPlainErrorLines(input.plugins.error, copy, layout);
2634
- const lines = [presentPlainStatusBullet(layout.configFileLabel, input.plugins.data.configFilePath)];
2635
- if (input.plugins.data.plugins.length === 0) return [...lines, ...presentPlainEmptyStatusLines(layout.noPluginsMessage, layout)];
2636
- return [
2637
- ...lines,
2638
- `- ${layout.configuredPluginsLabel}:`,
2639
- ...input.plugins.data.plugins.map((plugin) => ` - ${plugin}`)
2640
- ];
2708
+ if (input.plugins.data.plugins.length === 0) return [...presentPlainEmptyStatusLines(layout.noPluginsMessage, layout)];
2709
+ return input.plugins.data.plugins.map((plugin) => `- ${plugin}`);
2641
2710
  }
2642
2711
  function presentStatusMarkdownPluginLines(input, copy, layout) {
2643
2712
  if (input.plugins.status === "error") return presentStatusMarkdownErrorLines(input.plugins.error, copy, layout);
2644
- const lines = [presentMarkdownStatusBullet(layout.configFileLabel, input.plugins.data.configFilePath, { codeValue: true })];
2645
- if (input.plugins.data.plugins.length === 0) return [...lines, ...presentMarkdownEmptyStatusLines(layout.noPluginsMessage, layout)];
2646
- return [
2647
- ...lines,
2648
- `- **${layout.configuredPluginsLabel}:**`,
2649
- ...input.plugins.data.plugins.map((plugin) => ` - \`${plugin}\``)
2650
- ];
2713
+ if (input.plugins.data.plugins.length === 0) return [...presentMarkdownEmptyStatusLines(layout.noPluginsMessage, layout)];
2714
+ return input.plugins.data.plugins.map((plugin) => `- \`${plugin}\``);
2651
2715
  }
2652
2716
  function presentStatusPlainLspLines(input, copy, layout) {
2653
2717
  if (input.lsp.status === "error") return presentStatusPlainErrorLines(input.lsp.error, copy, layout);
2654
2718
  if (input.lsp.data.statuses.length === 0) return presentPlainEmptyStatusLines(copy.lsp.none, layout);
2655
2719
  return input.lsp.data.statuses.flatMap((status) => presentPlainStatusGroup(status.name, [{
2656
2720
  label: layout.statusLabel,
2657
- value: formatLspStatusBadge(status, copy)
2721
+ value: formatLspStatusBadge(status)
2658
2722
  }, {
2659
2723
  codeValue: !!status.root,
2660
2724
  label: layout.rootLabel,
@@ -2666,7 +2730,7 @@ function presentStatusMarkdownLspLines(input, copy, layout) {
2666
2730
  if (input.lsp.data.statuses.length === 0) return presentMarkdownEmptyStatusLines(copy.lsp.none, layout);
2667
2731
  return input.lsp.data.statuses.flatMap((status) => presentMarkdownStatusGroup(status.name, [{
2668
2732
  label: layout.statusLabel,
2669
- value: formatLspStatusBadge(status, copy)
2733
+ value: formatLspStatusBadge(status)
2670
2734
  }, {
2671
2735
  codeValue: !!status.root,
2672
2736
  label: layout.rootLabel,
@@ -2676,32 +2740,24 @@ function presentStatusMarkdownLspLines(input, copy, layout) {
2676
2740
  function presentStatusPlainMcpLines(input, copy, layout) {
2677
2741
  if (input.mcp.status === "error") return presentStatusPlainErrorLines(input.mcp.error, copy, layout);
2678
2742
  if (input.mcp.data.statuses.length === 0) return presentPlainEmptyStatusLines(copy.mcp.none, layout);
2679
- return input.mcp.data.statuses.flatMap(({ name, status }) => presentPlainStatusGroup(name, [{
2680
- label: layout.statusLabel,
2681
- value: formatMcpStatusBadge(status, copy, layout)
2682
- }, {
2683
- label: layout.mcpNotesLabel,
2684
- value: formatMcpStatusNotes(status, layout)
2685
- }]));
2743
+ return input.mcp.data.statuses.flatMap(({ name, status }) => presentPlainStatusGroup(name, getMcpStatusDetailLines(status, copy, layout)));
2686
2744
  }
2687
2745
  function presentStatusMarkdownMcpLines(input, copy, layout) {
2688
2746
  if (input.mcp.status === "error") return presentStatusMarkdownErrorLines(input.mcp.error, copy, layout);
2689
2747
  if (input.mcp.data.statuses.length === 0) return presentMarkdownEmptyStatusLines(copy.mcp.none, layout);
2690
- return input.mcp.data.statuses.flatMap(({ name, status }) => presentMarkdownStatusGroup(name, [{
2691
- label: layout.statusLabel,
2692
- value: formatMcpStatusBadge(status, copy, layout)
2693
- }, {
2694
- label: layout.mcpNotesLabel,
2695
- value: formatMcpStatusNotes(status, layout)
2696
- }], { codeName: true }));
2748
+ return input.mcp.data.statuses.flatMap(({ name, status }) => presentMarkdownStatusGroup(name, getMcpStatusDetailLines(status, copy, layout), { codeName: true }));
2697
2749
  }
2698
2750
  function presentStatusPlainErrorLines(error, copy, layout) {
2699
- const detailLines = splitStatusLines(presentError(error, copy));
2700
- return [presentPlainStatusBullet(layout.statusLabel, layout.errorStatus), ...detailLines.map((line) => presentPlainStatusBullet(layout.detailsLabel, line))];
2751
+ return [presentPlainStatusBullet(layout.statusLabel, layout.errorStatus), ...presentStatusPlainErrorDetailLines(error, copy, layout)];
2752
+ }
2753
+ function presentStatusPlainErrorDetailLines(error, copy, layout) {
2754
+ return splitStatusLines(presentError(error, copy)).map((line) => presentPlainStatusBullet(layout.detailsLabel, line));
2701
2755
  }
2702
2756
  function presentStatusMarkdownErrorLines(error, copy, layout) {
2703
- const detailLines = splitStatusLines(presentError(error, copy));
2704
- return [presentMarkdownStatusBullet(layout.statusLabel, layout.errorStatus), ...detailLines.map((line) => presentMarkdownStatusBullet(layout.detailsLabel, line))];
2757
+ return [presentMarkdownStatusBullet(layout.statusLabel, layout.errorStatus), ...presentStatusMarkdownErrorDetailLines(error, copy, layout)];
2758
+ }
2759
+ function presentStatusMarkdownErrorDetailLines(error, copy, layout) {
2760
+ return splitStatusLines(presentError(error, copy)).map((line) => presentMarkdownStatusBullet(layout.detailsLabel, line));
2705
2761
  }
2706
2762
  function presentPlainEmptyStatusLines(message, layout) {
2707
2763
  return [presentPlainStatusBullet(layout.statusLabel, layout.noneStatus), presentPlainStatusBullet(layout.detailsLabel, message)];
@@ -2725,38 +2781,51 @@ function splitStatusLines(text) {
2725
2781
  return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
2726
2782
  }
2727
2783
  function formatHealthBadge(healthy, layout) {
2728
- return healthy ? layout.healthyStatus : layout.errorStatus;
2784
+ return healthy ? "🟢" : layout.errorStatus;
2785
+ }
2786
+ function formatVoiceRecognitionBadge(enabled) {
2787
+ return enabled ? "🟢" : "⚪";
2729
2788
  }
2730
- function formatLspStatusBadge(status, copy) {
2789
+ function formatLspStatusBadge(status) {
2731
2790
  switch (status.status) {
2732
- case "connected": return `\uD83D\uDFE2 ${copy.lsp.connected}`;
2733
- case "error": return `\uD83D\uDD34 ${copy.lsp.error}`;
2791
+ case "connected": return "🟢";
2792
+ case "error": return "🔴";
2734
2793
  }
2735
2794
  return status.status;
2736
2795
  }
2737
- function formatMcpStatusBadge(status, copy, layout) {
2796
+ function formatMcpStatusBadge(status, layout) {
2738
2797
  switch (status.status) {
2739
- case "connected": return `\uD83D\uDFE2 ${copy.mcp.connected}`;
2740
- case "disabled": return `\u26AA ${copy.mcp.disabled}`;
2741
- case "needs_auth": return `\uD83D\uDFE1 ${copy.mcp.needsAuth}`;
2798
+ case "connected": return "🟢";
2799
+ case "disabled": return "⚪";
2800
+ case "needs_auth": return "🟡";
2742
2801
  case "failed": return layout.mcpFailedStatus;
2743
2802
  case "needs_client_registration": return layout.mcpRegistrationRequiredStatus;
2744
2803
  }
2745
2804
  return status;
2746
2805
  }
2747
- function formatMcpStatusNotes(status, layout) {
2806
+ function getMcpStatusDetailLines(status, copy, layout) {
2807
+ const notes = formatMcpStatusNotes(status, copy, layout);
2808
+ return notes ? [{
2809
+ label: layout.statusLabel,
2810
+ value: formatMcpStatusBadge(status, layout)
2811
+ }, {
2812
+ label: layout.mcpNotesLabel,
2813
+ value: notes
2814
+ }] : [{
2815
+ label: layout.statusLabel,
2816
+ value: formatMcpStatusBadge(status, layout)
2817
+ }];
2818
+ }
2819
+ function formatMcpStatusNotes(status, copy, layout) {
2748
2820
  switch (status.status) {
2749
2821
  case "connected": return layout.okLabel;
2750
- case "disabled": return "-";
2751
- case "needs_auth": return "-";
2822
+ case "disabled": return null;
2823
+ case "needs_auth": return copy.mcp.needsAuth;
2752
2824
  case "failed": return status.error;
2753
2825
  case "needs_client_registration": return status.error;
2754
2826
  }
2755
2827
  return status;
2756
2828
  }
2757
- function formatStatusDate(value) {
2758
- return value.toISOString().slice(0, 10);
2759
- }
2760
2829
  function formatStatusValue(value) {
2761
2830
  const normalized = value.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" / ");
2762
2831
  return normalized.length > 0 ? normalized : "-";
@@ -2766,53 +2835,53 @@ function normalizeStatusInlineValue(value) {
2766
2835
  }
2767
2836
  function getStatusLayoutCopy(copy) {
2768
2837
  if (copy.systemStatus.title === BOT_COPY.systemStatus.title) return {
2769
- configFileLabel: "Config File",
2770
- configuredPluginsLabel: "Configured Plugins",
2771
- currentDirectoryLabel: "Current Directory",
2838
+ connectivityLabel: "Connectivity",
2839
+ currentProjectLabel: "Current Project",
2840
+ currentSessionLabel: "Current Session",
2841
+ defaultSessionValue: "OpenCode Default",
2772
2842
  detailsLabel: "Details",
2773
- divider: "----------------",
2774
- errorStatus: "🔴 Unhealthy",
2775
- healthyStatus: "🟢 Healthy",
2776
- lastUpdatedLabel: "Last updated",
2777
- lspTitle: "🧠 LSP (Language Server)",
2778
- mcpFailedStatus: "🔴 Failed",
2843
+ errorStatus: "🔴",
2844
+ lspTitle: "🧠 LSP",
2845
+ mcpFailedStatus: "🔴",
2779
2846
  mcpNotesLabel: "Notes",
2780
- mcpRegistrationRequiredStatus: "🟡 Registration Required",
2781
- mcpTitle: "🔌 MCP (Model Context Protocol)",
2847
+ mcpRegistrationRequiredStatus: "🟡",
2848
+ mcpTitle: "🔌 MCP",
2782
2849
  noPluginsMessage: "No plugins configured in the OpenCode config.",
2783
- noneStatus: "⚪ None",
2850
+ noneStatus: "⚪",
2851
+ openCodeVersionLabel: "OpenCode Version",
2784
2852
  okLabel: "OK",
2853
+ overviewTitle: "🖥️ Overview",
2785
2854
  pageTitle: "📊 Service Status",
2786
2855
  pluginsTitle: "🧩 Plugins",
2787
2856
  rootLabel: "Root",
2788
- serverTitle: "🖥️ Server",
2789
2857
  statusLabel: "Status",
2790
- versionLabel: "Version",
2858
+ tbotVersionLabel: "opencode-tbot Version",
2859
+ voiceRecognitionLabel: "Voice Recognition",
2791
2860
  workspaceTitle: "📁 Workspace"
2792
2861
  };
2793
2862
  return {
2794
- configFileLabel: "配置文件",
2795
- configuredPluginsLabel: "已配置插件",
2796
- currentDirectoryLabel: "当前目录",
2863
+ connectivityLabel: "连通性",
2864
+ currentProjectLabel: "当前项目",
2865
+ currentSessionLabel: "当前会话",
2866
+ defaultSessionValue: "OpenCode 默认",
2797
2867
  detailsLabel: "详情",
2798
- divider: "----------------",
2799
- errorStatus: "🔴 异常",
2800
- healthyStatus: "🟢 健康",
2801
- lastUpdatedLabel: "最后更新",
2802
- lspTitle: "🧠 LSP (Language Server)",
2803
- mcpFailedStatus: "🔴 失败",
2868
+ errorStatus: "🔴",
2869
+ lspTitle: "🧠 LSP",
2870
+ mcpFailedStatus: "🔴",
2804
2871
  mcpNotesLabel: "说明",
2805
- mcpRegistrationRequiredStatus: "🟡 需要注册",
2806
- mcpTitle: "🔌 MCP (Model Context Protocol)",
2872
+ mcpRegistrationRequiredStatus: "🟡",
2873
+ mcpTitle: "🔌 MCP",
2807
2874
  noPluginsMessage: "当前 OpenCode 配置中未配置插件。",
2808
- noneStatus: "⚪",
2875
+ noneStatus: "⚪",
2876
+ openCodeVersionLabel: "OpenCode版本",
2809
2877
  okLabel: "正常",
2878
+ overviewTitle: "🖥️ 概览",
2810
2879
  pageTitle: "📊 服务状态",
2811
2880
  pluginsTitle: "🧩 插件",
2812
2881
  rootLabel: "根目录",
2813
- serverTitle: "🖥️ 服务端",
2814
2882
  statusLabel: "状态",
2815
- versionLabel: "版本",
2883
+ tbotVersionLabel: "opencode-tbot版本",
2884
+ voiceRecognitionLabel: "语音识别",
2816
2885
  workspaceTitle: "📁 工作区"
2817
2886
  };
2818
2887
  }