@oh-my-pi/snapcompact 17.0.8 → 17.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.1.0] - 2026-07-24
6
+
7
+ ### Added
8
+
9
+ - Added an `includeThinking` serialization option (defaulting to `true`) to allow excluding assistant reasoning (`¶think:` sections) from archived transcripts.
10
+
5
11
  ## [16.5.0] - 2026-07-13
6
12
 
7
13
  ### Changed
@@ -479,6 +479,12 @@ export interface SerializeOptions {
479
479
  /** Print tool-result text in dim gray ink so archived conversation reads
480
480
  * louder than archived tool noise. Defaults to `true`. */
481
481
  dimToolResults?: boolean;
482
+ /** Serialize assistant reasoning as `¶think:` sections. Defaults to `true`.
483
+ * Callers archiving for a Claude/Anthropic-dialect model set this `false`:
484
+ * the archive frames are replayed as text into every later request, and
485
+ * reasoning rendered back to Claude trips its `reasoning_extraction`
486
+ * classifier (issue #6093). */
487
+ includeThinking?: boolean;
482
488
  }
483
489
  export declare function serializeConversation(messages: Message[], options?: SerializeOptions): string;
484
490
  /** Printed in place of newline runs: the native renderer fills this cell
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/snapcompact",
4
- "version": "17.0.8",
4
+ "version": "17.1.0",
5
5
  "description": "Bitmap-frame context compression for vision-capable LLMs",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -31,10 +31,10 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-ai": "17.0.8",
35
- "@oh-my-pi/pi-natives": "17.0.8",
36
- "@oh-my-pi/pi-utils": "17.0.8",
37
- "@oh-my-pi/pi-wire": "17.0.8"
34
+ "@oh-my-pi/pi-ai": "17.1.0",
35
+ "@oh-my-pi/pi-natives": "17.1.0",
36
+ "@oh-my-pi/pi-utils": "17.1.0",
37
+ "@oh-my-pi/pi-wire": "17.1.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/bun": "^1.3.14"
@@ -735,6 +735,12 @@ export interface SerializeOptions {
735
735
  /** Print tool-result text in dim gray ink so archived conversation reads
736
736
  * louder than archived tool noise. Defaults to `true`. */
737
737
  dimToolResults?: boolean;
738
+ /** Serialize assistant reasoning as `¶think:` sections. Defaults to `true`.
739
+ * Callers archiving for a Claude/Anthropic-dialect model set this `false`:
740
+ * the archive frames are replayed as text into every later request, and
741
+ * reasoning rendered back to Claude trips its `reasoning_extraction`
742
+ * classifier (issue #6093). */
743
+ includeThinking?: boolean;
738
744
  }
739
745
 
740
746
  /** Keep the head and tail of `text`, eliding the middle beyond `maxChars`. */
@@ -773,6 +779,7 @@ export function serializeConversation(messages: Message[], options?: SerializeOp
773
779
  const toolCallMaxChars = options?.toolCallMaxChars ?? TOOL_CALL_MAX_CHARS;
774
780
  const headRatio = options?.truncateHeadRatio ?? TRUNCATE_HEAD_RATIO;
775
781
  const dimToolResults = options?.dimToolResults !== false;
782
+ const includeThinking = options?.includeThinking !== false;
776
783
  const parts: string[] = [];
777
784
  let lastPrefix: string | null = null;
778
785
 
@@ -845,6 +852,7 @@ export function serializeConversation(messages: Message[], options?: SerializeOp
845
852
  const text = stripDimMarkers(block.text);
846
853
  if (text.trim()) pendingText.push(text);
847
854
  } else if (block.type === "thinking") {
855
+ if (!includeThinking) continue;
848
856
  const thinking = stripDimMarkers(block.thinking);
849
857
  if (thinking.trim()) pendingThinking.push(thinking);
850
858
  } else if (block.type === "toolCall") {
@@ -1833,6 +1841,29 @@ function planArchive(text: string, high: Shape, low: Shape, maxFrames: number):
1833
1841
  };
1834
1842
  }
1835
1843
 
1844
+ /**
1845
+ * Drop `¶think:` sections from serialized archive source text.
1846
+ *
1847
+ * Archives written before {@link SerializeOptions.includeThinking} existed bake
1848
+ * reasoning into their kept source; replaying it to Claude trips the
1849
+ * `reasoning_extraction` classifier (issue #6093). Re-compaction re-renders the
1850
+ * whole unfolded source, so scrubbing the prior text heals a poisoned session
1851
+ * at its next compaction. Conservative by construction: only sections that
1852
+ * start with `¶think:` at a section boundary are dropped.
1853
+ */
1854
+ function stripThinkingSections(text: string): string {
1855
+ return text
1856
+ .split(NEWLINE_GLYPH)
1857
+ .map(segment =>
1858
+ segment
1859
+ .split(/\n\n(?=¶(?:user|think|ai|call):)/)
1860
+ .filter(section => !section.startsWith("¶think:"))
1861
+ .join("\n\n"),
1862
+ )
1863
+ .filter(segment => segment.length > 0)
1864
+ .join(NEWLINE_GLYPH);
1865
+ }
1866
+
1836
1867
  /**
1837
1868
  * Run a snapcompact compaction over prepared messages. Fully local: serializes
1838
1869
  * the discarded history, appends it to the accumulated archive source text, and
@@ -1858,11 +1889,18 @@ export async function compact<TMessage = Message>(
1858
1889
  const llmMessages = (options?.convertToLlm ?? defaultConvertToLlm)(messages);
1859
1890
  const serialized = serializeConversation(llmMessages, options);
1860
1891
  const previousArchive = getPreservedArchive(previousPreserveData);
1861
- const previousText =
1892
+ const previousTextRaw =
1862
1893
  previousArchive?.text ??
1863
1894
  [previousArchive?.textHead, previousArchive?.textTail]
1864
1895
  .filter((part): part is string => typeof part === "string" && part.length > 0)
1865
1896
  .join(NEWLINE_GLYPH);
1897
+ // Legacy archives may carry `¶think:` sections from before includeThinking
1898
+ // existed; scrub them when this compaction excludes thinking so the
1899
+ // re-rendered archive stops replaying reasoning (issue #6093).
1900
+ const previousText =
1901
+ options?.includeThinking === false && previousTextRaw.length > 0
1902
+ ? stripThinkingSections(previousTextRaw)
1903
+ : previousTextRaw;
1866
1904
  const hasPreviousText = previousText.length > 0;
1867
1905
  const includedPreviousSummary = !hasPreviousText && !!previousSummary;
1868
1906
  const shapeProbeText = renderabilityProbeText(serialized, previousPreserveData, previousSummary);