@hunyed15/codecgc 0.2.9 → 0.2.11

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.
@@ -198,6 +198,41 @@ def _terminate_process_tree(process: subprocess.Popen[str]) -> None:
198
198
  process.kill()
199
199
 
200
200
 
201
+ CODEX_JS_RELATIVE = Path("node_modules") / "@openai" / "codex" / "bin" / "codex.js"
202
+
203
+
204
+ def resolve_cli_command(cli_name: str) -> list[str]:
205
+ """Resolve a CLI command to a cross-platform executable command list.
206
+
207
+ On Windows, prefers direct node invocation (node cli.js) to avoid .cmd shim
208
+ issues. Falls back to cmd.exe wrapper if .js file is not found.
209
+
210
+ Args:
211
+ cli_name: The CLI command name (e.g., "codex")
212
+
213
+ Returns:
214
+ Command list ready for subprocess.Popen, e.g.:
215
+ - Linux/Mac: ["/usr/local/bin/codex"]
216
+ - Windows (node): ["node", "C:\\...\\codex.js"]
217
+ - Windows (.cmd): ["cmd.exe", "/s", "/c", "C:\\...\\codex.CMD"]
218
+ """
219
+ resolved_path = shutil.which(cli_name) or cli_name
220
+
221
+ if os.name == "nt":
222
+ # Prefer direct node invocation on Windows (avoids .cmd shim complexity)
223
+ shim_dir = Path(resolved_path).parent
224
+ cli_js = shim_dir / CODEX_JS_RELATIVE
225
+ if cli_js.is_file():
226
+ node_path = shutil.which("node") or "node"
227
+ return [node_path, str(cli_js)]
228
+
229
+ # Fallback: wrap .cmd/.bat with cmd.exe
230
+ if resolved_path.lower().endswith((".cmd", ".bat")):
231
+ return ["cmd.exe", "/s", "/c", resolved_path]
232
+
233
+ return [resolved_path]
234
+
235
+
201
236
  def run_shell_command(cmd: list[str], timeout_seconds: int = DEFAULT_CODEX_TIMEOUT_SECONDS) -> Generator[str, None, None]:
202
237
  """Execute a command and stream its output line-by-line.
203
238
 
@@ -207,14 +242,12 @@ def run_shell_command(cmd: list[str], timeout_seconds: int = DEFAULT_CODEX_TIMEO
207
242
  Yields:
208
243
  Output lines from the command
209
244
  """
210
- # On Windows, codex is exposed via a *.cmd shim. Use cmd.exe with /s so
211
- # user prompts containing quotes/newlines aren't reinterpreted as shell syntax.
212
- popen_cmd = cmd.copy()
213
- codex_path = shutil.which('codex') or cmd[0]
214
- popen_cmd[0] = codex_path
245
+ # Resolve cmd[0] (e.g., "codex") to a platform-appropriate executable command,
246
+ # wrapping .cmd/.bat shims with cmd.exe on Windows. The remaining args are
247
+ # passed through unchanged.
248
+ popen_cmd = resolve_cli_command(cmd[0]) + cmd[1:]
215
249
 
216
250
  # Ensure Codex CLI inherits the same Python environment as this MCP server
217
- # This is critical for Python 3.11+ syntax compatibility (e.g., except*)
218
251
  env = os.environ.copy()
219
252
  python_executable = sys.executable
220
253
  if python_executable:
@@ -160,23 +160,32 @@ GEMINI_JS_RELATIVE = Path("node_modules") / "@google" / "gemini-cli" / "bundle"
160
160
 
161
161
 
162
162
  def _resolve_gemini_command(cmd: list[str]) -> list[str]:
163
- """Resolve the gemini CLI command, preferring direct node invocation on Windows."""
163
+ """Resolve the gemini CLI command to a cross-platform executable command list.
164
+
165
+ On Windows, prefers direct node invocation (node gemini.js) to avoid .cmd shim
166
+ issues. Falls back to cmd.exe wrapper if gemini.js is not found.
167
+
168
+ Args:
169
+ cmd: Command list starting with "gemini", e.g., ["gemini", "-o", "stream-json", ...]
170
+
171
+ Returns:
172
+ Platform-appropriate command list ready for subprocess.Popen
173
+ """
164
174
  gemini_shim = shutil.which("gemini") or cmd[0]
165
175
 
166
176
  if os.name == "nt":
177
+ # Prefer direct node invocation on Windows (avoids .cmd shim complexity)
167
178
  shim_dir = Path(gemini_shim).parent
168
179
  gemini_js = shim_dir / GEMINI_JS_RELATIVE
169
180
  if gemini_js.is_file():
170
181
  node_path = shutil.which("node") or "node"
171
182
  return [node_path, str(gemini_js)] + cmd[1:]
172
183
 
184
+ # Fallback: wrap .cmd/.bat with cmd.exe
173
185
  if gemini_shim.lower().endswith((".cmd", ".bat")):
174
- from subprocess import list2cmdline
175
- cmd[0] = gemini_shim
176
- return ["cmd.exe", "/s", "/c", list2cmdline(cmd)]
186
+ return ["cmd.exe", "/s", "/c", gemini_shim] + cmd[1:]
177
187
 
178
- cmd[0] = gemini_shim
179
- return cmd
188
+ return [gemini_shim] + cmd[1:]
180
189
 
181
190
 
182
191
  def run_shell_command(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunyed15/codecgc",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Claude-hosted multi-model workflow product shell for CodeCGC.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -294,9 +294,22 @@ async def execute_payload(payload: dict[str, Any], timeout_seconds: int) -> dict
294
294
  payload["tool_args"],
295
295
  read_timeout_seconds=datetime.timedelta(seconds=timeout_seconds),
296
296
  )
297
- except* ExceptionGroup:
298
- # MCP SDK 清理阶段的 TaskGroup 异常不影响已获得的结果
299
- pass
297
+ except ExceptionGroup as eg:
298
+ # 递归展开 TaskGroup 中所有叶子异常
299
+ def _flatten(exc: BaseException) -> list[str]:
300
+ if isinstance(exc, ExceptionGroup):
301
+ result = []
302
+ for sub in exc.exceptions:
303
+ result.extend(_flatten(sub))
304
+ return result
305
+ return [f"{type(exc).__name__}: {exc}"]
306
+ sub_errors = _flatten(eg)
307
+ if raw_result is None:
308
+ raise RuntimeError(f"MCP call failed with TaskGroup errors: {'; '.join(sub_errors)}") from eg
309
+ except Exception as _exc:
310
+ # 只有在已获得结果的情况下才忽略清理阶段异常;否则保留真实错误
311
+ if raw_result is None:
312
+ raise RuntimeError(f"MCP call failed: {type(_exc).__name__}: {_exc}") from _exc
300
313
 
301
314
  if raw_result is None:
302
315
  raise RuntimeError("MCP call failed before producing a result.")