@zhihand/mcp 0.17.1 → 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.
@@ -413,48 +413,16 @@ function dispatchClaude(prompt, startTime, model) {
413
413
  activeChild = child;
414
414
  return collectChildOutput(child, startTime);
415
415
  }
416
- // ── Codex JSONL Output Parser ──────────────────────────────
417
- /** Parse codex JSONL output and extract agent message text. */
418
- function parseCodexJsonl(raw) {
419
- const lines = raw.split("\n").filter(Boolean);
420
- const texts = [];
421
- let hasError = false;
422
- for (const line of lines) {
423
- try {
424
- const event = JSON.parse(line);
425
- const type = String(event.type ?? "");
426
- // Extract text from completed agent messages
427
- if (type === "item.completed") {
428
- const item = event.item;
429
- if (item && typeof item.text === "string" && item.text.trim()) {
430
- texts.push(item.text.trim());
431
- }
432
- }
433
- // Capture errors
434
- if (type === "error") {
435
- const msg = String(event.message ?? "");
436
- if (msg)
437
- texts.push(`Error: ${msg}`);
438
- hasError = true;
439
- }
440
- if (type === "turn.failed") {
441
- hasError = true;
442
- }
443
- }
444
- catch {
445
- // Not valid JSON — skip (truncated line or stderr mixed in)
446
- }
447
- }
448
- if (texts.length > 0) {
449
- return { text: texts.join("\n\n"), success: !hasError };
450
- }
451
- return { text: raw.trim(), success: false };
452
- }
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
+ */
453
421
  function collectCodexOutput(child, startTime) {
454
422
  return new Promise((resolve) => {
455
- const chunks = [];
456
- let totalBytes = 0;
457
- let truncated = false;
423
+ const texts = [];
424
+ let hasError = false;
425
+ let lineBuffer = "";
458
426
  let settled = false;
459
427
  function settle(result) {
460
428
  if (settled)
@@ -462,36 +430,59 @@ function collectCodexOutput(child, startTime) {
462
430
  settled = true;
463
431
  resolve(result);
464
432
  }
465
- const timer = setTimeout(() => { closeChild(child); }, CLI_TIMEOUT);
466
- const collectOutput = (data) => {
467
- if (truncated)
433
+ function processLine(line) {
434
+ if (!line.trim())
468
435
  return;
469
- totalBytes += data.length;
470
- if (totalBytes > MAX_OUTPUT_BYTES) {
471
- truncated = true;
472
- 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
+ }
473
454
  }
474
- else {
475
- 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);
476
467
  }
477
468
  };
478
- child.stdout?.on("data", collectOutput);
479
- child.stderr?.on("data", collectOutput);
469
+ child.stdout?.on("data", onData);
470
+ // stderr is not JSONL, just discard
471
+ child.stderr?.resume();
480
472
  child.on("close", (code) => {
481
473
  clearTimeout(timer);
482
474
  activeChild = null;
475
+ // Process any remaining data in the buffer
476
+ if (lineBuffer.trim())
477
+ processLine(lineBuffer);
483
478
  const durationMs = Date.now() - startTime;
484
- const raw = Buffer.concat(chunks).toString("utf8").trim();
485
- const parsed = parseCodexJsonl(raw);
486
- let text = parsed.text;
487
- if (truncated)
488
- text += "\n\n[Output truncated at 100KB]";
479
+ let text = texts.join("\n\n");
489
480
  if (!text) {
490
481
  text = code === 0
491
482
  ? "Task completed (no output)."
492
483
  : `CLI process exited with code ${code}.`;
493
484
  }
494
- settle({ text, success: parsed.success && code === 0, durationMs });
485
+ settle({ text, success: !hasError && code === 0, durationMs });
495
486
  });
496
487
  child.on("error", (err) => {
497
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.1";
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.1",
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",