opencode-tbot 0.1.3 → 0.1.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/dist/plugin.js +149 -32
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
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
2
|
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, dirname, extname, join } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, 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";
|
|
@@ -337,14 +337,15 @@ var OpenCodeClient = class {
|
|
|
337
337
|
}, SDK_OPTIONS));
|
|
338
338
|
const finishedAt = Date.now();
|
|
339
339
|
const bodyMd = input.structured ? extractStructuredMarkdown(data.info?.structured) : null;
|
|
340
|
-
const
|
|
340
|
+
const responseParts = Array.isArray(data.parts) ? data.parts : [];
|
|
341
|
+
const fallbackText = extractTextFromParts(responseParts) || bodyMd || EMPTY_RESPONSE_TEXT;
|
|
341
342
|
return {
|
|
342
343
|
assistantError: data.info?.error ?? null,
|
|
343
344
|
bodyMd,
|
|
344
345
|
fallbackText,
|
|
345
346
|
info: data.info ?? null,
|
|
346
347
|
metrics: extractPromptMetrics(data.info, startedAt, finishedAt),
|
|
347
|
-
parts:
|
|
348
|
+
parts: responseParts,
|
|
348
349
|
structured: data.info?.structured ?? null
|
|
349
350
|
};
|
|
350
351
|
}
|
|
@@ -446,6 +447,7 @@ function normalizeVariants(variants) {
|
|
|
446
447
|
}));
|
|
447
448
|
}
|
|
448
449
|
function extractTextFromParts(parts) {
|
|
450
|
+
if (!Array.isArray(parts)) return "";
|
|
449
451
|
return parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
|
|
450
452
|
}
|
|
451
453
|
function extractStructuredMarkdown(structured) {
|
|
@@ -778,7 +780,10 @@ var OpenRouterVoiceTranscriptionClient = class {
|
|
|
778
780
|
temperature: 0
|
|
779
781
|
} }, { timeoutMs: this.timeoutMs });
|
|
780
782
|
} catch (error) {
|
|
781
|
-
throw new VoiceTranscriptionFailedError(
|
|
783
|
+
throw new VoiceTranscriptionFailedError(buildTranscriptionErrorMessage(error, {
|
|
784
|
+
format,
|
|
785
|
+
model: this.model
|
|
786
|
+
}));
|
|
782
787
|
}
|
|
783
788
|
return { text: extractTranscript(response) };
|
|
784
789
|
}
|
|
@@ -841,8 +846,108 @@ function extractTranscript(response) {
|
|
|
841
846
|
function isTextContentItem(value) {
|
|
842
847
|
return !!value && typeof value === "object" && "type" in value && value.type === "text" && "text" in value && typeof value.text === "string";
|
|
843
848
|
}
|
|
849
|
+
function buildTranscriptionErrorMessage(error, context) {
|
|
850
|
+
const parsedBody = parseJsonBody(extractStringField(error, "body"));
|
|
851
|
+
const rawMessages = dedupeNonEmptyStrings([
|
|
852
|
+
extractErrorMessage(error),
|
|
853
|
+
extractErrorMessage(readField(error, "error")),
|
|
854
|
+
extractErrorMessage(readField(error, "data")),
|
|
855
|
+
extractErrorMessage(readField(readField(error, "data"), "error")),
|
|
856
|
+
extractErrorMessage(parsedBody),
|
|
857
|
+
extractErrorMessage(readField(parsedBody, "error")),
|
|
858
|
+
extractMetadataRawMessage(error),
|
|
859
|
+
extractMetadataRawMessage(parsedBody)
|
|
860
|
+
]);
|
|
861
|
+
const messages = rawMessages.some((message) => !isGenericProviderMessage(message)) ? rawMessages.filter((message) => !isGenericProviderMessage(message)) : rawMessages;
|
|
862
|
+
const providerName = extractProviderName(error) ?? extractProviderName(parsedBody);
|
|
863
|
+
const statusCode = extractNumericField(error, "statusCode") ?? extractNumericField(parsedBody, "statusCode");
|
|
864
|
+
const errorCode = extractErrorCode(error, parsedBody);
|
|
865
|
+
return joinNonEmptyParts$1([
|
|
866
|
+
...messages,
|
|
867
|
+
`model: ${context.model}`,
|
|
868
|
+
`format: ${context.format}`,
|
|
869
|
+
providerName ? `provider: ${providerName}` : null,
|
|
870
|
+
statusCode !== null ? `status: ${statusCode}` : null,
|
|
871
|
+
errorCode !== null ? `code: ${errorCode}` : null
|
|
872
|
+
]) ?? "Failed to reach OpenRouter voice transcription.";
|
|
873
|
+
}
|
|
844
874
|
function extractErrorMessage(error) {
|
|
845
|
-
|
|
875
|
+
if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
|
|
876
|
+
return extractStringField(error, "message");
|
|
877
|
+
}
|
|
878
|
+
function extractMetadataRawMessage(value) {
|
|
879
|
+
const raw = extractStringField(readField(value, "metadata") ?? readField(readField(value, "error"), "metadata") ?? readField(readField(readField(value, "data"), "error"), "metadata"), "raw");
|
|
880
|
+
if (!raw) return null;
|
|
881
|
+
return raw.length <= 280 ? raw : `${raw.slice(0, 277)}...`;
|
|
882
|
+
}
|
|
883
|
+
function extractProviderName(value) {
|
|
884
|
+
const candidates = [
|
|
885
|
+
readField(value, "metadata"),
|
|
886
|
+
readField(readField(value, "error"), "metadata"),
|
|
887
|
+
readField(readField(readField(value, "data"), "error"), "metadata")
|
|
888
|
+
];
|
|
889
|
+
for (const candidate of candidates) {
|
|
890
|
+
const providerName = extractStringField(candidate, "provider_name") ?? extractStringField(candidate, "providerName") ?? extractStringField(candidate, "provider");
|
|
891
|
+
if (providerName) return providerName;
|
|
892
|
+
}
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
function extractErrorCode(...values) {
|
|
896
|
+
for (const value of values) {
|
|
897
|
+
const candidates = [
|
|
898
|
+
value,
|
|
899
|
+
readField(value, "error"),
|
|
900
|
+
readField(value, "data"),
|
|
901
|
+
readField(readField(value, "data"), "error")
|
|
902
|
+
];
|
|
903
|
+
for (const candidate of candidates) {
|
|
904
|
+
const code = extractNumericField(candidate, "code");
|
|
905
|
+
if (code !== null) return code;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
function extractNumericField(value, fieldName) {
|
|
911
|
+
if (!value || typeof value !== "object" || !(fieldName in value)) return null;
|
|
912
|
+
const fieldValue = value[fieldName];
|
|
913
|
+
return typeof fieldValue === "number" && Number.isFinite(fieldValue) ? fieldValue : null;
|
|
914
|
+
}
|
|
915
|
+
function extractStringField(value, fieldName) {
|
|
916
|
+
if (!value || typeof value !== "object" || !(fieldName in value)) return null;
|
|
917
|
+
const fieldValue = value[fieldName];
|
|
918
|
+
return typeof fieldValue === "string" && fieldValue.trim().length > 0 ? fieldValue.trim() : null;
|
|
919
|
+
}
|
|
920
|
+
function readField(value, fieldName) {
|
|
921
|
+
return value && typeof value === "object" && fieldName in value ? value[fieldName] : null;
|
|
922
|
+
}
|
|
923
|
+
function parseJsonBody(body) {
|
|
924
|
+
if (!body) return null;
|
|
925
|
+
try {
|
|
926
|
+
return JSON.parse(body);
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function dedupeNonEmptyStrings(values) {
|
|
932
|
+
const seen = /* @__PURE__ */ new Set();
|
|
933
|
+
const result = [];
|
|
934
|
+
for (const value of values) {
|
|
935
|
+
const normalized = value?.trim();
|
|
936
|
+
if (!normalized) continue;
|
|
937
|
+
const key = normalized.toLowerCase();
|
|
938
|
+
if (seen.has(key)) continue;
|
|
939
|
+
seen.add(key);
|
|
940
|
+
result.push(normalized);
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
}
|
|
944
|
+
function joinNonEmptyParts$1(parts) {
|
|
945
|
+
const filtered = parts.map((part) => part?.trim()).filter((part) => !!part);
|
|
946
|
+
return filtered.length > 0 ? filtered.join(" | ") : null;
|
|
947
|
+
}
|
|
948
|
+
function isGenericProviderMessage(message) {
|
|
949
|
+
const normalized = message.trim().toLowerCase();
|
|
950
|
+
return normalized === "provider returned error" || normalized === "failed to reach openrouter voice transcription.";
|
|
846
951
|
}
|
|
847
952
|
//#endregion
|
|
848
953
|
//#region src/services/voice-transcription/voice-transcription.service.ts
|
|
@@ -962,14 +1067,14 @@ var GetPathUseCase = class {
|
|
|
962
1067
|
//#endregion
|
|
963
1068
|
//#region src/use-cases/get-status.usecase.ts
|
|
964
1069
|
var GetStatusUseCase = class {
|
|
965
|
-
constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo,
|
|
1070
|
+
constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, voiceRecognitionStatus) {
|
|
966
1071
|
this.getHealthUseCase = getHealthUseCase;
|
|
967
1072
|
this.getPathUseCase = getPathUseCase;
|
|
968
1073
|
this.listLspUseCase = listLspUseCase;
|
|
969
1074
|
this.listMcpUseCase = listMcpUseCase;
|
|
970
1075
|
this.listSessionsUseCase = listSessionsUseCase;
|
|
971
1076
|
this.sessionRepo = sessionRepo;
|
|
972
|
-
this.
|
|
1077
|
+
this.voiceRecognitionStatus = voiceRecognitionStatus;
|
|
973
1078
|
}
|
|
974
1079
|
async execute(input) {
|
|
975
1080
|
const [health, path, lsp, mcp] = await Promise.allSettled([
|
|
@@ -984,7 +1089,7 @@ var GetStatusUseCase = class {
|
|
|
984
1089
|
health: mapSettledResult(health),
|
|
985
1090
|
path: pathResult,
|
|
986
1091
|
plugins,
|
|
987
|
-
voiceRecognition:
|
|
1092
|
+
voiceRecognition: this.voiceRecognitionStatus,
|
|
988
1093
|
workspace,
|
|
989
1094
|
lsp: mapSettledResult(lsp),
|
|
990
1095
|
mcp: mapSettledResult(mcp)
|
|
@@ -1052,11 +1157,11 @@ async function loadWorkspaceStatusResult(chatId, path, listSessionsUseCase, sess
|
|
|
1052
1157
|
status: "error"
|
|
1053
1158
|
};
|
|
1054
1159
|
const binding = await sessionRepo.getByChatId(chatId);
|
|
1055
|
-
let currentProject = path.data
|
|
1160
|
+
let currentProject = resolveWorkspaceProjectPath(path.data);
|
|
1056
1161
|
let currentSession = binding?.sessionId ?? null;
|
|
1057
1162
|
try {
|
|
1058
1163
|
const sessions = await listSessionsUseCase.execute({ chatId });
|
|
1059
|
-
currentProject = sessions.currentDirectory;
|
|
1164
|
+
currentProject = resolveWorkspaceProjectPath(path.data, sessions.currentDirectory);
|
|
1060
1165
|
currentSession = sessions.currentSessionId ? formatSessionStatusLabel(sessions.sessions.find((session) => session.id === sessions.currentSessionId) ?? null, sessions.currentSessionId) : null;
|
|
1061
1166
|
} catch {}
|
|
1062
1167
|
return {
|
|
@@ -1072,6 +1177,20 @@ function formatSessionStatusLabel(session, fallbackId) {
|
|
|
1072
1177
|
const title = session.title.trim() || session.slug || session.id;
|
|
1073
1178
|
return title === session.slug ? title : `${title} (${session.slug})`;
|
|
1074
1179
|
}
|
|
1180
|
+
function resolveWorkspaceProjectPath(path, preferredPath) {
|
|
1181
|
+
const candidates = [
|
|
1182
|
+
preferredPath,
|
|
1183
|
+
path.worktree,
|
|
1184
|
+
path.directory
|
|
1185
|
+
];
|
|
1186
|
+
for (const candidate of candidates) if (isUsableWorkspacePath(candidate)) return candidate;
|
|
1187
|
+
return preferredPath ?? path.worktree ?? path.directory;
|
|
1188
|
+
}
|
|
1189
|
+
function isUsableWorkspacePath(value) {
|
|
1190
|
+
if (typeof value !== "string") return false;
|
|
1191
|
+
const normalized = value.trim();
|
|
1192
|
+
return normalized.length > 0 && normalized !== "/" && normalized !== "\\" && isAbsolute(normalized);
|
|
1193
|
+
}
|
|
1075
1194
|
async function resolveOpenCodeConfigFilePath(configPath) {
|
|
1076
1195
|
try {
|
|
1077
1196
|
return (await stat(configPath)).isDirectory() ? join(configPath, "opencode.json") : configPath;
|
|
@@ -1603,7 +1722,10 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
1603
1722
|
const listLspUseCase = new ListLspUseCase(sessionRepo, opencodeClient);
|
|
1604
1723
|
const listMcpUseCase = new ListMcpUseCase(sessionRepo, opencodeClient);
|
|
1605
1724
|
const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
|
|
1606
|
-
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo,
|
|
1725
|
+
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, {
|
|
1726
|
+
status: config.openrouter.configured ? "configured" : "not_configured",
|
|
1727
|
+
model: config.openrouter.configured ? config.openrouter.model : null
|
|
1728
|
+
});
|
|
1607
1729
|
const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
|
|
1608
1730
|
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
1609
1731
|
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger, foregroundSessionTracker);
|
|
@@ -2636,32 +2758,26 @@ var VARIANT_ORDER = [
|
|
|
2636
2758
|
];
|
|
2637
2759
|
function presentStatusMessage(input, copy = BOT_COPY) {
|
|
2638
2760
|
const layout = getStatusLayoutCopy(copy);
|
|
2639
|
-
|
|
2761
|
+
return presentStatusSections([
|
|
2640
2762
|
presentStatusPlainSection(layout.overviewTitle, presentStatusPlainOverviewLines(input, copy, layout)),
|
|
2641
2763
|
presentStatusPlainSection(layout.workspaceTitle, presentStatusPlainWorkspaceLines(input, copy, layout)),
|
|
2642
2764
|
presentStatusPlainSection(layout.pluginsTitle, presentStatusPlainPluginLines(input, copy, layout)),
|
|
2643
2765
|
presentStatusPlainSection(layout.mcpTitle, presentStatusPlainMcpLines(input, copy, layout)),
|
|
2644
2766
|
presentStatusPlainSection(layout.lspTitle, presentStatusPlainLspLines(input, copy, layout))
|
|
2645
|
-
];
|
|
2646
|
-
return presentStatusSections(layout.pageTitle, sections);
|
|
2767
|
+
]);
|
|
2647
2768
|
}
|
|
2648
2769
|
function presentStatusMarkdownMessage(input, copy = BOT_COPY) {
|
|
2649
2770
|
const layout = getStatusLayoutCopy(copy);
|
|
2650
|
-
|
|
2771
|
+
return presentStatusSections([
|
|
2651
2772
|
presentStatusMarkdownSection(layout.overviewTitle, presentStatusMarkdownOverviewLines(input, copy, layout)),
|
|
2652
2773
|
presentStatusMarkdownSection(layout.workspaceTitle, presentStatusMarkdownWorkspaceLines(input, copy, layout)),
|
|
2653
2774
|
presentStatusMarkdownSection(layout.pluginsTitle, presentStatusMarkdownPluginLines(input, copy, layout)),
|
|
2654
2775
|
presentStatusMarkdownSection(layout.mcpTitle, presentStatusMarkdownMcpLines(input, copy, layout)),
|
|
2655
2776
|
presentStatusMarkdownSection(layout.lspTitle, presentStatusMarkdownLspLines(input, copy, layout))
|
|
2656
|
-
];
|
|
2657
|
-
return presentStatusSections(`# ${layout.pageTitle}`, sections);
|
|
2777
|
+
]);
|
|
2658
2778
|
}
|
|
2659
|
-
function presentStatusSections(
|
|
2660
|
-
return [
|
|
2661
|
-
title,
|
|
2662
|
-
"",
|
|
2663
|
-
...sections.flatMap((section, index) => index === 0 ? [section] : ["", section])
|
|
2664
|
-
].join("\n");
|
|
2779
|
+
function presentStatusSections(sections) {
|
|
2780
|
+
return sections.flatMap((section, index) => index === 0 ? [section] : ["", section]).join("\n");
|
|
2665
2781
|
}
|
|
2666
2782
|
function presentStatusPlainSection(title, lines) {
|
|
2667
2783
|
return [title, ...lines].join("\n");
|
|
@@ -2670,7 +2786,7 @@ function presentStatusMarkdownSection(title, lines) {
|
|
|
2670
2786
|
return [`## ${title}`, ...lines].join("\n");
|
|
2671
2787
|
}
|
|
2672
2788
|
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
|
|
2789
|
+
const lines = [presentPlainStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentPlainStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition, layout))];
|
|
2674
2790
|
if (input.health.status === "error") return [
|
|
2675
2791
|
...lines,
|
|
2676
2792
|
...presentStatusPlainErrorDetailLines(input.health.error, copy, layout),
|
|
@@ -2683,7 +2799,7 @@ function presentStatusPlainOverviewLines(input, copy, layout) {
|
|
|
2683
2799
|
];
|
|
2684
2800
|
}
|
|
2685
2801
|
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
|
|
2802
|
+
const lines = [presentMarkdownStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentMarkdownStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition, layout))];
|
|
2687
2803
|
if (input.health.status === "error") return [
|
|
2688
2804
|
...lines,
|
|
2689
2805
|
...presentStatusMarkdownErrorDetailLines(input.health.error, copy, layout),
|
|
@@ -2783,8 +2899,9 @@ function splitStatusLines(text) {
|
|
|
2783
2899
|
function formatHealthBadge(healthy, layout) {
|
|
2784
2900
|
return healthy ? "🟢" : layout.errorStatus;
|
|
2785
2901
|
}
|
|
2786
|
-
function formatVoiceRecognitionBadge(
|
|
2787
|
-
|
|
2902
|
+
function formatVoiceRecognitionBadge(status, layout) {
|
|
2903
|
+
if (status.status === "configured") return status.model ? `\uD83D\uDFE1 ${layout.voiceRecognitionConfiguredLabel} (${status.model})` : `\uD83D\uDFE1 ${layout.voiceRecognitionConfiguredLabel}`;
|
|
2904
|
+
return `\u26AA ${layout.voiceRecognitionNotConfiguredLabel}`;
|
|
2788
2905
|
}
|
|
2789
2906
|
function formatLspStatusBadge(status) {
|
|
2790
2907
|
switch (status.status) {
|
|
@@ -2818,7 +2935,7 @@ function getMcpStatusDetailLines(status, copy, layout) {
|
|
|
2818
2935
|
}
|
|
2819
2936
|
function formatMcpStatusNotes(status, copy, layout) {
|
|
2820
2937
|
switch (status.status) {
|
|
2821
|
-
case "connected": return
|
|
2938
|
+
case "connected": return null;
|
|
2822
2939
|
case "disabled": return null;
|
|
2823
2940
|
case "needs_auth": return copy.mcp.needsAuth;
|
|
2824
2941
|
case "failed": return status.error;
|
|
@@ -2849,14 +2966,14 @@ function getStatusLayoutCopy(copy) {
|
|
|
2849
2966
|
noPluginsMessage: "No plugins configured in the OpenCode config.",
|
|
2850
2967
|
noneStatus: "⚪",
|
|
2851
2968
|
openCodeVersionLabel: "OpenCode Version",
|
|
2852
|
-
okLabel: "OK",
|
|
2853
2969
|
overviewTitle: "🖥️ Overview",
|
|
2854
|
-
pageTitle: "📊 Service Status",
|
|
2855
2970
|
pluginsTitle: "🧩 Plugins",
|
|
2856
2971
|
rootLabel: "Root",
|
|
2857
2972
|
statusLabel: "Status",
|
|
2858
2973
|
tbotVersionLabel: "opencode-tbot Version",
|
|
2974
|
+
voiceRecognitionConfiguredLabel: "configured",
|
|
2859
2975
|
voiceRecognitionLabel: "Voice Recognition",
|
|
2976
|
+
voiceRecognitionNotConfiguredLabel: "not configured",
|
|
2860
2977
|
workspaceTitle: "📁 Workspace"
|
|
2861
2978
|
};
|
|
2862
2979
|
return {
|
|
@@ -2874,14 +2991,14 @@ function getStatusLayoutCopy(copy) {
|
|
|
2874
2991
|
noPluginsMessage: "当前 OpenCode 配置中未配置插件。",
|
|
2875
2992
|
noneStatus: "⚪",
|
|
2876
2993
|
openCodeVersionLabel: "OpenCode版本",
|
|
2877
|
-
okLabel: "正常",
|
|
2878
2994
|
overviewTitle: "🖥️ 概览",
|
|
2879
|
-
pageTitle: "📊 服务状态",
|
|
2880
2995
|
pluginsTitle: "🧩 插件",
|
|
2881
2996
|
rootLabel: "根目录",
|
|
2882
2997
|
statusLabel: "状态",
|
|
2883
2998
|
tbotVersionLabel: "opencode-tbot版本",
|
|
2999
|
+
voiceRecognitionConfiguredLabel: "已配置",
|
|
2884
3000
|
voiceRecognitionLabel: "语音识别",
|
|
3001
|
+
voiceRecognitionNotConfiguredLabel: "未配置",
|
|
2885
3002
|
workspaceTitle: "📁 工作区"
|
|
2886
3003
|
};
|
|
2887
3004
|
}
|