No description
  • Swift 98.7%
  • Shell 1.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Gabriel 4ae27da19a Harden every reader and writer after a full bug hunt
A line-by-line audit (cross-checked against the real stores on this Mac)
found path traversal, multi-gigabyte memory spikes, crashes on corrupt data,
silent scan failures and several data-loss paths. All are fixed:

Security
- omp `blob:sha256:<hash>` must be a bare 64-hex digest; `../../.ssh/id_rsa`
  used to be read and embedded into the imported conversation.
- Vibe and Copilot attachment paths (absolute, outside the session folder in
  real data) are only read when the file is a regular file <= 32 MB whose bytes
  are really an image (magic-byte check).
- A project folder ending in `..` no longer makes Freebuff imports write
  outside ~/.config/manicode/projects: cwd is standardized for every target and
  the Freebuff project name is sanitized.
- Session text renders inline markdown without clickable links.

Memory
- JSONL files are streamed in 1 MB chunks with an autorelease pool per chunk;
  Claude and pi/omp reads index parent links + line offsets and re-read only
  the active branch. Listing a real 1.3 GB Claude session went from 1.3 GB to
  30 MB RSS, opening it from ~1.4 GB to 116 MB.
- Goose/OpenCode scans stream rows instead of loading every user message;
  whole-document JSON reads are size-capped; thumbnails are downsampled off
  the main thread.

Crashes and data integrity
- Timestamps: unit classified by magnitude (s/ms/us/ns), NaN/inf/absurd
  values rejected; Double->Int64 conversions can no longer trap.
- SQLite: text keeps embedded NULs, UTF-8 BLOBs read as text, scalar() reads
  the first selected column.
- Writes are 0600 from the first byte and atomic; Aider transcripts and
  Freebuff chat folders are claimed exclusively (no clobbering on races).
- Codex index appends use O_APPEND and repair a missing trailing newline;
  the installed version comes from the newest rollout's cli_version.
- Messages without a timestamp keep their position in time-ordered stores
  (Hermes, OpenCode v1, Freebuff, Goose, Copilot).
- OpenCode IDs are ascending like the CLI's own; existing v1 project files are
  no longer overwritten; git lookups time out after 5 s.
- Goose session ids are allocated inside the write transaction.

Format fidelity
- Aider: consecutive `#### ` lines form one user message (as aider parses
  them), so multi-line prompts survive; escaped marker lines read back clean.
- Codex: injected `<environment_context>` / AGENTS.md user messages are no
  longer imported as human turns; tool-output images are kept.
- Copilot: compaction keeps the recent messages Copilot itself keeps; results
  stay in completion order; workspace.yaml strings are always quoted.
- Vibe: provider read from the `config.models` list.
- Freebuff: dates roll over past midnight and accept folders without ms.

Reporting and app
- A store that exists but cannot be read is reported (warning in the sidebar
  and list) instead of looking empty; one bad Copilot/Codex file no longer
  hides the others.
- Rescans requested during a scan are queued; a deleted session's
  conversation is cleared; Terminal failures show an alert; the import preview
  is computed once per target.

Packaging
- The installer refuses sudo, checks bundle id + codesign before clearing the
  quarantine, and replaces the old app only after a verified staged copy;
  build_app.sh does the same. Docs updated; jimmy/ ignored.

Verified: 72 tests, 0 failures (10 opt-in probes skipped); the 15 new
regression tests fail on the previous code (one crashed the test process on
Int64(inf)). RealStoreProbe: all 11 harnesses scan and load with no unreadable
entries. Release app launched and opened the 1.3 GB session (407 MB RSS).

Bump to 1.3.5 (build 9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model: anthropic/claude-opus-5-5
2026-11-07 16:57:57 +01:00
scripts Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00
Sources Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00
Support Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00
Tests/AgentChatCoreTests Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00
.gitignore Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00
Package.swift Support every installed agent harness end to end 2026-11-02 19:53:53 +01:00
project.yml Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00
README.md Harden every reader and writer after a full bug hunt 2026-11-07 16:57:57 +01:00

Agent Chat Importer

A native macOS app that finds every chat session from your coding agents and copies any of them into another agent, so you can continue the same conversation there with its full context: every human turn, AI reply, reasoning block, tool call, tool result and image.

Claude Code, pi, Oh My Pi, Mistral Vibe, Freebuff, OpenCode (v1 and v2), Codex, Goose, Aider, GitHub Copilot CLI and Hermes are all supported as sources and as import targets.


Install (no coding needed)

You need: a Mac running macOS 14 (Sonoma) or newer, and at least one of the agents above already used on your Mac. Not sure of your macOS version? Click the menu → About This Mac.

Step 1: Download

  1. Open the Releases page and click the latest version (for example v1.3.5).
  2. Under Assets, click Agent-Chat-Importer-1.3.5.dmg to download it. (If your browser or chat app doesn't like the .dmg, download ...-dmg.zip instead and double-click it to unzip.)

Step 2: Install

Easy way: double-click Installer Agent Chat Importer.command in the disk image. Terminal opens, copies the app to Applications, clears the macOS quarantine flag and launches it. Nothing else to do.

Manual way:

  1. Double-click the downloaded .dmg. A small window opens.
  2. Drag the "Agent Chat Importer" icon onto the "Applications" folder in that same window.
  3. Close the window. You can drag the .dmg to the Trash, and eject the "Agent Chat Importer" disk from Finder's sidebar.

Step 3: Open it the first time

The app isn't signed with a paid Apple developer account, so macOS blocks the first launch with "Apple could not verify … is free of malware". That message means "not notarized", not "virus"; the sources are in this repository.

On macOS 15 and newer the dialog has only a Done button — the old right-click → Open trick no longer exists:

  1. Click Done.
  2. Open System Settings → Privacy & Security.
  3. Scroll to the bottom: "Agent Chat Importer was blocked…" → click Open Anyway.
  4. Authenticate, then Open.

Or clear the flag once from Terminal:

xattr -dr com.apple.quarantine "/Applications/Agent Chat Importer.app"

You only have to do this once. After that it opens normally (Launchpad, Spotlight, or the Dock).

Step 4: Move a chat

  1. In the left column, pick an agent (Claude Code, pi, Oh My Pi, Mistral Vibe, Freebuff, OpenCode, Codex, Goose, Aider, GitHub Copilot CLI or Hermes). Your chats appear in the middle.
  2. Click a chat to preview it on the right.
  3. Click Import into… at the top right and choose where it should go.
  4. Check the project folder (the default is where the chat originally ran, which is usually right) and click Import.
  5. The app shows a command like cd ~/my-project && claude --resume …. Click Copy, open Terminal, paste it and press Return. You're now continuing the same conversation in the other agent.

Your original chat is never changed or deleted; the import always creates a new copy.

Continue a chat where it stopped

You don't need to import to pick an existing session back up. In the session list, every chat has a ▷ button (also in the right-click menu, and as Resume in the toolbar of the open conversation):

  • Copy Resume Command puts the exact command on your clipboard, e.g. cd ~/my-project && omp --resume ~/.omp/agent/sessions/....
  • Resume in Terminal opens Terminal in the project folder and runs it for you (the command is also copied to the clipboard, so you can paste it by hand if needed).

The command uses the session's own ID and the folder it originally ran in, so the agent opens the conversation with its full history — no need to start the agent and hunt for the session.

Troubleshooting

Problem What to do
An agent shows a "–" or "not found" That agent has no chats on this Mac yet (or isn't installed). Nothing is wrong.
A new chat isn't in the list Press ⌘R (or the circular arrow button) to rescan.
"Agent Chat Importer is damaged" Run the xattr command from Step 3 in Terminal, then open it again.
The imported chat isn't found when I start the agent Start the agent from the same project folder you chose, or just use the copied command.

To remove the app: drag it from Applications to the Trash. It stores nothing else on your Mac.


Supported agents

Agent Sessions read from / written to
Claude Code ~/.claude/projects/<encoded-folder>/<uuid>.jsonl
pi ~/.pi/agent/sessions/--<folder>--/<time>_<uuid>.jsonl
Oh My Pi (omp) ~/.omp/agent/sessions/<folder>/<time>_<uuid>.jsonl (+ image blobs in ~/.omp/agent/blobs)
Mistral Vibe ~/.vibe/logs/session/session_<time>_<id>/ (meta.json + messages.jsonl), or save_dir from ~/.vibe/config.toml
Freebuff ~/.config/manicode/projects/<project>/chats/<timestamp>/ (chat-messages.json, chat-meta.json, run-state.json — the runner state with the replayed messageHistory)
OpenCode v2 ~/.local/share/opencode/opencode.db (session_v2 + session_message)
OpenCode v1 ~/.local/share/opencode/storage/session/<project>/<id>.json (+ message/, part/)
Codex ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (+ session_index.jsonl)
Goose ~/.local/share/goose/sessions/sessions.db
Aider per-project .aider.chat.history.md; imports write .aider.imported-<id>.md
GitHub Copilot CLI ~/.copilot/session-state/<uuid>/events.jsonl (+ session-store.db)
Hermes ~/.hermes/state.db

Using it

  1. Open Agent Chat Importer from /Applications. It scans every supported agent (about 1 s for ~1.6 GB of sessions).
  2. Pick an agent, then a session. The preview shows exactly what will be imported. Every AI reply carries a model chip, and the session header summarizes the models used; click a chip for the provider, API and exact model ID.
  3. Click Import into…, choose the target agent and project folder (defaults to where the original session ran), then Import.
  4. Copy the shown command (or click Open Terminal Here) to continue the chat, e.g. cd ~/Documents/Dev/FreeLLMapi && claude --resume <id>.

To continue the session in its own agent instead, click the ▷ button on its row and choose Copy Resume Command or Resume in Terminal.

The original session is never modified; every import creates a brand-new session.

What gets imported

The active context: what the source agent itself would send to its model.

  • The current branch only (rewinds/abandoned branches and subagent sidechains are left out).
  • For compacted sessions: the compaction summary plus the messages after it (for Copilot, the recent messages Copilot itself kept are included too).
  • Harness-only records (hook attachments, status/reminder injections, omp developer notes, Codex's injected <environment_context> / AGENTS.md messages) are skipped.
  • Images referenced by path (Vibe and Copilot attachments, omp blobs) are read only when the file really is an image; a session file cannot make the app read anything else.

Sessions are read in a streaming way, so multi-gigabyte transcripts don't load into memory. If an agent's store exists but can't be read (a locked or corrupt database, an unreadable file), a ⚠️ appears next to the agent with the reason, instead of an empty list.

Each target gets what its model API accepts:

  • Claude Code only accepts its own signed thinking, so reasoning from other agents becomes visible text. Tool-call IDs are rewritten to Anthropic's allowed alphabet.
  • Mistral Vibe needs 9-character tool-call IDs (remapped), stores images as attachment files, and marks failed tool results with <tool_error>. Images inside tool results can't be stored by Vibe, so a text marker is left.
  • Tool calls without a recorded result (session ended mid-tool) get a placeholder error result. Results whose call was compacted away are kept as plain text, so every agent's API sees valid call/result pairs.
  • Aider transcripts write every line of a user message with the #### prefix, as Aider itself does, so multi-line prompts keep their line breaks.

Build & install

Requirements: Xcode 15+ (license accepted) and XcodeGen (brew install xcodegen).

./scripts/build_app.sh          # builds Release and installs /Applications/Agent Chat Importer.app

To work in Xcode instead: xcodegen generate && open AgentChatImporter.xcodeproj.

The icon is drawn in code; regenerate it with swift scripts/make_icon.swift ..

Tests

The conversion engine is a Foundation-only Swift package, tested with synthetic fixtures:

DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swift test

Optional end-to-end checks use the real CLIs on this Mac (each writes only to a throwaway home):

ACI_INTEGRATION=1 swift test --filter IntegrationProbe
ACI_PROBE=1 swift test --filter RealStoreProbe

The integration tests are gated because they need the corresponding agent installed and signed in; they never modify the real session stores. Besides listing/resuming, IntegrationProbe also runs opencode session export on an imported session, which fails if any stored row does not match the installed OpenCode's schema.

RobustnessTests.testEveryProviderRoundTripsTheSameConversation imports one conversation into every target in an isolated home and reads it back, so each provider is verified in both directions (reading real stores is RealStoreProbe).

Layout

Sources/AgentChatCore/       Conversion engine (no UI)
  Model.swift                Unified conversation model
  ClaudeFormat.swift         Claude Code reader/writer
  PiFormat.swift             pi + omp reader/writer
  VibeFormat.swift           Mistral Vibe reader/writer
  FreebuffFormat.swift       Freebuff reader/writer
  OpenCodeFormat.swift       OpenCode v1 (JSON) + v2 (SQLite) reader/writer
  CodexFormat.swift          Codex rollout reader/writer
  GooseFormat.swift          Goose SQLite reader/writer
  AiderFormat.swift          Aider markdown reader/writer
  CopilotFormat.swift        GitHub Copilot CLI event-log reader/writer
  HermesFormat.swift         Hermes SQLite reader/writer
  SQLite.swift               Small SQLite C-API wrapper
  ExportPreparation.swift    Per-target adaptations and tool-call pairing repair
  SessionLibrary.swift       Scan, load, import
  AgentPaths.swift           Storage locations and folder-name encodings
Sources/AgentChatImporter/   SwiftUI app
  TerminalLauncher.swift     Clipboard + "resume in Terminal" helpers
Tests/AgentChatCoreTests/    Unit tests
scripts/                     build_app.sh, make_icon.swift
project.yml                  XcodeGen spec for the app