@bivy/bivy 0.16.22-staging.1 → 0.16.22-staging.3

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.
@@ -623,12 +623,51 @@ function textFromStreamEvent(msg) {
623
623
  }
624
624
  return "";
625
625
  }
626
- /** Extract an ACP tool update from a JSON-RPC session/update notification.
627
- * ACP deliberately calls these updates rather than tool calls; mapping the
628
- * common fields here lets any ACP-speaking CLI show the same tool cards. */
626
+ /** Flatten an ACP `ContentBlock[]` (or a single block/string) to plain text.
627
+ * ACP tool updates carry their human-readable output as content blocks
628
+ * `[{type:"content", content:{type:"text", text}}]` or `[{type:"text", text}]`
629
+ * which is the display-normalized form to prefer over a raw byte dump. Returns
630
+ * undefined when there is no usable text (so callers fall back to rawOutput). */
631
+ function flattenAcpContent(value) {
632
+ const walk = (v) => {
633
+ if (typeof v === "string")
634
+ return v;
635
+ if (Array.isArray(v))
636
+ return v.map(walk).join("");
637
+ if (v && typeof v === "object") {
638
+ const o = v;
639
+ if (typeof o.text === "string")
640
+ return o.text;
641
+ if (o.content !== undefined)
642
+ return walk(o.content);
643
+ }
644
+ return "";
645
+ };
646
+ const text = walk(value);
647
+ return text.trim() ? text : undefined;
648
+ }
649
+ /** Extract a numeric exit code from a raw tool-output object, if present. */
650
+ function rawExitCode(raw) {
651
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
652
+ return undefined;
653
+ const o = raw;
654
+ const exit = o.exit_code ?? o.exitCode ?? o.code;
655
+ return typeof exit === "number" ? exit : undefined;
656
+ }
657
+ /** Extract an ACP-style tool update. Two transports converge here:
658
+ * 1. A JSON-RPC `session/update` notification (`params.update`), the true ACP
659
+ * wire form used by Gemini/Qwen/Copilot etc.
660
+ * 2. A TOP-LEVEL tool frame (`{type:"tool_call", toolCallId, toolName,
661
+ * rawInput}` / `{type:"tool_call_update", …, rawOutput}`) — the shape the
662
+ * Grok CLI's `--output-format streaming-json` emits without the JSON-RPC
663
+ * envelope. Both name the same fields, so one mapper renders both as the
664
+ * same tool cards (no per-agent branch). */
629
665
  function acpToolUpdate(msg) {
630
666
  const params = msg.params;
631
- const update = (params?.update ?? msg.update);
667
+ // Fall back to the message itself so a top-level tool frame (Grok) is read the
668
+ // same way as a nested `session/update` (canonical ACP). A non-tool top-level
669
+ // frame has no toolCallId and is rejected by the `id` guard below.
670
+ const update = (params?.update ?? msg.update ?? msg);
632
671
  if (!update || typeof update !== "object")
633
672
  return undefined;
634
673
  const type = String(update.sessionUpdate ?? update.type ?? "").toLowerCase();
@@ -636,15 +675,27 @@ function acpToolUpdate(msg) {
636
675
  if (!id)
637
676
  return undefined;
638
677
  if (type === "tool_call" || type === "tool_call_started" || type === "tool_call_start") {
639
- return { kind: "call", id, name: String(update.title ?? update.name ?? "tool"), input: update.rawInput ?? update.input ?? update.arguments };
678
+ return { kind: "call", id, name: String(update.title ?? update.toolName ?? update.name ?? "tool"), input: update.rawInput ?? update.input ?? update.arguments };
640
679
  }
641
680
  if (type === "tool_call_update" || type === "tool_result" || type === "tool_call_completed") {
642
681
  const status = String(update.status ?? "").toLowerCase();
643
- // ACP sends progress updates through the same envelope. Only close the
644
- // card once it has a terminal status or an actual result payload.
645
- const output = update.rawOutput ?? update.output ?? update.content ?? update.result;
646
- if (type === "tool_call_update" && (status === "in_progress" || status === "pending" || (output === undefined && !["completed", "failed", "error"].includes(status))))
682
+ const terminal = status === "completed" || status === "complete" || status === "success" || status === "done" || status === "failed" || status === "error";
683
+ const hasStatus = update.status !== undefined && update.status !== null && String(update.status).trim() !== "";
684
+ const raw = update.rawOutput ?? update.output ?? update.result;
685
+ // A CLI that streams progress (Grok) tags every frame with a non-terminal
686
+ // status and a partial payload — only a terminal status closes the card. A
687
+ // CLI that sends a single result frame with no status closes as soon as a
688
+ // payload lands. This prevents both a premature close and a duplicate card.
689
+ if (type === "tool_call_update" && (hasStatus ? !terminal : raw === undefined))
647
690
  return undefined;
691
+ // Prefer the display-normalized ACP content text over a raw payload (Grok's
692
+ // rawOutput.output is a byte array that would render as garbage). Preserve
693
+ // the exit code from the raw payload when we take the content text instead.
694
+ const contentText = flattenAcpContent(update.content);
695
+ const exit = rawExitCode(raw);
696
+ const output = contentText !== undefined
697
+ ? (exit !== undefined ? { content: contentText, exit_code: exit } : contentText)
698
+ : (raw ?? update.content);
648
699
  return { kind: "result", id, output, error: status === "failed" || status === "error" };
649
700
  }
650
701
  return undefined;
@@ -78,13 +78,14 @@ function str(o, ...keys) {
78
78
  }
79
79
  const SHELL = new Set([
80
80
  "bash", "sh", "shell", "run", "runcommand", "command", "commandexecution",
81
- "exec", "execute", "executecommand", "terminal", "runterminalcmd", "localshell",
81
+ "exec", "execute", "executecommand", "terminal", "runterminalcmd",
82
+ "runterminalcommand", "localshell",
82
83
  ]);
83
84
  const READ = new Set(["read", "readfile", "view", "viewfile", "cat", "openfile", "fileread"]);
84
85
  const WRITE = new Set(["write", "writefile", "create", "createfile", "newfile", "filewrite", "savefile"]);
85
86
  const EDIT = new Set([
86
87
  "edit", "editfile", "multiedit", "strreplace", "strreplaceeditor", "replace",
87
- "applypatch", "patch", "filechange", "filechanges", "update", "modify",
88
+ "searchreplace", "applypatch", "patch", "filechange", "filechanges", "update", "modify",
88
89
  ]);
89
90
  const SEARCH = new Set(["search", "grep", "glob", "ripgrep", "rg", "find", "findfiles", "codebasesearch", "filesearch"]);
90
91
  const FETCH = new Set(["fetch", "webfetch", "httpfetch", "geturl", "browse", "curl", "openurl"]);
@@ -96,7 +97,8 @@ const PLAN = new Set(["plan", "updateplan", "setplan", "todo", "todowrite", "exi
96
97
  // still falls through to the opaque default, never a false "Delegated".
97
98
  const DELEGATE = new Set([
98
99
  "task", "agent", "subagent", "runagent", "runsubagent", "spawnagent",
99
- "dispatchagent", "delegate", "delegatetask", "agenttask", "startagent", "startrun",
100
+ "spawnsubagent", "dispatchagent", "delegate", "delegatetask", "agenttask",
101
+ "startagent", "startrun",
100
102
  // Codex app-server 0.147+ first-class child lifecycle item.
101
103
  "subagentactivity",
102
104
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.16.22-staging.1",
3
+ "version": "0.16.22-staging.3",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",