diff --git a/src/agents/pi-embedded-block-chunker.ts b/src/agents/pi-embedded-block-chunker.ts index 798ea4e2d..9aa97afad 100644 --- a/src/agents/pi-embedded-block-chunker.ts +++ b/src/agents/pi-embedded-block-chunker.ts @@ -48,6 +48,8 @@ export class EmbeddedBlockChunker { } drain(params: { force: boolean; emit: (chunk: string) => void }) { + // KNOWN: We cannot split inside fenced code blocks (Markdown breaks + UI glitches). + // When forced (maxChars), we close + reopen the fence to keep Markdown valid. const { force, emit } = params; const minChars = Math.max(1, Math.floor(this.#chunking.minChars)); const maxChars = Math.max(minChars, Math.floor(this.#chunking.maxChars)); diff --git a/src/agents/pi-embedded-subscribe.ts b/src/agents/pi-embedded-subscribe.ts index dd851e90d..69bbecf65 100644 --- a/src/agents/pi-embedded-subscribe.ts +++ b/src/agents/pi-embedded-subscribe.ts @@ -177,6 +177,9 @@ export function subscribeEmbeddedPiSession(params: { const blockChunker = blockChunking ? new EmbeddedBlockChunker(blockChunking) : null; + // KNOWN: Provider streams are not strictly once-only or perfectly ordered. + // `text_end` can repeat full content; late `text_end` can arrive after `message_end`. + // Tests: `src/agents/pi-embedded-subscribe.test.ts` (e.g. late text_end cases). const shouldEmitToolResult = () => typeof params.shouldEmitToolResult === "function" ? params.shouldEmitToolResult() @@ -231,6 +234,8 @@ export function subscribeEmbeddedPiSession(params: { if (evt.type === "message_start") { const msg = (evt as AgentEvent & { message: AgentMessage }).message; if (msg?.role === "assistant") { + // KNOWN: Resetting at `text_end` is unsafe (late/duplicate end events). + // ASSUME: `message_start` is the only reliable boundary for “new assistant message begins”. // Start-of-message is a safer reset point than message_end: some providers // may deliver late text_end updates after message_end, which would // otherwise re-trigger block replies. @@ -387,6 +392,8 @@ export function subscribeEmbeddedPiSession(params: { if (delta) { chunk = delta; } else if (content) { + // KNOWN: Some providers resend full content on `text_end`. + // We only append a suffix (or nothing) to keep output monotonic. // Providers may resend full content on text_end; append only the suffix. if (content.startsWith(deltaBuffer)) { chunk = content.slice(deltaBuffer.length); diff --git a/src/agents/pi-tool-definition-adapter.ts b/src/agents/pi-tool-definition-adapter.ts index 48b33730a..fdb0777f4 100644 --- a/src/agents/pi-tool-definition-adapter.ts +++ b/src/agents/pi-tool-definition-adapter.ts @@ -24,6 +24,8 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] { _ctx, signal, ): Promise> => { + // KNOWN: pi-coding-agent `ToolDefinition.execute` has a different signature/order + // than pi-agent-core `AgentTool.execute`. This adapter keeps our existing tools intact. return tool.execute(toolCallId, params, signal, onUpdate); }, } satisfies ToolDefinition; diff --git a/src/media/parse.ts b/src/media/parse.ts index 4f7c21e5c..2a01156c4 100644 --- a/src/media/parse.ts +++ b/src/media/parse.ts @@ -27,6 +27,8 @@ export function splitMediaFromOutput(raw: string): { mediaUrls?: string[]; mediaUrl?: string; // legacy first item for backward compatibility } { + // KNOWN: Leading whitespace is semantically meaningful in Markdown (lists, indented fences). + // We only trim the end; token cleanup below handles removing `MEDIA:` lines. const trimmedRaw = raw.trimEnd(); if (!trimmedRaw.trim()) return { text: "" };