First real-data export against v0.4.0 surfaced 66 unknown blocks across three content types — captured live and added. Added: - execution_output (Code Interpreter / container.exec / python tool output) → tool_result block. output=content.text, tool_name=author.name, is_error=metadata.aggregate_result.status, summary=metadata.reasoning_title - system_error → error tool_result with tool_name=author.name - tether_browsing_display: spinner placeholders (empty result+summary) skip silently with DEBUG log; defensive populated-case branch maps to tool_result (untested in real data) - tool_result block schema: optional `summary` field rendered as italic line between header and fence - tool_result rendering: tool_name appears in header when present (e.g. `📤 Result: container.exec`); existing tool_name=None calls unchanged - _ROLE_LABELS["tool"] = ("🔧 Tool", "tool") Fixed: - chatgpt.normalize_conversation reads `conversation_id` as fallback for `id`. Live API uses conversation_id; fixtures use id. Pre-fix: empty id in YAML frontmatter and missing context in WARNING logs. Tests: 11 new (192 total, 0 failures). Fixture extended with 4 tool-output cases (execution_output success, empty execution_output that should skip, system_error, tether_browsing_display spinner). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6.2 KiB
6.2 KiB
Changelog
All notable changes to this project will be documented here. Format follows Keep a Changelog.
[0.4.1] - Unreleased
Added
- ChatGPT
execution_output(Code Interpreter /container.exec/python) renders as atool_resultblock withtool_namefromauthor.name,is_errorfrommetadata.aggregate_result.status, and the optionalsummaryline populated frommetadata.reasoning_title. Captured live during planning. - ChatGPT
system_errorcontent (e.g. browse-service 503) renders as an errortool_resultblock withtool_namefromauthor.name(typically"web"). - ChatGPT
tether_browsing_displaypopulated case (defensive, not observed in real data) renders as atool_resultblock; transient spinner placeholders (emptyresult+summary) skip silently with DEBUG log. tool_resultblock schema gains optionalsummary: str | Nonefield, rendered as italic line between header and fenced output.tool_resultrendering showstool_namein the header when present (e.g.📤 **Result: container.exec**); when absent, header stays as📤 **Result**(no regression).- Markdown exporter:
_ROLE_LABELS["tool"] = ("🔧 Tool", "tool")so tool-role messages render under a recognisable header instead of the generic fallback. - 11 new tests covering all four cases plus the conv_id fallback (192 total, all passing).
Fixed
- ChatGPT
normalize_conversationnow readsconversation_idas a fallback forid. Live ChatGPT detail responses useconversation_idat top level; fixtures and listing summaries useid. Without the fallback, normalized conversations had emptyid(visible as blankconversation_id:in YAML frontmatter and missing context in WARNING log lines).
Migration
- No new schema breaks;
tool_resultblocks gain asummaryfield that defaults to None on legacy data. Existing exports re-render cleanly with the cache-clear-and-export workflow from v0.4.0.
[0.4.0] - Unreleased
Added
- Rich content support: messages now carry an ordered
blockslist (text, code, thinking, tool_use, tool_result, citation, image_placeholder, file_placeholder, unknown) - ChatGPT voice mode:
audio_transcriptionparts render as text blocks;audio_asset_pointerandreal_time_user_audio_video_asset_pointerrender as📎 File attachedplaceholders with size and duration metadata - ChatGPT Custom Instructions:
user_editable_contextandmodel_editable_contextmessages now appear in exports (were silently dropped — pre-existing bug fixed); rendered with a> ℹ️ Hidden contextmarker driven by theis_visually_hidden_from_conversationflag - Image placeholders for
image_asset_pointerparts (uploads + DALL-E) insidemultimodal_textand at message level - Defensive Claude block extraction:
text,thinking,tool_use,tool_result(including nested-block flattening),imageblocks (untested against real data; will fix-forward in v0.4.1 if real shapes diverge) LossReportsummary table emitted at end of everyexportrun, breaking downunknown blocksandextraction failuresby raw type so silently-dropped data becomes visible_safe_fencehelper picks a fence longer than any backtick run in extracted content, preventing embedded triple-backticks from corrupting downstream rendering (verified live in Joplin during planning)unknownblocks render as> ⚠️ Unsupported contentwith the raw type, observed top-level keys, and reason — so future API additions are visible rather than silent
Changed
- ChatGPT role filter (previously dropped
toolandsystemmessages) is lifted: all roles now route through normal extraction; truly empty messages skip via the existing empty-content guard - Markdown rendering moves from provider-time to exporter-write-time. Providers produce blocks; exporters call
render_blocks_to_markdownat write time. This unblocks future Obsidian/HTML exporters BaseProvider.normalize_conversationsignature now accepts an optionalLossReportparameter (breaking change for any future custom subclass; FileProvider hasn't shipped yet)o1/o3reasoning subparts insidetextcontent_type messages remain rendered as plain text (defensive; reclassification tothinkingblock deferred until live shape is captured)
Fixed
user_editable_context/model_editable_contextextraction (parts-vs-direct-fields mismatch) — Custom Instructions are no longer silently dropped from every conversation
Migration
- Existing exports are not re-rendered automatically. To pick up v0.4.0 rendering for previously exported conversations:
python -m src.main cache --clear python -m src.main export --provider all - JSON exports: messages now contain
blocks(typed structured content) and may omit the legacycontentfield. External consumers reading JSON should preferblocks. - Per-conversation message counts may increase: previously-dropped Custom Instructions, image-only user turns, and tool-only assistant turns now appear.
Out of scope (deferred to v0.5.0+)
- Binary downloads of images and audio assets (placeholders show metadata only;
content not preserved in this export) - Joplin resource upload for embedded media
- Filename resolution for
file-XYZ/sediment://references - Speculative ChatGPT types (
tether_browsing_display,tether_quote) and DALL-E assistant images — fall through tounknownblocks if encountered
[0.2.0] - Unreleased
Added
- Joplin import automation:
joplincommand syncs exported Markdown files to Joplin as notes - Notebooks created automatically per provider+project (
ChatGPT - My Project, etc.) - Re-running is safe: notes are updated, not duplicated (Joplin note ID stored in manifest)
JOPLIN_API_TOKEN,JOPLIN_API_URL,JOPLIN_REQUEST_TIMEOUTconfig variables- Configurable request timeout with clear error messages and actionable hints on timeout
--projectfilter onexportandlistcommands (case-insensitive substring ornone)- ChatGPT Projects support via
CHATGPT_PROJECT_IDSenv var
[0.1.0] - Unreleased
Added
- Initial implementation: ChatGPT and Claude export via internal web APIs
- Markdown and JSON exporters
- Local cache/manifest for incremental sync
- CLI with export, list, cache, doctor, and auth commands