fix: preprompt would show after loading session (#8744)

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Toohey
2026-04-23 11:06:27 +12:00
committed by GitHub
parent e7e50320a1
commit 4928ce55e2
5 changed files with 148 additions and 71 deletions
+9 -6
View File
@@ -73,12 +73,15 @@ export async function acpSendMessage(
throw new Error("Session not prepared. Call acpPrepareSession first.");
}
const hasSystem = systemPrompt && systemPrompt.trim().length > 0;
const effectivePrompt = hasSystem
? `<persona-instructions>\n${systemPrompt}\n</persona-instructions>\n\n<user-message>\n${prompt}\n</user-message>`
: prompt;
const content: ContentBlock[] = [{ type: "text", text: effectivePrompt }];
const content: ContentBlock[] = [];
if (systemPrompt?.trim()) {
content.push({
type: "text",
text: systemPrompt,
annotations: { audience: ["assistant"] },
});
}
content.push({ type: "text", text: prompt });
if (images) {
for (const [data, mimeType] of images) {
content.push({ type: "image", data, mimeType } as ContentBlock);
@@ -10,6 +10,7 @@ import {
findLatestUnpairedToolRequest,
} from "@/features/chat/hooks/replayBuffer";
import type {
TextContent,
ToolRequestContent,
ToolResponseContent,
} from "@/shared/types/messages";
@@ -196,32 +197,32 @@ function handleReplay(sessionId: string, update: SessionUpdate): void {
}
case "user_message_chunk": {
if (update.content.type !== "text" || !("text" in update.content)) break;
const messageId = update.messageId ?? crypto.randomUUID();
const buffer = ensureReplayBuffer(sessionId);
const existing = getBufferedMessage(sessionId, messageId);
// biome-ignore lint/suspicious/noExplicitAny: wire format has annotations but SDK types don't
const rawAnn = (update.content as any).annotations;
const ann: TextContent["annotations"] | undefined =
typeof rawAnn === "object" && rawAnn !== null ? rawAnn : undefined;
// Drop assistant-only blocks so they never enter chat state.
if (
!existing &&
update.content.type === "text" &&
"text" in update.content
) {
ann?.audience &&
ann.audience.length > 0 &&
!ann.audience.includes("user")
)
break;
const textBlock = makeTextBlock(update.content.text, ann);
if (!existing) {
buffer.push({
id: messageId,
role: "user",
created: Date.now(),
content: [{ type: "text", text: update.content.text }],
content: [textBlock],
metadata: { userVisible: true, agentVisible: true },
});
} else if (
existing &&
update.content.type === "text" &&
"text" in update.content
) {
const last = existing.content[existing.content.length - 1];
if (last?.type === "text") {
(last as { type: "text"; text: string }).text += update.content.text;
} else {
existing.content.push({ type: "text", text: update.content.text });
}
} else {
existing.content.push(textBlock);
}
break;
}
@@ -486,6 +487,13 @@ function findStreamingMessageId(sessionId: string): string | null {
.streamingMessageId;
}
function makeTextBlock(
text: string,
ann?: TextContent["annotations"],
): TextContent {
return { type: "text", text, ...(ann ? { annotations: ann } : {}) };
}
function findMessageInBuffer(
sessionId: string,
_toolCallId: string,
+17
View File
@@ -33,10 +33,19 @@ export type ChatAttachmentDraft =
// Message roles
export type MessageRole = "user" | "assistant" | "system";
/** ACP audience restriction — which roles may see a content block. */
export type Audience = ("user" | "assistant")[];
/** ACP content-block annotations (mirrors the SDK's Annotations shape). */
export interface ContentAnnotations {
audience?: Audience;
}
// Content block types
export interface TextContent {
type: "text";
text: string;
annotations?: ContentAnnotations;
}
export interface ImageContent {
@@ -44,6 +53,7 @@ export interface ImageContent {
source:
| { type: "base64"; mediaType: string; data: string }
| { type: "url"; url: string };
annotations?: ContentAnnotations;
}
export type ToolCallStatus =
@@ -67,6 +77,7 @@ export interface ToolRequestContent {
status: ToolCallStatus;
/** Epoch ms when the tool call started executing (set on event receipt). */
startedAt?: number;
annotations?: ContentAnnotations;
}
export interface ToolResponseContent {
@@ -75,20 +86,24 @@ export interface ToolResponseContent {
name: string;
result: string;
isError: boolean;
annotations?: ContentAnnotations;
}
export interface ThinkingContent {
type: "thinking";
text: string;
annotations?: ContentAnnotations;
}
export interface RedactedThinkingContent {
type: "redactedThinking";
annotations?: ContentAnnotations;
}
export interface ReasoningContent {
type: "reasoning";
text: string;
annotations?: ContentAnnotations;
}
export interface ActionRequiredContent {
@@ -99,12 +114,14 @@ export interface ActionRequiredContent {
toolName?: string;
arguments?: Record<string, unknown>;
schema?: Record<string, unknown>;
annotations?: ContentAnnotations;
}
export interface SystemNotificationContent {
type: "systemNotification";
notificationType: "compaction" | "info" | "warning" | "error";
text: string;
annotations?: ContentAnnotations;
}
export type MessageContent =