@bivy/bivy 0.16.20-staging.4 → 0.16.20-staging.6

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.
@@ -313,13 +313,44 @@ export function claudeStreamJsonParser() {
313
313
  * codex-cli 0.142): a thread with turns, each turn a stream of typed items.
314
314
  * {"type":"thread.started","thread_id":…}
315
315
  * {"type":"turn.started"} / {"type":"turn.completed"} / {"type":"turn.failed","error":{message}}
316
+ * {"type":"item.started","item":{…}} ← a shell/patch/MCP call begins (live card)
316
317
  * {"type":"item.completed","item":{"id":…,"type":"agent_message"|"reasoning"|
317
- * "command_execution"|"mcp_tool_call"|"file_change"|"error", …}}
318
+ * "command_execution"|"mcp_tool_call"|"file_change"|"error", "status":…, …}}
318
319
  * {"type":"error","message":…} ← transient reconnect noise (non-fatal)
320
+ * A `file_change`/`command_execution` item carries a terminal `status`
321
+ * (`completed`|`failed`); a `failed` one is surfaced as an errored tool result
322
+ * rather than a silent success (mirrors the governed app-server shim).
319
323
  */
320
324
  export function codexJsonParser() {
321
325
  const acc = new TurnAccumulator({ provider: "codex", protocol: "structured-pipe" });
322
326
  let sessionRef;
327
+ // Tool ids already surfaced as a running card, so `item.started` (live) and the
328
+ // later `item.completed` (terminal) render one card per call, not two.
329
+ const startedTools = new Set();
330
+ const ensureToolUse = (id, name, input, events) => {
331
+ if (id && startedTools.has(id))
332
+ return;
333
+ if (id)
334
+ startedTools.add(id);
335
+ acc.addToolUse(id, name, input, events);
336
+ };
337
+ // A tool item begins. Codex emits `item.started` for shell/patch/MCP calls
338
+ // before the matching `item.completed`, so surfacing it shows a long-running
339
+ // command or patch as a live "running" card instead of nothing until it ends.
340
+ const handleStarted = (item, events) => {
341
+ const id = String(item.id ?? "");
342
+ switch (String(item.type ?? "")) {
343
+ case "command_execution":
344
+ ensureToolUse(id, "shell", { command: item.command ?? "" }, events);
345
+ break;
346
+ case "file_change":
347
+ ensureToolUse(id, "apply_patch", { changes: item.changes ?? item }, events);
348
+ break;
349
+ case "mcp_tool_call":
350
+ ensureToolUse(id, String(item.tool ?? item.server ?? "mcp"), item.arguments ?? item.input, events);
351
+ break;
352
+ }
353
+ };
323
354
  const handleItem = (item, events) => {
324
355
  const id = String(item.id ?? "");
325
356
  switch (String(item.type ?? "")) {
@@ -327,19 +358,27 @@ export function codexJsonParser() {
327
358
  acc.appendText(String(item.text ?? ""), events);
328
359
  break;
329
360
  case "command_execution":
330
- acc.addToolUse(id, "shell", { command: item.command ?? "" }, events);
331
- if (item.aggregated_output != null || item.exit_code != null) {
332
- acc.addToolResult(id, "shell", { content: item.aggregated_output ?? "", exitCode: item.exit_code }, events, Number(item.exit_code) !== 0);
361
+ ensureToolUse(id, "shell", { command: item.command ?? "" }, events);
362
+ if (item.aggregated_output != null || item.exit_code != null || item.status != null) {
363
+ acc.addToolResult(id, "shell", { content: item.aggregated_output ?? "", exitCode: item.exit_code }, events, Number(item.exit_code) !== 0 || item.status === "failed");
333
364
  }
334
365
  break;
335
366
  case "mcp_tool_call":
336
- acc.addToolUse(id, String(item.tool ?? item.server ?? "mcp"), item.arguments ?? item.input, events);
337
- if (item.result != null)
338
- acc.addToolResult(id, String(item.tool ?? "mcp"), item.result, events);
367
+ ensureToolUse(id, String(item.tool ?? item.server ?? "mcp"), item.arguments ?? item.input, events);
368
+ if (item.result != null || item.error != null || item.status != null) {
369
+ acc.addToolResult(id, String(item.tool ?? "mcp"), item.result ?? item.error ?? item.status, events, item.error != null || item.status === "failed");
370
+ }
339
371
  break;
340
- case "file_change":
341
- acc.addToolUse(id, "apply_patch", { changes: item.changes ?? item }, events);
372
+ case "file_change": {
373
+ ensureToolUse(id, "apply_patch", { changes: item.changes ?? item }, events);
374
+ // `file_change` is terminal in `exec --json` (no separate result event),
375
+ // so close the card with a result reflecting its status. Without this a
376
+ // completed patch hangs as "running" and — worse — a FAILED patch renders
377
+ // as a successful edit. Mirrors the app-server shim's handling.
378
+ const status = String(item.status ?? "completed");
379
+ acc.addToolResult(id, "apply_patch", status, events, status === "failed");
342
380
  break;
381
+ }
343
382
  case "reasoning": {
344
383
  // Codex reasoning items carry either `text` or a `summary` (string or an
345
384
  // array of {text}) — surface whichever is present as a thinking stream.
@@ -380,6 +419,10 @@ export function codexJsonParser() {
380
419
  case "turn.started":
381
420
  events.push({ type: "turn_start" });
382
421
  break;
422
+ case "item.started":
423
+ if (msg.item && typeof msg.item === "object")
424
+ handleStarted(msg.item, events);
425
+ break;
383
426
  case "item.completed":
384
427
  if (msg.item && typeof msg.item === "object")
385
428
  handleItem(msg.item, events);
@@ -116,13 +116,21 @@ export function mapToolCall(toolName, input, context = {}) {
116
116
  }
117
117
  if (inToolSet(EDIT, toolName, key)) {
118
118
  let path = str(o, ...PATH_KEYS);
119
- // Codex `apply_patch`/`file_change` carries a `changes` map keyed by path.
119
+ // Codex `apply_patch`/`file_change` carries a `changes` collection. The
120
+ // app-server keys it by path (a map); `codex exec --json` sends an array of
121
+ // `{path, kind}` entries. Derive the first touched path from either shape so
122
+ // the patch renders as an edit card instead of an opaque blob.
120
123
  if (!path) {
121
124
  const changes = o.changes;
122
- if (changes && typeof changes === "object" && !Array.isArray(changes)) {
123
- const first = Object.keys(changes)[0];
125
+ if (Array.isArray(changes)) {
126
+ const first = changes.find((c) => !!c && typeof c === "object" && !Array.isArray(c));
124
127
  if (first)
125
- path = first;
128
+ path = str(first, ...PATH_KEYS);
129
+ }
130
+ else if (changes && typeof changes === "object") {
131
+ const firstKey = Object.keys(changes)[0];
132
+ if (firstKey)
133
+ path = firstKey;
126
134
  }
127
135
  }
128
136
  if (!path)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.16.20-staging.4",
3
+ "version": "0.16.20-staging.6",
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.",