You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@casbin.apache.org by hs...@apache.org on 2026/09/06 16:05:20 UTC

(casbin-gateway) 02/02: fix(chat): keep Codex's passing notices out of the reply

This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git

commit 0ddd45549f82cc34980cbbbbcdf9f5ca8709b2ba
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Mon Sep 7 00:05:10 2026 +0800

    fix(chat): keep Codex's passing notices out of the reply
---
 agentsession/event.go       |  6 +++++-
 agentsession/parse_codex.go |  5 ++++-
 imbridge/render.go          | 19 ++++++++++++++-----
 web/src/pages/ChatPage.tsx  | 14 +++++++++++++-
 web/src/types.ts            |  2 +-
 5 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/agentsession/event.go b/agentsession/event.go
index 57a663e..9483510 100644
--- a/agentsession/event.go
+++ b/agentsession/event.go
@@ -30,6 +30,10 @@ const (
 	EventUsage      EventType = "usage"
 	EventDone       EventType = "done"
 	EventError      EventType = "error"
+	// EventNotice is something the agent mentioned in passing - a setting it
+	// ignored, a limit it worked around. The turn carried on, so it is kept out
+	// of the answer and shown beside it.
+	EventNotice EventType = "notice"
 )
 
 // Event is one piece of what an agent said, in a shape every agent is flattened
@@ -44,7 +48,7 @@ type Event struct {
 	CreatedTime string `json:"createdTime"`
 
 	// Text is the answer for EventText and EventThinking, and the message for
-	// EventError.
+	// EventError and EventNotice.
 	Text string `json:"text,omitempty"`
 
 	ToolName  string `json:"toolName,omitempty"`
diff --git a/agentsession/parse_codex.go b/agentsession/parse_codex.go
index 4e2ec37..1a16255 100644
--- a/agentsession/parse_codex.go
+++ b/agentsession/parse_codex.go
@@ -138,9 +138,12 @@ func emitCodexItem(item *codexItem, emit func(Event)) {
 		event.ToolName = "Edit"
 		event.ToolInput = firstOf(item.Text, item.Message)
 		emit(event)
+	// An error item is Codex saying something went sideways without the turn
+	// ending: a setting it dropped, a budget it trimmed to fit. A turn that
+	// really failed says so through turn.failed, which fails the run.
 	case "error":
 		if text := firstOf(item.Message, item.Text); text != "" {
-			emit(textEvent(EventError, text))
+			emit(textEvent(EventNotice, text))
 		}
 	}
 }
diff --git a/imbridge/render.go b/imbridge/render.go
index 7d1d200..f2f366a 100644
--- a/imbridge/render.go
+++ b/imbridge/render.go
@@ -25,6 +25,9 @@ import (
 // what an agent did is most of what somebody watching wants to see.
 type answerBuffer struct {
 	parts []string
+	// prose says the last part is the agent talking, which is the only part a
+	// fragment of prose may be joined onto.
+	prose bool
 	// dirty says something has arrived since the last time this was written out.
 	dirty bool
 }
@@ -37,25 +40,31 @@ func (b *answerBuffer) add(event agentsession.Event) {
 		}
 		// Prose arrives in fragments, so it is joined onto the piece before it
 		// rather than starting a new one.
-		if len(b.parts) > 0 && !strings.HasPrefix(b.parts[len(b.parts)-1], toolMarker) {
+		if b.prose {
 			b.parts[len(b.parts)-1] += event.Text
 		} else {
 			b.parts = append(b.parts, event.Text)
+			b.prose = true
 		}
 	case agentsession.EventToolUse:
 		b.parts = append(b.parts, toolMarker+toolLine(event))
+		b.prose = false
 	case agentsession.EventError:
-		if event.Text != "" {
-			b.parts = append(b.parts, "\n⚠ "+event.Text)
+		if event.Text == "" {
+			return
 		}
+		b.parts = append(b.parts, "\n⚠ "+event.Text)
+		b.prose = false
+	// What the agent mentioned in passing stays out of the chat: the answer is
+	// what somebody asked for, and a warning about a setting on top of every
+	// reply is noise. The session page still shows it.
 	default:
 		return
 	}
 	b.dirty = true
 }
 
-// toolMarker starts the pieces that are a tool rather than prose, so a fragment
-// of prose is not appended onto one.
+// toolMarker sets a tool line apart from the prose around it.
 const toolMarker = "· "
 
 // toolInputLimit keeps a command in the message without the message becoming the
diff --git a/web/src/pages/ChatPage.tsx b/web/src/pages/ChatPage.tsx
index 883e190..93933ce 100644
--- a/web/src/pages/ChatPage.tsx
+++ b/web/src/pages/ChatPage.tsx
@@ -49,6 +49,9 @@ interface Turn {
   seq: number;
   prompt: string;
   blocks: Block[];
+  // notices are what the agent mentioned in passing; they are shown beside the
+  // answer rather than in it, because the turn carried on regardless.
+  notices: string[];
   error: string;
   usage?: DrivenEvent;
   done: boolean;
@@ -78,7 +81,7 @@ function toTurns(events: DrivenEvent[]): Turn[] {
   for (const event of events) {
     switch (event.type) {
     case "prompt":
-      turn = {seq: event.seq, prompt: event.text ?? "", blocks: [], error: "", done: false};
+      turn = {seq: event.seq, prompt: event.text ?? "", blocks: [], notices: [], error: "", done: false};
       turns.push(turn);
       break;
     case "text":
@@ -112,6 +115,11 @@ function toTurns(events: DrivenEvent[]): Turn[] {
         turn.error = event.text ?? "";
       }
       break;
+    case "notice":
+      if (turn && event.text && !turn.notices.includes(event.text)) {
+        turn.notices.push(event.text);
+      }
+      break;
     case "done":
       if (turn) {
         turn.done = true;
@@ -166,6 +174,10 @@ function TurnView({turn}: {turn: Turn}) {
         );
       })}
 
+      {turn.notices.map(notice => (
+        <MessageAlert key={notice} variant="warning" description={notice} />
+      ))}
+
       {turn.error ? <MessageAlert variant="destructive" title={turn.error} /> : null}
 
       {turn.usage ? (
diff --git a/web/src/types.ts b/web/src/types.ts
index 978122c..adc40e6 100644
--- a/web/src/types.ts
+++ b/web/src/types.ts
@@ -553,7 +553,7 @@ export interface DrivenSession {
 
 /** One piece of what a driven agent said, in the shape every agent is flattened into. */
 export interface DrivenEvent {
-  type: "prompt" | "text" | "thinking" | "toolUse" | "toolResult" | "usage" | "done" | "error";
+  type: "prompt" | "text" | "thinking" | "toolUse" | "toolResult" | "usage" | "done" | "error" | "notice";
   seq: number;
   createdTime: string;
   text?: string;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org