@bivy/bivy 0.16.25-staging.21 → 0.16.25-staging.23

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.
package/bin/acp-shim.mjs CHANGED
@@ -313,6 +313,13 @@ function onSessionUpdate(params) {
313
313
  if (!content) return "";
314
314
  if (typeof content === "string") return content;
315
315
  if (Array.isArray(content)) return content.map(textOf).join("");
316
+ // ACP's ToolCallContent wraps a ContentBlock: `{ type: "content", content:
317
+ // { type: "text", text } }`. Unwrap it (recursively) so a tool's real output
318
+ // — a command's stdout, a fetched document, a read's text — isn't dropped,
319
+ // which collapsed the tool_result to the bare status ("completed"). opencode
320
+ // and most spec-compliant ACP agents deliver tool output in this wrapped
321
+ // shape, so reading only the bare `{type:"text"}` form lost it in production.
322
+ if (content.type === "content" && content.content != null) return textOf(content.content);
316
323
  if (content.type === "text" && typeof content.text === "string") return content.text;
317
324
  if (typeof content.text === "string") return content.text;
318
325
  return "";
@@ -415,6 +415,16 @@ function onNotification(m) {
415
415
  bivy({ type: "session.status", status: "working" });
416
416
  return;
417
417
  case "turn/completed": {
418
+ // The completion event is authoritative about a terminal failure: a turn
419
+ // that hit a usage limit (or any provider error) arrives here as
420
+ // `status: "failed"` carrying `turn.error`. Capture it directly instead of
421
+ // depending on a separate `error` notification landing first — otherwise a
422
+ // completion that omits that notification seals as session.done and the
423
+ // user sees a silent empty turn (fail loud, never swallow the error).
424
+ const turnError =
425
+ params?.turn?.error?.message ||
426
+ (params?.turn?.status === "failed" ? params?.error?.message || params?.message || "Codex turn failed" : undefined);
427
+ if (turnError && !sawError) sawError = turnError;
418
428
  // Codex 0.147 can publish turn/completed just before the final
419
429
  // collabAgentToolCall item/completed notification. Sealing immediately
420
430
  // leaves the sub-agent card running forever and drops its persisted
@@ -711,14 +711,41 @@ class ProtocolSession {
711
711
  return;
712
712
  }
713
713
  if (type === "session.error") {
714
+ const errorText = String(msg.error || "Protocol agent error");
715
+ // Preserve whatever the failed turn already streamed — a partial assistant
716
+ // reply and its tool calls/results — persisted exactly as session.done
717
+ // persists a clean turn. The old handler wiped turnContent here, so a mid-
718
+ // turn failure dropped all of that work from the reopened transcript.
719
+ const hadTools = this.turnContent.length > 0 || this.turnToolResults.length > 0;
720
+ if (hadTools) {
721
+ this.flushPendingTurnText();
722
+ if (this.turnContent.length)
723
+ this.messages.push({ role: "assistant", content: this.turnContent, timestamp: Date.now() });
724
+ if (this.turnToolResults.length)
725
+ this.messages.push({ role: "user", content: this.turnToolResults, timestamp: Date.now() });
726
+ }
727
+ else if (this.assistantText) {
728
+ this.messages.push({ role: "assistant", content: this.assistantText, timestamp: Date.now() });
729
+ }
730
+ // Append a terminal error marker in the shape store-render + terminalTurnError
731
+ // recognize (stopReason "error" + errorMessage), so the failure itself
732
+ // survives a reload as an inline error instead of the turn reopening blank
733
+ // ("looks done, no reply"). Matches the live session.error emitted just below.
734
+ const errored = { role: "assistant", content: "", stopReason: "error", errorMessage: errorText, timestamp: Date.now() };
735
+ this.messages.push(errored);
714
736
  this.streaming = false;
737
+ this.assistantText = "";
738
+ this.assistantItemBoundary = false;
715
739
  this.reasoningText = "";
716
740
  this.turnContent = [];
717
741
  this.turnTextFlushed = "";
718
742
  this.turnToolResults = [];
719
743
  this.toolDetailsByCallId.clear();
720
744
  this.openToolIds = [];
721
- this.emit({ type: "session.error", error: String(msg.error || "Protocol agent error") });
745
+ // message_end drives the daemon's base-transcript snapshot (agent_end does
746
+ // not), so the preserved content + error marker are durably persisted.
747
+ this.emit({ type: "message_end", message: errored });
748
+ this.emit({ type: "session.error", error: errorText });
722
749
  this.emit({ type: "agent_end" });
723
750
  return;
724
751
  }
@@ -872,7 +899,10 @@ class ProtocolSession {
872
899
  const preflightError = await this.preparePreflight();
873
900
  if (preflightError) {
874
901
  this.streaming = false;
875
- const message = { role: "assistant", content: "", errorMessage: preflightError };
902
+ // stopReason "error" is what store-render keys on to re-render this as an
903
+ // inline error on reload (without it the turn reopened blank); it also
904
+ // matches the terminal-error message shape every other failure path uses.
905
+ const message = { role: "assistant", content: "", stopReason: "error", errorMessage: preflightError };
876
906
  this.messages.push(message);
877
907
  this.emit({ type: "message_start", message: { role: "assistant", content: "" } });
878
908
  this.emit({ type: "session.error", error: preflightError });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.16.25-staging.21",
3
+ "version": "0.16.25-staging.23",
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.",