mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
fix(ui): drop assistant-only ACP text chunks
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -333,7 +333,7 @@ describe("acpNotificationHandler", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves live text annotations and splits chunks when audience changes", async () => {
|
||||
it("drops assistant-only live text chunks while preserving visible annotations", async () => {
|
||||
registerSession("draft-session-5", "goose-session-5", "goose", "/tmp");
|
||||
|
||||
await handleSessionNotification({
|
||||
@@ -358,9 +358,9 @@ describe("acpNotificationHandler", () => {
|
||||
messageId: "message-1",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "prompt",
|
||||
text: "Visible ",
|
||||
annotations: {
|
||||
audience: ["assistant"],
|
||||
audience: ["user"],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -373,7 +373,10 @@ describe("acpNotificationHandler", () => {
|
||||
messageId: "message-1",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "Visible reply",
|
||||
text: "reply",
|
||||
annotations: {
|
||||
audience: ["user"],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as SessionNotification);
|
||||
@@ -382,21 +385,17 @@ describe("acpNotificationHandler", () => {
|
||||
useChatStore.getState().messagesBySession["draft-session-5"]?.[0]
|
||||
?.content,
|
||||
).toEqual([
|
||||
{
|
||||
type: "text",
|
||||
text: "internal prompt",
|
||||
annotations: {
|
||||
audience: ["assistant"],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Visible reply",
|
||||
annotations: {
|
||||
audience: ["user"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves replay text annotations on user message chunks", async () => {
|
||||
it("drops assistant-only replay text chunks while preserving visible annotations", async () => {
|
||||
registerSession("draft-session-6", "goose-session-6", "goose", "/tmp");
|
||||
useChatStore.setState({
|
||||
loadingSessionIds: new Set(["draft-session-6"]),
|
||||
@@ -424,23 +423,37 @@ describe("acpNotificationHandler", () => {
|
||||
messageId: "message-1",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "Visible prompt",
|
||||
text: "Visible ",
|
||||
annotations: {
|
||||
audience: ["user"],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as SessionNotification);
|
||||
|
||||
await handleSessionNotification({
|
||||
sessionId: "goose-session-6",
|
||||
update: {
|
||||
sessionUpdate: "user_message_chunk",
|
||||
messageId: "message-1",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "prompt",
|
||||
annotations: {
|
||||
audience: ["user"],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as SessionNotification);
|
||||
|
||||
const buffer = getAndDeleteReplayBuffer("draft-session-6");
|
||||
expect(buffer?.[0]?.content).toEqual([
|
||||
{
|
||||
type: "text",
|
||||
text: "internal prompt",
|
||||
annotations: {
|
||||
audience: ["assistant"],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Visible prompt",
|
||||
annotations: {
|
||||
audience: ["user"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "./toolCallBlockBuilders";
|
||||
import {
|
||||
appendTextContent,
|
||||
isUserVisibleTextAnnotations,
|
||||
normalizeTextAnnotations,
|
||||
} from "./textContentBlocks";
|
||||
|
||||
@@ -180,61 +181,69 @@ export function clearReplayPerf(sessionId: string): void {
|
||||
function handleReplay(sessionId: string, update: SessionUpdate): void {
|
||||
switch (update.sessionUpdate) {
|
||||
case "agent_message_chunk": {
|
||||
const messageId = update.messageId ?? crypto.randomUUID();
|
||||
const buffer = ensureReplayBuffer(sessionId);
|
||||
if (!getBufferedMessage(sessionId, messageId)) {
|
||||
buffer.push({
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
created: Date.now(),
|
||||
content: [],
|
||||
metadata: {
|
||||
userVisible: true,
|
||||
agentVisible: true,
|
||||
completionStatus: "inProgress",
|
||||
},
|
||||
});
|
||||
}
|
||||
const msg = getBufferedMessage(sessionId, messageId);
|
||||
if (msg && update.content.type === "text" && "text" in update.content) {
|
||||
if (update.content.type === "text" && "text" in update.content) {
|
||||
const annotations = normalizeTextAnnotations(
|
||||
update.content.annotations,
|
||||
);
|
||||
if (!isUserVisibleTextAnnotations(annotations)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const messageId = update.messageId ?? crypto.randomUUID();
|
||||
const buffer = ensureReplayBuffer(sessionId);
|
||||
if (!getBufferedMessage(sessionId, messageId)) {
|
||||
buffer.push({
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
created: Date.now(),
|
||||
content: [],
|
||||
metadata: {
|
||||
userVisible: true,
|
||||
agentVisible: true,
|
||||
completionStatus: "inProgress",
|
||||
},
|
||||
});
|
||||
}
|
||||
const msg = getBufferedMessage(sessionId, messageId);
|
||||
if (!msg) {
|
||||
break;
|
||||
}
|
||||
msg.content = appendTextContent(
|
||||
msg.content,
|
||||
update.content.text,
|
||||
normalizeTextAnnotations(update.content.annotations),
|
||||
annotations,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "user_message_chunk": {
|
||||
const messageId = update.messageId ?? crypto.randomUUID();
|
||||
const buffer = ensureReplayBuffer(sessionId);
|
||||
const existing = getBufferedMessage(sessionId, messageId);
|
||||
if (
|
||||
!existing &&
|
||||
update.content.type === "text" &&
|
||||
"text" in update.content
|
||||
) {
|
||||
if (update.content.type === "text" && "text" in update.content) {
|
||||
const annotations = normalizeTextAnnotations(
|
||||
update.content.annotations,
|
||||
);
|
||||
buffer.push({
|
||||
id: messageId,
|
||||
role: "user",
|
||||
created: Date.now(),
|
||||
content: [{ type: "text", text: update.content.text, annotations }],
|
||||
metadata: { userVisible: true, agentVisible: true },
|
||||
});
|
||||
} else if (
|
||||
existing &&
|
||||
update.content.type === "text" &&
|
||||
"text" in update.content
|
||||
) {
|
||||
existing.content = appendTextContent(
|
||||
existing.content,
|
||||
update.content.text,
|
||||
normalizeTextAnnotations(update.content.annotations),
|
||||
);
|
||||
if (!isUserVisibleTextAnnotations(annotations)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const messageId = update.messageId ?? crypto.randomUUID();
|
||||
const buffer = ensureReplayBuffer(sessionId);
|
||||
const existing = getBufferedMessage(sessionId, messageId);
|
||||
if (!existing) {
|
||||
buffer.push({
|
||||
id: messageId,
|
||||
role: "user",
|
||||
created: Date.now(),
|
||||
content: [{ type: "text", text: update.content.text, annotations }],
|
||||
metadata: { userVisible: true, agentVisible: true },
|
||||
});
|
||||
} else {
|
||||
existing.content = appendTextContent(
|
||||
existing.content,
|
||||
update.content.text,
|
||||
annotations,
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -299,35 +308,39 @@ function handleLive(
|
||||
|
||||
switch (update.sessionUpdate) {
|
||||
case "agent_message_chunk": {
|
||||
const messageId =
|
||||
update.messageId ??
|
||||
presetMessageIds.get(gooseSessionId) ??
|
||||
crypto.randomUUID();
|
||||
const existing = store.messagesBySession[sessionId]?.find(
|
||||
(m) => m.id === messageId,
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
store.addMessage(sessionId, {
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
created: Date.now(),
|
||||
content: [],
|
||||
metadata: {
|
||||
userVisible: true,
|
||||
agentVisible: true,
|
||||
completionStatus: "inProgress",
|
||||
},
|
||||
});
|
||||
store.setPendingAssistantProvider(sessionId, null);
|
||||
store.setStreamingMessageId(sessionId, messageId);
|
||||
}
|
||||
|
||||
if (update.content.type === "text" && "text" in update.content) {
|
||||
const text = update.content.text;
|
||||
const annotations = normalizeTextAnnotations(
|
||||
update.content.annotations,
|
||||
);
|
||||
if (!isUserVisibleTextAnnotations(annotations)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const messageId =
|
||||
update.messageId ??
|
||||
presetMessageIds.get(gooseSessionId) ??
|
||||
crypto.randomUUID();
|
||||
const existing = store.messagesBySession[sessionId]?.find(
|
||||
(m) => m.id === messageId,
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
store.addMessage(sessionId, {
|
||||
id: messageId,
|
||||
role: "assistant",
|
||||
created: Date.now(),
|
||||
content: [],
|
||||
metadata: {
|
||||
userVisible: true,
|
||||
agentVisible: true,
|
||||
completionStatus: "inProgress",
|
||||
},
|
||||
});
|
||||
store.setPendingAssistantProvider(sessionId, null);
|
||||
store.setStreamingMessageId(sessionId, messageId);
|
||||
}
|
||||
|
||||
const text = update.content.text;
|
||||
store.setStreamingMessageId(sessionId, messageId);
|
||||
store.updateMessage(sessionId, messageId, (msg) => ({
|
||||
...msg,
|
||||
|
||||
@@ -24,6 +24,13 @@ export function normalizeTextAnnotations(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function isUserVisibleTextAnnotations(
|
||||
annotations?: ContentAnnotations,
|
||||
): boolean {
|
||||
const audience = annotations?.audience;
|
||||
return !audience || audience.length === 0 || audience.includes("user");
|
||||
}
|
||||
|
||||
function sameTextAnnotations(
|
||||
left?: ContentAnnotations,
|
||||
right?: ContentAnnotations,
|
||||
|
||||
Reference in New Issue
Block a user