oh-langfuse 0.1.59 → 0.1.61

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.
@@ -1,20 +1,20 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import { spawnSync } from "node:child_process";
4
- import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
5
-
6
- const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
7
- const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
8
-
9
- function normalizeUserId(v) {
10
- return String(v || "").trim();
11
- }
12
-
13
- function assertValidUserId(userId) {
14
- if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
15
- throw new Error(`User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`);
16
- }
17
- }
3
+ import { spawnSync } from "node:child_process";
4
+ import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
5
+
6
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
7
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
8
+
9
+ function normalizeUserId(v) {
10
+ return String(v || "").trim();
11
+ }
12
+
13
+ function assertValidUserId(userId) {
14
+ if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
15
+ throw new Error(`User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`);
16
+ }
17
+ }
18
18
 
19
19
  function parseArgs(argv) {
20
20
  const args = {};
@@ -31,7 +31,7 @@ function parseArgs(argv) {
31
31
  }
32
32
 
33
33
  function runNodeScript(scriptPath, extraArgs = []) {
34
- const r = spawnSync(process.execPath, [scriptPath, ...extraArgs], { stdio: "inherit" });
34
+ const r = spawnSync(process.execPath, [scriptPath, ...extraArgs], { stdio: "inherit", encoding: "utf8" });
35
35
  if (r.status !== 0) process.exit(r.status ?? 1);
36
36
  }
37
37
 
@@ -42,12 +42,12 @@ function main() {
42
42
  args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
43
43
  const secretKey =
44
44
  args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
45
- const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
46
- const userId = normalizeUserId(args.userId || args.userid || "");
47
- if (!userId) {
48
- throw new Error("Missing userId. Run with --userId=your-id.");
49
- }
50
- assertValidUserId(userId);
45
+ const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
46
+ const userId = normalizeUserId(args.userId || args.userid || "");
47
+ if (!userId) {
48
+ throw new Error("Missing userId. Run with --userId=your-id.");
49
+ }
50
+ assertValidUserId(userId);
51
51
 
52
52
  // 1) 先执行 setup:默认会写入 Windows 用户级 LANGFUSE_*(从桌面/开始菜单启动 OpenCode 也能读到)
53
53
  const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
@@ -89,7 +89,7 @@ function main() {
89
89
 
90
90
  console.log(`启动 OpenCode:${resolved} ${cmdArgs.join(" ")}`.trim() + ". userId: " + (userId || "<none>"));
91
91
  const useShell = process.platform === "win32" && /\.(cmd|bat)$/i.test(resolved);
92
- const r = spawnSync(resolved, cmdArgs, { stdio: "inherit", env, shell: useShell });
92
+ const r = spawnSync(resolved, cmdArgs, { stdio: "inherit", encoding: "utf8", env, shell: useShell });
93
93
  if (r.error?.code === "ENOENT") {
94
94
  console.error(
95
95
  `无法执行 "${resolved}"。若这是 .cmd,请尝试:npm run opencode:langfuse:run -- --cmd=${path.join(
@@ -510,45 +510,45 @@ function getPatchedLangfuseDistIndexJs() {
510
510
  " const processor = new LangfuseSpanProcessor({ publicKey, secretKey, baseUrl, environment });",
511
511
  " const spanProcessors = [processor, createMetricsSpanProcessor(userId)];",
512
512
  " if (userId) spanProcessors.push(createUserIdSpanProcessor(userId));",
513
- "",
514
- " const sdk = new NodeSDK({ spanProcessors });",
515
- " const sdkStartPromise = Promise.resolve().then(() => sdk.start()).catch((err) => {",
516
- ' log("warn", `OTEL SDK start failed: ${err?.message ?? err}`);',
517
- " });",
518
- " const getMetricsTracer = () => trace.getTracer('oh-langfuse-opencode-metrics');",
519
- " const knownSkillNames = await collectKnownSkillNames();",
520
- " const startupSkillUsages = detectOpencodeSkillUsages(process.argv.join('\\n'), knownSkillNames);",
513
+ "",
514
+ " const sdk = new NodeSDK({ spanProcessors });",
515
+ " const sdkStartPromise = Promise.resolve().then(() => sdk.start()).catch((err) => {",
516
+ ' log("warn", `OTEL SDK start failed: ${err?.message ?? err}`);',
517
+ " });",
518
+ " const getMetricsTracer = () => trace.getTracer('oh-langfuse-opencode-metrics');",
519
+ " const knownSkillNames = await collectKnownSkillNames();",
520
+ " const startupSkillUsages = detectOpencodeSkillUsages(process.argv.join('\\n'), knownSkillNames);",
521
521
  " const messageTextById = new Map();",
522
522
  " const skillUsagesByMessageId = new Map();",
523
523
  " const skillUsagesBySessionId = new Map();",
524
524
  " const startupSkillSessionIds = new Set();",
525
525
  " const toolCallIdsByMessageId = new Map();",
526
526
  " const toolCallIdsBySessionId = new Map();",
527
- " const toolResultIdsByMessageId = new Map();",
528
- " const toolResultIdsBySessionId = new Map();",
529
- " const emittedMessageIds = new Set();",
530
- "",
527
+ " const toolResultIdsByMessageId = new Map();",
528
+ " const toolResultIdsBySessionId = new Map();",
529
+ " const emittedMessageIds = new Set();",
530
+ "",
531
531
  ' log("info", `OTEL tracing initialized -> ${baseUrl}`);',
532
532
  ' if (userId) log("info", `LANGFUSE userId configured -> ${userId}`);',
533
533
  ' if (knownSkillNames.length) log("info", `OpenCode skills discovered -> ${knownSkillNames.length}`);',
534
534
  "",
535
- " let shutdownStarted = false;",
536
- " const flush = async (reason) => {",
537
- " try {",
538
- ' log("info", `Flushing OTEL spans on ${reason}`);',
539
- " await sdkStartPromise;",
540
- " await processor.forceFlush();",
535
+ " let shutdownStarted = false;",
536
+ " const flush = async (reason) => {",
537
+ " try {",
538
+ ' log("info", `Flushing OTEL spans on ${reason}`);',
539
+ " await sdkStartPromise;",
540
+ " await processor.forceFlush();",
541
541
  " } catch (error) {",
542
542
  ' log("warn", `OTEL forceFlush failed on ${reason}: ${error?.message ?? error}`);',
543
543
  " }",
544
544
  " };",
545
545
  " const shutdown = async (reason) => {",
546
- " if (shutdownStarted) return;",
547
- " shutdownStarted = true;",
548
- " try {",
549
- ' log("info", `Shutting down OTEL SDK on ${reason}`);',
550
- " await sdkStartPromise;",
551
- " await sdk.shutdown();",
546
+ " if (shutdownStarted) return;",
547
+ " shutdownStarted = true;",
548
+ " try {",
549
+ ' log("info", `Shutting down OTEL SDK on ${reason}`);',
550
+ " await sdkStartPromise;",
551
+ " await sdk.shutdown();",
552
552
  " } catch (error) {",
553
553
  ' log("warn", `OTEL shutdown failed on ${reason}: ${error?.message ?? error}`);',
554
554
  " }",
@@ -586,7 +586,7 @@ function getPatchedLangfuseDistIndexJs() {
586
586
  " }",
587
587
  " set.add(activity.toolCallId || `${kind}:${set.size + 1}`);",
588
588
  " };",
589
- " const recordInteractionMetric = async (event) => {",
589
+ " const recordInteractionMetric = async (event) => {",
590
590
  " const payload = eventPayload(event);",
591
591
  " const part = eventPart(event);",
592
592
  " const partType = part?.type ?? '';",
@@ -617,8 +617,8 @@ function getPatchedLangfuseDistIndexJs() {
617
617
  " const tokenMetrics = tokenMetricsFromPart(part);",
618
618
  " const total = tokenMetrics.total ?? (tokenMetrics.input !== undefined && tokenMetrics.output !== undefined ? tokenMetrics.input + tokenMetrics.output : undefined);",
619
619
  " const tokenAvailable = [tokenMetrics.input, tokenMetrics.output, total, tokenMetrics.cacheRead, tokenMetrics.reasoning].some((value) => value !== undefined);",
620
- " await sdkStartPromise;",
621
- " const span = getMetricsTracer().startSpan('OpenCode Agent Turn');",
620
+ " await sdkStartPromise;",
621
+ " const span = getMetricsTracer().startSpan('OpenCode Agent Turn');",
622
622
  " const text = messageTextById.get(messageId) || '';",
623
623
  " const skillUsages = dedupeSkillUsages([...(skillUsagesByMessageId.get(messageId) ?? []), ...(skillUsagesBySessionId.get(sessionId) ?? [])]);",
624
624
  " const interactionId = `opencode:${userId || \"unknown\"}:${sessionId || \"unknown\"}:${messageId}`;",
@@ -674,9 +674,9 @@ function getPatchedLangfuseDistIndexJs() {
674
674
  ' log("warn", "OpenTelemetry experimental feature is disabled in Opencode config - tracing disabled");',
675
675
  " }",
676
676
  " },",
677
- " event: async ({ event }) => {",
678
- " const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
679
- " await recordInteractionMetric(event);",
677
+ " event: async ({ event }) => {",
678
+ " const eventType = pickEventString(event?.name, event?.type, eventPayload(event)?.name, eventPayload(event)?.type);",
679
+ " await recordInteractionMetric(event);",
680
680
  ' if (eventType === "session.idle" || eventType === "message.updated" || eventType === "message.part.updated" || eventType === "session.updated" || eventType === "session.idle.1" || eventType === "message.updated.1" || eventType === "message.part.updated.1" || eventType === "session.updated.1") {',
681
681
  " await flush(eventType);",
682
682
  " }",
@@ -719,7 +719,7 @@ function writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, u
719
719
  cmd.push(" exit /b %ERRORLEVEL%");
720
720
  cmd.push(")");
721
721
  cmd.push("opencode %*");
722
- fs.writeFileSync(p, cmd.join("\r\n") + "\r\n", "utf8");
722
+ fs.writeFileSync(p, "\ufeff" + cmd.join("\r\n") + "\r\n", "utf8");
723
723
  return p;
724
724
  }
725
725
 
@@ -754,7 +754,7 @@ function writeAutoUpdateHelper(target) {
754
754
  "exit /b 0",
755
755
  ""
756
756
  ];
757
- fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
757
+ fs.writeFileSync(helper, "\ufeff" + lines.join(os.EOL), "utf8");
758
758
  return helper;
759
759
  }
760
760
 
@@ -802,7 +802,7 @@ function writeWindowsUpdateCheckScript(dir) {
802
802
  "}",
803
803
  ""
804
804
  ].join("\r\n");
805
- fs.writeFileSync(p, ps, "utf8");
805
+ fs.writeFileSync(p, "\ufeff" + ps, "utf8");
806
806
  return p;
807
807
  }
808
808
 
@@ -820,7 +820,7 @@ function writeOpencodeCommandShim(opencodeDir, { publicKey, secretKey, baseUrl,
820
820
  cmd.push(windowsAutoUpdateCommand("opencode"));
821
821
  cmd.push(`call ${cmdQuote(realOpencodeCli)} %*`);
822
822
  cmd.push("exit /b %ERRORLEVEL%");
823
- fs.writeFileSync(shim, cmd.join("\r\n") + "\r\n", "utf8");
823
+ fs.writeFileSync(shim, "\ufeff" + cmd.join("\r\n") + "\r\n", "utf8");
824
824
  return { shim, shimDir };
825
825
  }
826
826
 
@@ -925,17 +925,32 @@ function printNpmDiagnostics() {
925
925
  } catch (_) {}
926
926
  }
927
927
 
928
- function runNpmInstallCapture(npmArgs) {
928
+ /**
929
+ * 重置终端状态,防止子进程异常退出后遗留 ANSI 转义序列。
930
+ */
931
+ function resetTerminalState() {
932
+ if (process.stdout.isTTY) {
933
+ // \x1b[0m - 重置所有颜色和属性
934
+ // \x1b[?25h - 显示光标(防止 npm 隐藏光标后未恢复)
935
+ // \x1b[r - 重置滚动区域
936
+ process.stdout.write('\x1b[0m\x1b[?25h\x1b[r');
937
+ }
938
+ }
939
+
940
+ function runNpmInstallCapture(npmArgs, options = {}) {
929
941
  return new Promise((resolve) => {
930
942
  const cliJs = getNpmCliJsPath();
931
943
  const npmExe = getNpmExecutable();
932
944
  const command = fs.existsSync(cliJs) ? process.execPath : npmExe;
933
945
  const args = fs.existsSync(cliJs) ? [cliJs, ...npmArgs] : npmArgs;
934
946
  const useShell = process.platform === "win32" && !fs.existsSync(cliJs) && /\.(cmd|bat)$/i.test(npmExe);
947
+ const timeoutMs = options.timeoutMs ?? 600000; // 默认 10 分钟超时
948
+
935
949
  const child = spawn(command, args, {
936
950
  encoding: "utf8",
937
951
  shell: useShell,
938
- windowsHide: true
952
+ windowsHide: true,
953
+ timeout: timeoutMs
939
954
  });
940
955
  let stdout = "";
941
956
  let stderr = "";
@@ -960,12 +975,23 @@ function runNpmInstallCapture(npmArgs) {
960
975
  });
961
976
  child.on("error", (error) => {
962
977
  clearInterval(timer);
978
+ resetTerminalState();
963
979
  resolve({ status: null, stdout, stderr, error });
964
980
  });
965
981
  child.on("close", (status) => {
966
982
  clearInterval(timer);
983
+ if (status !== 0) {
984
+ resetTerminalState();
985
+ }
967
986
  resolve({ status, stdout, stderr });
968
987
  });
988
+ child.on("timeout", () => {
989
+ clearInterval(timer);
990
+ console.error("");
991
+ console.error(`npm install timed out after ${Math.round(timeoutMs / 1000)}s, killing process...`);
992
+ resetTerminalState();
993
+ child.kill('SIGTERM');
994
+ });
969
995
  });
970
996
  }
971
997
 
@@ -974,29 +1000,78 @@ function npmInstallLooksLikeMissingVersion(result) {
974
1000
  return text.includes("etarget") || text.includes("notarget") || text.includes("no matching version found");
975
1001
  }
976
1002
 
977
- function isOfficialNpmRegistry(registry) {
978
- return /^https?:\/\/registry\.npmjs\.org\/?$/i.test(String(registry || "").trim());
979
- }
980
-
981
- const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org/";
982
-
983
- async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
984
- const npmArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save", "--audit=false", "--fund=false"];
985
- const effectiveRegistry = npmRegistry || OFFICIAL_NPM_REGISTRY;
986
- npmArgs.push("--registry", effectiveRegistry);
987
- const cliJs = getNpmCliJsPath();
988
- console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
989
- console.log(`使用 npm registry:${effectiveRegistry}`);
990
- console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
991
- let r = await runNpmInstallCapture(npmArgs);
992
- if (!r.error && r.status !== 0 && npmInstallLooksLikeMissingVersion(r) && !isOfficialNpmRegistry(effectiveRegistry)) {
993
- console.error("");
994
- console.error(`npm registry appears to be missing a package version. Retrying with ${OFFICIAL_NPM_REGISTRY} ...`);
995
- const retryArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save", "--audit=false", "--fund=false", "--registry", OFFICIAL_NPM_REGISTRY];
996
- r = await runNpmInstallCapture(retryArgs);
997
- if (!r.error && r.status === 0) return;
998
- }
1003
+ function isOfficialNpmRegistry(registry) {
1004
+ return /^https?:\/\/registry\.npmjs\.org\/?$/i.test(String(registry || "").trim());
1005
+ }
1006
+
1007
+ const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org/";
1008
+ const DEFAULT_NPM_REGISTRY = "https://registry.npmmirror.com/";
1009
+
1010
+ function isChinaNetwork() {
1011
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
1012
+ return tz.includes("Shanghai") || tz.includes("Chongqing") || tz.includes("Harbin");
1013
+ }
1014
+
1015
+ async function checkInstalledVersion(opencodeDir, pkgName) {
1016
+ const pkgJsonPath = path.join(opencodeDir, "node_modules", pkgName, "package.json");
1017
+ if (!fs.existsSync(pkgJsonPath)) return null;
1018
+ try {
1019
+ const txt = stripBom(fs.readFileSync(pkgJsonPath, "utf8"));
1020
+ if (!txt.trim()) return null;
1021
+ const installed = parseJsonRelaxed(txt, pkgJsonPath);
1022
+ return installed?.version || null;
1023
+ } catch {
1024
+ return null;
1025
+ }
1026
+ }
1027
+
1028
+ async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
1029
+ const installedVersion = await checkInstalledVersion(opencodeDir, pkgName);
1030
+ const currentVersion = packageJson.version;
1031
+
1032
+ if (installedVersion === currentVersion) {
1033
+ console.log(`插件版本 ${currentVersion} 已安装,跳过 npm install ...`);
1034
+ return;
1035
+ }
1036
+
1037
+ if (installedVersion) {
1038
+ console.log(`插件版本 ${installedVersion} 已安装,升级到 ${currentVersion} ...`);
1039
+ }
1040
+
1041
+ const isOfficial = npmRegistry && isOfficialNpmRegistry(npmRegistry);
1042
+ const effectiveRegistry = npmRegistry || (isChinaNetwork() ? DEFAULT_NPM_REGISTRY : OFFICIAL_NPM_REGISTRY);
1043
+
1044
+ const npmArgs = [
1045
+ "install", pkgName, "--prefix", opencodeDir,
1046
+ "--package-lock=false", "--no-save",
1047
+ "--audit=false", "--fund=false",
1048
+ "--prefer-offline",
1049
+ "--registry", effectiveRegistry
1050
+ ];
1051
+
1052
+ const cliJs = getNpmCliJsPath();
1053
+ console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
1054
+ console.log(`使用 npm registry:${effectiveRegistry}`);
1055
+ console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
1056
+
1057
+ let r = await runNpmInstallCapture(npmArgs, { timeoutMs: 600000 });
1058
+
1059
+ if (!r.error && r.status !== 0 && npmInstallLooksLikeMissingVersion(r) && !isOfficialNpmRegistry(effectiveRegistry)) {
1060
+ console.error("");
1061
+ console.error(`npm registry appears to be missing a package version. Retrying with ${OFFICIAL_NPM_REGISTRY} ...`);
1062
+ const retryArgs = [
1063
+ "install", pkgName, "--prefix", opencodeDir,
1064
+ "--package-lock=false", "--no-save",
1065
+ "--audit=false", "--fund=false",
1066
+ "--prefer-offline",
1067
+ "--registry", OFFICIAL_NPM_REGISTRY
1068
+ ];
1069
+ r = await runNpmInstallCapture(retryArgs, { timeoutMs: 600000 });
1070
+ if (!r.error && r.status === 0) return;
1071
+ }
1072
+
999
1073
  if (!r.error && r.status === 0) return;
1074
+
1000
1075
  printNpmDiagnostics();
1001
1076
  const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
1002
1077
  throw new Error(
@@ -1011,12 +1086,13 @@ function setWindowsUserEnv({ publicKey, secretKey, baseUrl }) {
1011
1086
  `[Environment]::SetEnvironmentVariable('LANGFUSE_PUBLIC_KEY', ${psQuote(publicKey)}, 'User');`,
1012
1087
  `[Environment]::SetEnvironmentVariable('LANGFUSE_SECRET_KEY', ${psQuote(secretKey)}, 'User');`,
1013
1088
  `[Environment]::SetEnvironmentVariable('LANGFUSE_BASEURL', ${psQuote(baseUrl)}, 'User');`
1014
- ].join(" ");
1015
- const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
1089
+ ].join(" ");
1090
+ const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
1016
1091
  if (r.status !== 0) {
1017
- throw new Error("写入用户级环境变量失败(PowerShell SetEnvironmentVariable)。");
1092
+ throw new Error("写入用户级环境变量失败。");
1018
1093
  }
1019
1094
  }
1095
+ }
1020
1096
 
1021
1097
  function prependWindowsUserPath(dir) {
1022
1098
  if (process.platform !== "win32") return false;
@@ -1033,7 +1109,7 @@ function prependWindowsUserPath(dir) {
1033
1109
  " [Environment]::SetEnvironmentVariable('Path', $next, 'User');",
1034
1110
  "}"
1035
1111
  ].join(" ");
1036
- const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
1112
+ const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
1037
1113
  if (r.status !== 0) {
1038
1114
  throw new Error("写入用户 PATH 失败:无法把 OpenCode Langfuse shim 加入 PATH。");
1039
1115
  }
@@ -1110,7 +1186,7 @@ async function main() {
1110
1186
  throw new Error("缺少参数:--userId=你的工号");
1111
1187
  }
1112
1188
  assertValidUserId(userId);
1113
- const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || "";
1189
+ const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || "";
1114
1190
 
1115
1191
  const home = os.homedir();
1116
1192
  const opencodeDir = path.join(home, ".config", "opencode");
@@ -1213,7 +1289,7 @@ async function main() {
1213
1289
  "$ErrorActionPreference = 'Stop';",
1214
1290
  `[Environment]::SetEnvironmentVariable('LANGFUSE_USER_ID', ${psQuote(userId)}, 'User');`
1215
1291
  ].join(" ");
1216
- spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
1292
+ spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
1217
1293
  }
1218
1294
  if (opencodeShim?.shimDir) {
1219
1295
  prependWindowsUserPath(opencodeShim.shimDir);