nolo-cli 0.1.41 → 0.1.43

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.
Files changed (136) hide show
  1. package/README.md +27 -12
  2. package/agent-runtime/agentThread.ts +222 -0
  3. package/agent-runtime/agentThreadAdmission.ts +74 -0
  4. package/agent-runtime/dialogWritePlan.ts +42 -0
  5. package/agent-runtime/hostAdapter.ts +1 -0
  6. package/agent-runtime/index.ts +43 -0
  7. package/agent-runtime/localLoop.ts +31 -2
  8. package/agent-runtime/localToolPolicy.ts +1 -0
  9. package/agent-runtime/localWorkspaceTools.ts +1258 -78
  10. package/agent-runtime/noloWorkspaceTools.ts +20 -1
  11. package/agent-runtime/runtimeToolPolicy.ts +1 -5
  12. package/agent-runtime/runtimeToolSurface.ts +1 -0
  13. package/agentAliases.ts +29 -9
  14. package/agentInternalCommandEntries.ts +11 -3
  15. package/agentMachineCommands.ts +1 -0
  16. package/agentRecordCommands.ts +2 -2
  17. package/agentRecordHelpers.ts +59 -6
  18. package/agentRunCommand.ts +84 -19
  19. package/ai/agent/buildSystemPrompt.ts +14 -0
  20. package/ai/agent/cliExecutor.ts +202 -43
  21. package/ai/agent/createAgentSchema.ts +2 -2
  22. package/ai/agent/liveAudioModel.ts +1 -1
  23. package/ai/agent/streamAgentChatTurn.ts +154 -20
  24. package/ai/agent/streamAgentChatTurnUtils.ts +63 -0
  25. package/ai/ai.locale.ts +70 -0
  26. package/ai/chat/inlineImageUrlsForCustomProvider.ts +7 -2
  27. package/ai/chat/sendOpenAICompletionsRequest.ts +25 -1
  28. package/ai/llm/kimi.ts +0 -1
  29. package/ai/llm/openrouterModels.ts +27 -2
  30. package/ai/memory/policy.ts +49 -0
  31. package/ai/memory/queryShared.ts +10 -4
  32. package/ai/memory/recentRelationshipRecap.ts +4 -0
  33. package/ai/memory/remember.ts +48 -104
  34. package/ai/memory/scope.ts +74 -0
  35. package/ai/memory/types.ts +6 -0
  36. package/ai/memory/understanding.ts +10 -18
  37. package/ai/policy/runtimePolicy.ts +95 -23
  38. package/ai/tools/agent/agentTools.ts +0 -21
  39. package/ai/tools/agent/startAgentDialogTool.ts +2 -1
  40. package/ai/tools/index.ts +2 -20
  41. package/ai/tools/prepareTools.ts +171 -1
  42. package/ai/tools/readXhsProfileTool.ts +80 -69
  43. package/ai/tools/table/createTableTool.ts +11 -0
  44. package/ai/tools/toolPacks.ts +19 -0
  45. package/ai/types.ts +3 -0
  46. package/app/favorite/deletedFavoriteProjection.ts +31 -0
  47. package/app/hooks/deleteDbKey.ts +24 -3
  48. package/app/i18n/translations/interface.locale.ts +48 -16
  49. package/app/types/appSummary.ts +4 -0
  50. package/app/utils/desktopAgentRuntimeTurnClient.ts +2 -0
  51. package/auth/adminPermissions.ts +64 -4
  52. package/auth/routes.ts +5 -0
  53. package/chat/chat.locale.ts +4 -0
  54. package/chat/dialog/actions/handleSendMessageAction.ts +7 -2
  55. package/chat/dialog/dialogAttachmentCleanup.ts +135 -0
  56. package/chat/dialog/dialogSlice.ts +117 -4
  57. package/chat/messages/activityCompletion.ts +97 -0
  58. package/chat/messages/imagePayloadPersistence.ts +22 -0
  59. package/chat/messages/messageContent.ts +20 -16
  60. package/chat/messages/messageSlice.ts +20 -1
  61. package/chat/messages/toolThunks.ts +32 -11
  62. package/chat/messages/types.ts +8 -1
  63. package/chat/messages/web/toolDisplayName.ts +686 -0
  64. package/cli/agentAliases.ts +29 -9
  65. package/cli/agentInternalCommandEntries.ts +11 -3
  66. package/cli/agentMachineCommands.ts +1 -0
  67. package/cli/agentRecordCommands.ts +2 -2
  68. package/cli/agentRecordHelpers.ts +59 -6
  69. package/cli/agentRunCommand.ts +84 -19
  70. package/cli/client/agentRun.ts +65 -253
  71. package/cli/client/localRuntimeAdapter.ts +179 -53
  72. package/cli/commandRegistry.ts +7 -4
  73. package/cli/dialogAttachmentCleanup.ts +151 -0
  74. package/cli/dialogCommands.ts +163 -61
  75. package/cli/dialogInternalCommandEntries.ts +1 -1
  76. package/cli/docCreateCommands.ts +345 -0
  77. package/cli/docInternalCommandEntries.ts +11 -0
  78. package/cli/docPageHelpers.ts +80 -0
  79. package/cli/docSkillHelpers.ts +67 -0
  80. package/cli/docSpaceHelpers.ts +46 -0
  81. package/cli/internalCommandEntries.ts +4 -0
  82. package/cli/machineCommands.ts +18 -34
  83. package/cli/memoryCommands.ts +253 -0
  84. package/cli/memoryInternalCommandEntries.ts +9 -0
  85. package/cli/qoderUsageProbe.ts +252 -0
  86. package/cli/scriptCommandEntries.ts +0 -3
  87. package/cli/secretScan.ts +31 -0
  88. package/cli/tableCommands.ts +248 -13
  89. package/cli/workflowInternalCommandEntries.ts +2 -3
  90. package/client/agentRun.test.ts +68 -84
  91. package/client/agentRun.ts +65 -253
  92. package/client/localRuntimeAdapter.test.ts +505 -74
  93. package/client/localRuntimeAdapter.ts +179 -53
  94. package/commandRegistry.ts +7 -4
  95. package/connector-experimental/capabilities.ts +10 -9
  96. package/create/space/sidebarVisibleTypes.ts +19 -0
  97. package/dialogAttachmentCleanup.ts +151 -0
  98. package/dialogCommands.ts +163 -61
  99. package/dialogInternalCommandEntries.ts +1 -1
  100. package/docCreateCommands.ts +345 -0
  101. package/docInternalCommandEntries.ts +11 -0
  102. package/docPageHelpers.ts +80 -0
  103. package/docSkillHelpers.ts +67 -0
  104. package/docSpaceHelpers.ts +46 -0
  105. package/integrations/google/models.ts +4 -4
  106. package/integrations/openai/generateOpenAIRequestBody.ts +0 -3
  107. package/integrations/xhs-reader/types.ts +36 -3
  108. package/integrations/xhs-reader/url.ts +24 -1
  109. package/internalCommandEntries.ts +4 -0
  110. package/machineCommands.ts +18 -34
  111. package/memoryCommands.ts +253 -0
  112. package/memoryInternalCommandEntries.ts +9 -0
  113. package/package.json +141 -114
  114. package/qoderUsageProbe.ts +252 -0
  115. package/render/contentIcon/types.ts +167 -0
  116. package/render/page/docSlice.ts +30 -3
  117. package/render/page/types.ts +2 -0
  118. package/render/table/createTableAction.ts +4 -0
  119. package/render/table/tableSlice.ts +61 -0
  120. package/render/table/types.ts +10 -0
  121. package/scriptCommandEntries.ts +0 -3
  122. package/secretScan.ts +31 -0
  123. package/server/handlers/agentRun/types.ts +24 -23
  124. package/tableCommands.ts +248 -13
  125. package/workflowInternalCommandEntries.ts +2 -3
  126. package/ai/tools/agent/taskRunTool.ts +0 -112
  127. package/cli/client/taskRunPrompt.ts +0 -27
  128. package/cli/taskRunCommand.ts +0 -265
  129. package/client/taskRunPrompt.ts +0 -27
  130. package/integrations/xhs-reader/analyze/profileAnalyzer.ts +0 -205
  131. package/integrations/xhs-reader/backends/playwrightProfileCollector.ts +0 -624
  132. package/integrations/xhs-reader/bridge/readXhsProfileWithBridge.ts +0 -227
  133. package/integrations/xhs-reader/client/xhsWebApiClient.ts +0 -229
  134. package/integrations/xhs-reader/normalize.ts +0 -390
  135. package/integrations/xhs-reader/orchestrator.ts +0 -178
  136. package/taskRunCommand.ts +0 -265
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CLI Executor - 通过命令行工具执行 AI 任务
3
3
  *
4
- * 设计为可扩展:当前支持 Copilot CLI、Gemini CLI、Codex CLI、Claude CLI 与 Antigravity CLI。
4
+ * 设计为可扩展:当前支持 Copilot CLI、Gemini CLI、Codex CLI、Claude CLI、Antigravity CLI 与 Qoder CLI。
5
5
  *
6
6
  * 注意:
7
7
  * - CLI provider 与普通 model 路由共享 prompt / model / 最近文本上下文这些能力面
@@ -18,13 +18,14 @@ import { randomUUID } from "node:crypto";
18
18
  import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
19
19
  import { isAbsolute, join, resolve } from "node:path";
20
20
  import { tmpdir } from "node:os";
21
+ import { StringDecoder } from "node:string_decoder";
21
22
  import { resolveLaunchableCodexCommand } from "../../connector-experimental/codexBinary";
22
23
  import { buildCliPrompt } from "./cliPrompt";
23
24
 
24
25
  // ── 类型定义 ──────────────────────────────────────────────────────────────────
25
26
 
26
27
  /** 已支持的 CLI 工具。新增时在此联合类型追加,并在 EXECUTORS 里注册实现 */
27
- export type CliProvider = "copilot" | "gemini" | "codex" | "claude" | "agy";
28
+ export type CliProvider = "copilot" | "gemini" | "codex" | "claude" | "agy" | "qoder";
28
29
 
29
30
  /**
30
31
  * CLI provider 图片输入。
@@ -71,6 +72,16 @@ type CliSessionMessage = {
71
72
  };
72
73
 
73
74
  const DEFAULT_AGY_TIMEOUT_MS = 600_000;
75
+ const BUFFERED_STREAMING_PROVIDERS = new Set<CliProvider>([
76
+ "codex",
77
+ "claude",
78
+ "agy",
79
+ "qoder",
80
+ ]);
81
+
82
+ export function isBufferedCliStreamingProvider(provider: CliProvider): boolean {
83
+ return BUFFERED_STREAMING_PROVIDERS.has(provider);
84
+ }
74
85
 
75
86
  export interface CliSessionHandle {
76
87
  sessionId: string;
@@ -159,12 +170,39 @@ function normalizeProxyEnv(env: Record<string, string | undefined>): Record<stri
159
170
  }
160
171
 
161
172
  function buildCliProcessEnv(extraEnv?: Record<string, string | undefined>) {
173
+ const baseEnv = {
174
+ ...process.env,
175
+ ...extraEnv,
176
+ NO_COLOR: "1",
177
+ };
178
+
179
+ // Enhance PATH on macOS so GUI-launched app can find brew/local tools
180
+ if (process.platform === "darwin") {
181
+ const defaultMacPaths = [
182
+ `${process.env.HOME || ""}/.local/bin`,
183
+ "/opt/homebrew/bin",
184
+ "/opt/homebrew/sbin",
185
+ "/usr/local/bin",
186
+ "/usr/local/sbin",
187
+ `${process.env.HOME || ""}/.bun/bin`,
188
+ `${process.env.HOME || ""}/Library/pnpm`,
189
+ `${process.env.HOME || ""}/.npm-global/bin`,
190
+ `${process.env.HOME || ""}/bin`,
191
+ ];
192
+ const existingPath = baseEnv.PATH || "/usr/bin:/bin:/usr/sbin:/sbin";
193
+ const paths = existingPath.split(":");
194
+ for (const p of defaultMacPaths) {
195
+ if (p && !paths.includes(p)) {
196
+ paths.unshift(p); // Put brew/local paths at the front
197
+ }
198
+ }
199
+ baseEnv.PATH = paths.join(":");
200
+ }
201
+
162
202
  return Object.fromEntries(
163
- Object.entries(normalizeProxyEnv({
164
- ...process.env,
165
- ...extraEnv,
166
- NO_COLOR: "1",
167
- })).filter((entry): entry is [string, string] => typeof entry[1] === "string")
203
+ Object.entries(normalizeProxyEnv(baseEnv)).filter(
204
+ (entry): entry is [string, string] => typeof entry[1] === "string"
205
+ )
168
206
  );
169
207
  }
170
208
 
@@ -178,6 +216,8 @@ function getCliProviderLabel(provider: CliProvider): string {
178
216
  return "Copilot CLI";
179
217
  case "agy":
180
218
  return "Antigravity CLI";
219
+ case "qoder":
220
+ return "Qoder CLI";
181
221
  case "gemini":
182
222
  return "Gemini CLI";
183
223
  }
@@ -194,7 +234,7 @@ function supportsModel(provider: CliProvider): boolean {
194
234
  }
195
235
 
196
236
  function supportsReasoningEffort(provider: CliProvider): boolean {
197
- return provider === "claude" || provider === "copilot";
237
+ return provider === "claude" || provider === "copilot" || provider === "qoder";
198
238
  }
199
239
 
200
240
  function shouldPassClaudeModel(options: CliExecuteOptions): boolean {
@@ -626,8 +666,8 @@ function executeCodex(
626
666
  env: buildCliProcessEnv(options.env),
627
667
  stdio: ["ignore", "pipe", "pipe"],
628
668
  });
629
- let stdout = "";
630
- let stderr = "";
669
+ const stdout = createUtf8Collector();
670
+ const stderr = createUtf8Collector();
631
671
  let timer: ReturnType<typeof setTimeout> | undefined;
632
672
 
633
673
  if (timeout > 0) {
@@ -638,22 +678,24 @@ function executeCodex(
638
678
  }
639
679
 
640
680
  proc.stdout.on("data", (data: Buffer) => {
641
- stdout += data.toString();
681
+ stdout.write(data);
642
682
  });
643
683
 
644
684
  proc.stderr.on("data", (data: Buffer) => {
645
- stderr += data.toString();
685
+ stderr.write(data);
646
686
  });
647
687
 
648
688
  proc.on("close", (code) => {
649
689
  if (timer) clearTimeout(timer);
690
+ const stdoutText = stdout.end();
691
+ const stderrText = stderr.end();
650
692
  if (code !== 0 && code !== null) {
651
693
  rmSync(tempDir, { recursive: true, force: true });
652
- reject(new Error(`Codex CLI exited with code ${code}\nstderr: ${stderr}`));
694
+ reject(new Error(`Codex CLI exited with code ${code}\nstderr: ${stderrText}`));
653
695
  return;
654
696
  }
655
697
 
656
- let text = stdout.trim();
698
+ let text = stdoutText.trim();
657
699
  try {
658
700
  const lastMessage = readFileSync(outputFile, "utf8").trim();
659
701
  if (lastMessage) text = lastMessage;
@@ -665,7 +707,7 @@ function executeCodex(
665
707
 
666
708
  resolve({
667
709
  text,
668
- raw: stdout,
710
+ raw: stdoutText,
669
711
  elapsed: Date.now() - start,
670
712
  });
671
713
  });
@@ -716,8 +758,8 @@ function executeClaude(
716
758
  env: buildCliProcessEnv(options.env),
717
759
  stdio: ["ignore", "pipe", "pipe"],
718
760
  });
719
- let stdout = "";
720
- let stderr = "";
761
+ const stdout = createUtf8Collector();
762
+ const stderr = createUtf8Collector();
721
763
  let timer: ReturnType<typeof setTimeout> | undefined;
722
764
 
723
765
  if (timeout > 0) {
@@ -728,23 +770,25 @@ function executeClaude(
728
770
  }
729
771
 
730
772
  proc.stdout.on("data", (data: Buffer) => {
731
- stdout += data.toString();
773
+ stdout.write(data);
732
774
  });
733
775
 
734
776
  proc.stderr.on("data", (data: Buffer) => {
735
- stderr += data.toString();
777
+ stderr.write(data);
736
778
  });
737
779
 
738
780
  proc.on("close", (code) => {
739
781
  if (timer) clearTimeout(timer);
782
+ const stdoutText = stdout.end();
783
+ const stderrText = stderr.end();
740
784
  if (code !== 0 && code !== null) {
741
- reject(new Error(`Claude CLI exited with code ${code}\nstderr: ${stderr}`));
785
+ reject(new Error(`Claude CLI exited with code ${code}\nstderr: ${stderrText}`));
742
786
  return;
743
787
  }
744
788
 
745
789
  resolve({
746
- text: stdout.trim(),
747
- raw: stdout,
790
+ text: stdoutText.trim(),
791
+ raw: stdoutText,
748
792
  elapsed: Date.now() - start,
749
793
  });
750
794
  });
@@ -765,6 +809,35 @@ function tailText(value: string, maxChars = 1200): string {
765
809
  return value.slice(value.length - maxChars);
766
810
  }
767
811
 
812
+ function createUtf8Collector() {
813
+ const decoder = new StringDecoder("utf8");
814
+ let text = "";
815
+ let ended = false;
816
+ const finish = () => {
817
+ if (ended) return "";
818
+ ended = true;
819
+ const chunk = decoder.end();
820
+ if (chunk) text += chunk;
821
+ return chunk;
822
+ };
823
+ return {
824
+ write(data: Buffer): string {
825
+ if (ended) return "";
826
+ const chunk = decoder.write(data);
827
+ text += chunk;
828
+ return chunk;
829
+ },
830
+ finish,
831
+ end(): string {
832
+ finish();
833
+ return text;
834
+ },
835
+ get text() {
836
+ return text;
837
+ },
838
+ };
839
+ }
840
+
768
841
  function terminateCliProcessGroup(
769
842
  proc: ReturnType<typeof spawn>,
770
843
  signal: NodeJS.Signals
@@ -827,8 +900,8 @@ function executeAgy(
827
900
  stdio: ["ignore", "pipe", "pipe"],
828
901
  detached: process.platform !== "win32",
829
902
  });
830
- let stdout = "";
831
- let stderr = "";
903
+ const stdout = createUtf8Collector();
904
+ const stderr = createUtf8Collector();
832
905
  let timer: ReturnType<typeof setTimeout> | undefined;
833
906
  let forceTimer: ReturnType<typeof setTimeout> | undefined;
834
907
  let settled = false;
@@ -846,16 +919,16 @@ function executeAgy(
846
919
  forceTimer = setTimeout(() => {
847
920
  terminateCliProcessGroup(proc, "SIGKILL");
848
921
  }, 3_000);
849
- reject(buildAgyTimeoutError(timeout, stdout, stderr));
922
+ reject(buildAgyTimeoutError(timeout, stdout.end(), stderr.end()));
850
923
  }, timeout + 1_000);
851
924
  }
852
925
 
853
926
  proc.stdout.on("data", (data: Buffer) => {
854
- stdout += data.toString();
927
+ stdout.write(data);
855
928
  });
856
929
 
857
930
  proc.stderr.on("data", (data: Buffer) => {
858
- stderr += data.toString();
931
+ stderr.write(data);
859
932
  });
860
933
 
861
934
  proc.on("close", (code) => {
@@ -865,14 +938,17 @@ function executeAgy(
865
938
  }
866
939
  settled = true;
867
940
  clearTimers();
941
+ const stdoutText = stdout.end();
942
+ const stderrText = stderr.end();
868
943
  if (code !== 0 && code !== null) {
869
- reject(new Error(`Antigravity CLI exited with code ${code}\nstderr: ${stderr}`));
944
+ console.error("[cliExecutor] agy process exited with non-zero code:", code, "stderr:", stderrText);
945
+ reject(new Error(`Antigravity CLI exited with code ${code}\nstderr: ${stderrText}`));
870
946
  return;
871
947
  }
872
948
 
873
949
  resolve({
874
- text: stdout.trim(),
875
- raw: stdout,
950
+ text: stdoutText.trim(),
951
+ raw: stdoutText,
876
952
  elapsed: Date.now() - start,
877
953
  });
878
954
  });
@@ -881,6 +957,83 @@ function executeAgy(
881
957
  if (settled) return;
882
958
  settled = true;
883
959
  clearTimers();
960
+ console.error("[cliExecutor] agy process error event:", err);
961
+ reject(err);
962
+ });
963
+ });
964
+ }
965
+
966
+ function executeQoder(
967
+ prompt: string,
968
+ options: CliExecuteOptions
969
+ ): Promise<CliExecuteResult> {
970
+ const {
971
+ model,
972
+ reasoningEffort,
973
+ timeout = 120_000,
974
+ cwd = process.cwd(),
975
+ yolo = true,
976
+ } = options;
977
+
978
+ return new Promise((resolve, reject) => {
979
+ const args = ["-p", prompt, "--cwd", cwd];
980
+ if (model) {
981
+ args.push("--model");
982
+ args.push(model);
983
+ }
984
+ if (reasoningEffort) {
985
+ args.push("--reasoning-effort");
986
+ args.push(reasoningEffort);
987
+ }
988
+ if (yolo) {
989
+ args.push("--dangerously-skip-permissions");
990
+ }
991
+
992
+ const start = Date.now();
993
+ const proc = spawn("qoder", args, {
994
+ cwd,
995
+ env: buildCliProcessEnv(options.env),
996
+ stdio: ["ignore", "pipe", "pipe"],
997
+ });
998
+ const stdout = createUtf8Collector();
999
+ const stderr = createUtf8Collector();
1000
+ let timer: ReturnType<typeof setTimeout> | undefined;
1001
+
1002
+ if (timeout > 0) {
1003
+ timer = setTimeout(() => {
1004
+ proc.kill("SIGTERM");
1005
+ reject(new Error(`Qoder CLI timed out after ${timeout}ms`));
1006
+ }, timeout);
1007
+ }
1008
+
1009
+ proc.stdout.on("data", (data: Buffer) => {
1010
+ stdout.write(data);
1011
+ });
1012
+
1013
+ proc.stderr.on("data", (data: Buffer) => {
1014
+ stderr.write(data);
1015
+ });
1016
+
1017
+ proc.on("close", (code) => {
1018
+ if (timer) clearTimeout(timer);
1019
+ const stdoutText = stdout.end();
1020
+ const stderrText = stderr.end();
1021
+ if (code !== 0 && code !== null) {
1022
+ console.error("[cliExecutor] qoder process exited with non-zero code:", code, "stderr:", stderrText);
1023
+ reject(new Error(`Qoder CLI exited with code ${code}\nstderr: ${stderrText}`));
1024
+ return;
1025
+ }
1026
+
1027
+ resolve({
1028
+ text: stdoutText.trim(),
1029
+ raw: stdoutText,
1030
+ elapsed: Date.now() - start,
1031
+ });
1032
+ });
1033
+
1034
+ proc.on("error", (err) => {
1035
+ if (timer) clearTimeout(timer);
1036
+ console.error("[cliExecutor] qoder process error event:", err);
884
1037
  reject(err);
885
1038
  });
886
1039
  });
@@ -897,6 +1050,7 @@ const EXECUTORS: Record<
897
1050
  codex: executeCodex,
898
1051
  claude: executeClaude,
899
1052
  agy: executeAgy,
1053
+ qoder: executeQoder,
900
1054
  };
901
1055
 
902
1056
  function formatCliSessionTask(messages: CliSessionMessage[]): string {
@@ -933,7 +1087,7 @@ function getCliSessionOrThrow(sessionId: string): CliSessionState {
933
1087
  /**
934
1088
  * 执行 CLI 任务
935
1089
  *
936
- * @param provider CLI 工具类型(如 "copilot" | "gemini" | "codex" | "claude" | "agy")
1090
+ * @param provider CLI 工具类型(如 "copilot" | "gemini" | "codex" | "claude" | "agy" | "qoder")
937
1091
  * @param prompt 完整 prompt(system prompt 已由调用方拼好)
938
1092
  * @param options 执行选项
939
1093
  */
@@ -1119,7 +1273,7 @@ export function executeCliStreaming(
1119
1273
  })
1120
1274
  );
1121
1275
  }
1122
- if (provider === "agy") {
1276
+ if (provider === "agy" || provider === "qoder") {
1123
1277
  return withCleanup(
1124
1278
  executor(resolved.prompt, resolved.options).then((result) => {
1125
1279
  const merged = { ...result, warnings: [...resolved.warnings, ...(result.warnings ?? [])] };
@@ -1158,10 +1312,10 @@ export function executeCliStreaming(
1158
1312
  const start = Date.now();
1159
1313
  const proc = spawn("gh", args, {
1160
1314
  cwd,
1161
- env: { ...process.env, NO_COLOR: "1" },
1315
+ env: buildCliProcessEnv(resolved.options.env),
1162
1316
  });
1163
1317
 
1164
- let raw = "";
1318
+ const stdout = createUtf8Collector();
1165
1319
  let timer: ReturnType<typeof setTimeout> | undefined;
1166
1320
 
1167
1321
  if (timeout > 0) {
@@ -1172,8 +1326,7 @@ export function executeCliStreaming(
1172
1326
  }
1173
1327
 
1174
1328
  proc.stdout.on("data", (data: Buffer) => {
1175
- const chunk = data.toString();
1176
- raw += chunk;
1329
+ const chunk = stdout.write(data);
1177
1330
  // 过滤噪音行后再推送
1178
1331
  const cleaned = chunk
1179
1332
  .split("\n")
@@ -1184,6 +1337,7 @@ export function executeCliStreaming(
1184
1337
 
1185
1338
  proc.on("close", (code) => {
1186
1339
  if (timer) clearTimeout(timer);
1340
+ const raw = stdout.end();
1187
1341
  if (code !== 0 && code !== null) {
1188
1342
  reject(new Error(`Copilot CLI exited with code ${code}`));
1189
1343
  return;
@@ -1228,11 +1382,14 @@ function executeGeminiStreaming(
1228
1382
  const start = Date.now();
1229
1383
  const proc = spawn("gemini", args, {
1230
1384
  cwd,
1231
- env: { ...process.env, NO_COLOR: "1", NODE_OPTIONS: "--no-deprecation" },
1385
+ env: buildCliProcessEnv({
1386
+ ...options.env,
1387
+ NODE_OPTIONS: "--no-deprecation",
1388
+ }),
1232
1389
  });
1233
1390
 
1234
- let raw = "";
1235
- let stderr = "";
1391
+ const stdout = createUtf8Collector();
1392
+ const stderr = createUtf8Collector();
1236
1393
  let lineBuffer = "";
1237
1394
  let timer: ReturnType<typeof setTimeout> | undefined;
1238
1395
 
@@ -1257,8 +1414,7 @@ function executeGeminiStreaming(
1257
1414
  }
1258
1415
 
1259
1416
  proc.stdout.on("data", (data: Buffer) => {
1260
- const chunk = data.toString();
1261
- raw += chunk;
1417
+ const chunk = stdout.write(data);
1262
1418
  lineBuffer += chunk;
1263
1419
 
1264
1420
  let newlineIndex = lineBuffer.indexOf("\n");
@@ -1279,15 +1435,18 @@ function executeGeminiStreaming(
1279
1435
  });
1280
1436
 
1281
1437
  proc.stderr.on("data", (data: Buffer) => {
1282
- stderr += data.toString();
1438
+ stderr.write(data);
1283
1439
  });
1284
1440
 
1285
1441
  proc.on("close", (code) => {
1286
1442
  if (timer) clearTimeout(timer);
1443
+ lineBuffer += stdout.finish();
1444
+ const raw = stdout.text;
1445
+ const stderrText = stderr.end();
1287
1446
  flushLineBuffer();
1288
1447
 
1289
1448
  if (code !== 0 && code !== null) {
1290
- reject(new Error(`Gemini CLI exited with code ${code}\nstderr: ${stderr}`));
1449
+ reject(new Error(`Gemini CLI exited with code ${code}\nstderr: ${stderrText}`));
1291
1450
  return;
1292
1451
  }
1293
1452
 
@@ -89,10 +89,10 @@ export const getCreateAgentSchema = (t: TFunction) =>
89
89
  }).optional(),
90
90
 
91
91
  /**
92
- * CLI provider(apiSource=cli 时有效):支持 "copilot" | "gemini" | "codex" | "claude" | "agy"
92
+ * CLI provider(apiSource=cli 时有效):支持 "copilot" | "gemini" | "codex" | "claude" | "agy" | "qoder"
93
93
  */
94
94
  cliProvider: z
95
- .enum(["copilot", "gemini", "codex", "claude", "agy"])
95
+ .enum(["copilot", "gemini", "codex", "claude", "agy", "qoder"])
96
96
  .optional()
97
97
  .or(z.literal("")),
98
98
 
@@ -1,2 +1,2 @@
1
1
  export const DEFAULT_GOOGLE_LIVE_AUDIO_MODEL =
2
- "gemini-2.5-flash-native-audio-preview-12-2025";
2
+ "gemini-3.1-flash-live-preview";