diff --git a/packages/snapcompact/src/prompts/snapcompact-summary.md b/packages/snapcompact/src/prompts/snapcompact-summary.md index 182f23d0a..f74a4e7bc 100644 --- a/packages/snapcompact/src/prompts/snapcompact-summary.md +++ b/packages/snapcompact/src/prompts/snapcompact-summary.md @@ -1,6 +1,11 @@ Prior conversation history has been archived verbatim onto {{frameCount}} snapcompact frame{{#if multipleFrames}}s{{/if}} — the bitmap image{{#if multipleFrames}}s{{/if}} attached below{{#if multipleFrames}}, ordered oldest to newest{{/if}}. -Reading a frame: monospace {{fontCell}} pixel font on a white background, {{#if docColumns}}typeset as two word-wrapped newspaper columns of {{cols}} characters by {{rows}} lines each — read the left column top to bottom, then the right column{{else}}{{cols}} characters per row, {{rows}} text rows per frame; read left to right, top to bottom. Text flows continuously with no word wrap, so words may break across row ends{{/if}}. Horizontal whitespace runs were collapsed to single spaces; line breaks print as a solid black cell (one character wide) — treat each as a newline. {{#if sentenceInk}}Ink color cycles through six colors, advancing at sentence boundaries — a color change marks a new sentence.{{else}}Glyphs are plain black ink.{{/if}}{{#if stopwordDimmed}} Common function words (the, of, and, …) are printed in dim gray; content words carry the full ink.{{/if}}{{#if dimmedToolResults}} Tool output is printed in dim gray ink — gray text is archived tool output, not conversation.{{/if}}{{#if lineRepeated}} Every text line is printed twice in a row — first on the white background, then repeated on a pale yellow band. The copies are identical: read each line once and use the duplicate only to double-check hard glyphs.{{/if}} Roles are tagged inline as [User]:, [Assistant]:, [Think]:, [Tool Call]:, and [Tool Result]:. +Reading a frame: a grid {{cols}} cells wide and up to {{rows}} tall{{#if docColumns}}, set as two word-wrapped columns — read the left column top to bottom, then the right{{else}} — read left to right, top to bottom, with no word wrap, so words may break across rows{{/if}}. A solid black cell is a newline; runs of spaces collapse to one. Turns are headed # User ¶, # Assistant ¶, or # Tool call ¶; assistant reasoning is _italic_ and tool output sits in …. +{{#if sentenceInk}}- Ink cycles six colors, one per sentence. +{{/if}}{{#if stopwordDimmed}}- Function words are dim gray; content words keep full ink. +{{/if}}{{#if dimmedToolResults}}- Text inside is dim gray; that gray is archived tool output, not conversation. +{{/if}}{{#if lineRepeated}}- Each line is printed twice (white, then a pale-yellow band); the copies are identical. +{{/if}} {{#if mixedShapes}} Older frames may use a different font, grid, or ink coloring than described above; the reading order is always the same (left to right, top to bottom, oldest frame first). diff --git a/packages/snapcompact/src/snapcompact.ts b/packages/snapcompact/src/snapcompact.ts index bfdd71fe1..1389f3acc 100644 --- a/packages/snapcompact/src/snapcompact.ts +++ b/packages/snapcompact/src/snapcompact.ts @@ -740,9 +740,11 @@ export function serializeConversation(messages: Message[], options?: SerializeOp for (const block of msg.content) { if (block.type === "text") { - pendingText.push(stripDimMarkers(block.text)); + const text = stripDimMarkers(block.text); + if (text.trim()) pendingText.push(text); } else if (block.type === "thinking") { - pendingThinking.push(stripDimMarkers(block.thinking)); + const thinking = stripDimMarkers(block.thinking); + if (thinking.trim()) pendingThinking.push(thinking); } else if (block.type === "toolCall") { if (uselessCallIds.has(block.id)) continue; flushAssistant(); diff --git a/packages/snapcompact/test/snapcompact.test.ts b/packages/snapcompact/test/snapcompact.test.ts index b4fb20b97..6cb6df38a 100644 --- a/packages/snapcompact/test/snapcompact.test.ts +++ b/packages/snapcompact/test/snapcompact.test.ts @@ -609,6 +609,22 @@ describe("serializeConversation", () => { expect(out).toBe("# Assistant ¶\nbefore\nafter"); }); + it("drops blank text/thinking blocks instead of emitting an empty assistant heading", () => { + const out = snapcompact.serializeConversation( + [ + createAssistantMessage([ + { type: "thinking", thinking: " " }, + { type: "text", text: "" }, + { type: "toolCall", id: "c1", name: "read", arguments: { path: "a.ts" } }, + ]), + { ...createToolResultMessage("body"), toolCallId: "c1" } as Message, + ], + { dimToolResults: false }, + ); + expect(out).toBe('# Tool call ¶\nread(path="a.ts")\n\nbody\n'); + expect(out).not.toContain("# Assistant ¶"); + }); + it("wraps tool-result bodies in dim toggles by default and strips stray toggles from content", () => { const out = snapcompact.serializeConversation([ createUserMessage(`hello ${snapcompact.DIM_ON}world`),