@zhihand/mcp 0.17.0 → 0.17.2

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.
@@ -387,8 +387,9 @@ function dispatchGemini(prompt, startTime, log, model) {
387
387
  }
388
388
  // ── Codex Dispatch ─────────────────────────────────────────
389
389
  function dispatchCodex(prompt, startTime, model) {
390
- // codex exec --full-auto --skip-git-repo-check --json [-m model] <prompt>
391
- const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
390
+ // --dangerously-bypass-approvals-and-sandbox is required so MCP tool calls
391
+ // are not auto-cancelled in non-interactive mode (--full-auto cancels them)
392
+ const args = ["exec", "--dangerously-bypass-approvals-and-sandbox", "--skip-git-repo-check", "--json"];
392
393
  const codexModel = model ?? process.env.CLAUDE_CODEX_MODEL;
393
394
  if (codexModel) {
394
395
  args.push("-m", codexModel);
@@ -412,48 +413,16 @@ function dispatchClaude(prompt, startTime, model) {
412
413
  activeChild = child;
413
414
  return collectChildOutput(child, startTime);
414
415
  }
415
- // ── Codex JSONL Output Parser ──────────────────────────────
416
- /** Parse codex JSONL output and extract agent message text. */
417
- function parseCodexJsonl(raw) {
418
- const lines = raw.split("\n").filter(Boolean);
419
- const texts = [];
420
- let hasError = false;
421
- for (const line of lines) {
422
- try {
423
- const event = JSON.parse(line);
424
- const type = String(event.type ?? "");
425
- // Extract text from completed agent messages
426
- if (type === "item.completed") {
427
- const item = event.item;
428
- if (item && typeof item.text === "string" && item.text.trim()) {
429
- texts.push(item.text.trim());
430
- }
431
- }
432
- // Capture errors
433
- if (type === "error") {
434
- const msg = String(event.message ?? "");
435
- if (msg)
436
- texts.push(`Error: ${msg}`);
437
- hasError = true;
438
- }
439
- if (type === "turn.failed") {
440
- hasError = true;
441
- }
442
- }
443
- catch {
444
- // Not valid JSON — skip (truncated line or stderr mixed in)
445
- }
446
- }
447
- if (texts.length > 0) {
448
- return { text: texts.join("\n\n"), success: !hasError };
449
- }
450
- return { text: raw.trim(), success: false };
451
- }
416
+ /**
417
+ * Collect codex JSONL output with streaming line parsing.
418
+ * Processes each JSONL line as it arrives so we extract agent text
419
+ * without buffering large binary payloads (e.g. base64 screenshots).
420
+ */
452
421
  function collectCodexOutput(child, startTime) {
453
422
  return new Promise((resolve) => {
454
- const chunks = [];
455
- let totalBytes = 0;
456
- let truncated = false;
423
+ const texts = [];
424
+ let hasError = false;
425
+ let lineBuffer = "";
457
426
  let settled = false;
458
427
  function settle(result) {
459
428
  if (settled)
@@ -461,36 +430,59 @@ function collectCodexOutput(child, startTime) {
461
430
  settled = true;
462
431
  resolve(result);
463
432
  }
464
- const timer = setTimeout(() => { closeChild(child); }, CLI_TIMEOUT);
465
- const collectOutput = (data) => {
466
- if (truncated)
433
+ function processLine(line) {
434
+ if (!line.trim())
467
435
  return;
468
- totalBytes += data.length;
469
- if (totalBytes > MAX_OUTPUT_BYTES) {
470
- truncated = true;
471
- chunks.push(data.subarray(0, MAX_OUTPUT_BYTES - (totalBytes - data.length)));
436
+ try {
437
+ const event = JSON.parse(line);
438
+ const type = String(event.type ?? "");
439
+ if (type === "item.completed") {
440
+ const item = event.item;
441
+ if (item && typeof item.text === "string" && item.text.trim()) {
442
+ texts.push(item.text.trim());
443
+ }
444
+ }
445
+ if (type === "error") {
446
+ const msg = String(event.message ?? "");
447
+ if (msg)
448
+ texts.push(`Error: ${msg}`);
449
+ hasError = true;
450
+ }
451
+ if (type === "turn.failed") {
452
+ hasError = true;
453
+ }
472
454
  }
473
- else {
474
- chunks.push(data);
455
+ catch {
456
+ // Not valid JSON — skip
457
+ }
458
+ }
459
+ const timer = setTimeout(() => { closeChild(child); }, CLI_TIMEOUT);
460
+ const onData = (data) => {
461
+ lineBuffer += data.toString("utf8");
462
+ const lines = lineBuffer.split("\n");
463
+ // Keep the last (possibly incomplete) line in the buffer
464
+ lineBuffer = lines.pop() ?? "";
465
+ for (const line of lines) {
466
+ processLine(line);
475
467
  }
476
468
  };
477
- child.stdout?.on("data", collectOutput);
478
- child.stderr?.on("data", collectOutput);
469
+ child.stdout?.on("data", onData);
470
+ // stderr is not JSONL, just discard
471
+ child.stderr?.resume();
479
472
  child.on("close", (code) => {
480
473
  clearTimeout(timer);
481
474
  activeChild = null;
475
+ // Process any remaining data in the buffer
476
+ if (lineBuffer.trim())
477
+ processLine(lineBuffer);
482
478
  const durationMs = Date.now() - startTime;
483
- const raw = Buffer.concat(chunks).toString("utf8").trim();
484
- const parsed = parseCodexJsonl(raw);
485
- let text = parsed.text;
486
- if (truncated)
487
- text += "\n\n[Output truncated at 100KB]";
479
+ let text = texts.join("\n\n");
488
480
  if (!text) {
489
481
  text = code === 0
490
482
  ? "Task completed (no output)."
491
483
  : `CLI process exited with code ${code}.`;
492
484
  }
493
- settle({ text, success: parsed.success && code === 0, durationMs });
485
+ settle({ text, success: !hasError && code === 0, durationMs });
494
486
  });
495
487
  child.on("error", (err) => {
496
488
  clearTimeout(timer);
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { controlSchema, screenshotSchema, pairSchema } from "./tools/schemas.js"
5
5
  import { executeControl } from "./tools/control.js";
6
6
  import { handleScreenshot } from "./tools/screenshot.js";
7
7
  import { handlePair } from "./tools/pair.js";
8
- const PACKAGE_VERSION = "0.17.0";
8
+ const PACKAGE_VERSION = "0.17.2";
9
9
  export function createServer(deviceName) {
10
10
  const server = new McpServer({
11
11
  name: "zhihand",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhihand/mcp",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "ZhiHand MCP Server — phone control tools for Claude Code, Codex, Gemini CLI, and OpenClaw",