This commit is contained in:
1alyx 2026-01-30 11:55:33 +00:00 committed by GitHub
commit 75f8a42a93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 48 additions and 3 deletions

45
NEWLINE-FIX-TASK.md Normal file
View File

@ -0,0 +1,45 @@
# Task: Fix Leading Newline Bug
## Problem
iMessage responses have 1-2 blank lines prepended. Root cause: Anthropic models in thinking mode emit a `\n\n` text block before the thinking block, and the streaming/flush path doesn't trim leading whitespace.
## Fixes Required (apply ALL THREE for defense in depth)
### Fix 1: `emitBlockChunk` in `src/agents/pi-embedded-subscribe.ts`
Find the line that does `.trimEnd()` on the chunk text and change it to `.trim()`:
```
// Find something like:
const chunk = stripBlockTags(text, state.blockState).trimEnd();
// Change to:
const chunk = stripBlockTags(text, state.blockState).trim();
```
### Fix 2: `collapseConsecutiveDuplicateBlocks` in `src/agents/pi-embedded-helpers/errors.ts`
Fix the early return to use trimmed text instead of original:
```
// Find:
if (blocks.length < 2) return text;
// Change to:
if (blocks.length < 2) return trimmed;
```
### Fix 3: Safety net in `sanitizeUserFacingText` in same file (`errors.ts`)
Make sure the final return trims:
```
// Find the final return of collapseConsecutiveDuplicateBlocks(stripped)
// Change to:
return collapseConsecutiveDuplicateBlocks(stripped).trim();
```
## Rules
- Edit the TypeScript SOURCE files in `src/`, NOT the compiled `dist/` files
- Build after changes: `npm run build` or `npx tsc`
- Commit with message: `fix: trim leading newlines from streaming/block-flush text output`
- Co-author: `Co-authored-by: Zach Canepa <zcanepa19@gmail.com>`
- Push to branch: `fix/leading-newline-trim`
- Do NOT create a PR, just push
## Verification
After building, check that the compiled JS in `dist/` reflects your changes:
- `dist/agents/pi-embedded-subscribe.js` should have `.trim()` not `.trimEnd()`
- `dist/agents/pi-embedded-helpers/errors.js` should return `trimmed` not `text` in early return

View File

@ -81,7 +81,7 @@ function collapseConsecutiveDuplicateBlocks(text: string): string {
const trimmed = text.trim();
if (!trimmed) return text;
const blocks = trimmed.split(/\n{2,}/);
if (blocks.length < 2) return text;
if (blocks.length < 2) return trimmed;
const normalizeBlock = (value: string) => value.trim().replace(/\s+/g, " ");
const result: string[] = [];
@ -344,7 +344,7 @@ export function sanitizeUserFacingText(text: string): string {
return formatRawAssistantErrorForUi(trimmed);
}
return collapseConsecutiveDuplicateBlocks(stripped);
return collapseConsecutiveDuplicateBlocks(stripped).trim();
}
export function isRateLimitAssistantError(msg: AssistantMessage | undefined): boolean {

View File

@ -359,7 +359,7 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
const emitBlockChunk = (text: string) => {
if (state.suppressBlockChunks) return;
// Strip <think> and <final> blocks across chunk boundaries to avoid leaking reasoning.
const chunk = stripBlockTags(text, state.blockState).trimEnd();
const chunk = stripBlockTags(text, state.blockState).trim();
if (!chunk) return;
if (chunk === state.lastBlockReplyText) return;