Now I have everything I need. Let me look at the Pi adapter too since it's the newest and likely the cleanest pattern to follow (it also reads JSON files like Amp):
Now I have all the context. Let me create everything in the worktree — project file, fixture, stub adapter, and failing tests:
Now let me create the fixture, stub adapter, register it, and write failing tests — all in the worktree:
Now create the empty thread fixture (edge case):
Now add the history.jsonl fixture:
Now update types and config, create the stub adapter, and register it:
Now create the stub adapter:
Now register it:
Now build shared types and add the Amp adapter to the test file:
Now add the Amp adapter to the test file — add the import and fixture-backed adapter:
Now let's also add a background fixture for the Amp adapter. Let me check the existing pattern at the bottom of the backgroundFixtures object:
Now let's add the amp background fixture. Let me find the closing bracket of backgroundFixtures:
Now let's run the tests to see them fail:
9 failing tests against the stub, exactly as expected. All existing tests still pass. Here's the summary:
Created in the feat/amp-adapter worktree:
docs/projects/amp-adapter.md — Project plan with phasespackages/shared/src/agent.ts — Added 'amp' to AgentIdcli/src/types.ts — Added 'amp' to CLI AgentIdcli/src/config.ts — Added amp storage path (~/.local/share/amp)cli/src/adapters/amp/v1.ts — Stub adapter with TODO implementationscli/src/adapters/index.ts — Registered AmpAdaptercli/test/fixture/amp/ — Fixtures (normal thread with tool calls/thinking, empty thread, history.jsonl)cli/test/core/adapters.test.ts — Added Amp to adapter contract tests + background fixtures9 tests failing against the stub — ready to implement. Want me to start filling in the adapter?
Let me commit the foundation first, then implement and commit incrementally.
Good, 3 clean commits. Now let me implement the adapter:
Now let's run the tests:
All 133 tests pass, including all 9 Amp adapter tests. Let me also run the full CLI test suite to be safe:
The one failure is a pre-existing flaky OpenCode test, not related to our changes. Let me verify:
Confirmed — that test fails on main too. Not our issue. Let me commit the implementation:
Done! 4 incremental, reversible commits:
'amp' AgentId and storage pathThe adapter handles all Amp content block types (text, thinking, tool_use, tool_result), extracts model info, supports getCurrentTraceHint via session.json, and has live subscribe via fs.watch. Ready for manual testing or PR!
Great question. Let me dig into the Amp format differences and the frontend rendering pipeline.
Now let me check what Amp-specific content blocks look like that differ from Claude Code — especially interruptions, images/attachments, and the tool_result structure:
Now I have the full picture. Here's how Amp differs and what it means for the frontend:
1. Single JSON file per thread (not JSONL)
Other adapters use JSONL (one event per line) or SQLite. Amp uses a single threads/T-{uuid}.json with a messages array — the whole file is rewritten on each update. This means we parse the entire file each time (no tail-scanning optimization like Claude Code).
2. Rich tool_result structure
Claude Code tool results are simple content blocks. Amp wraps them in { run: { status, result, progress } } with structured result objects (not just strings). Our adapter serializes run.result to JSON, which works but means the frontend sees JSON blobs rather than pre-formatted output.
3. Interruptions are state.type: "cancelled" or "streaming"
Claude Code marks interruptions with a [Request interrupted by user] text message. Amp uses:
state.type: "cancelled" on assistant messagesstate.type: "streaming" for in-progress (thread read while still running)tool_result blocks with run.status: "cancelled"The frontend's isInterruptionMessage() only checks for Claude Code's [Request interrupted...] pattern and OpenCode's (no error message) error parts — it won't detect Amp interruptions.
4. Rich env metadata
Amp stores detailed environment info: git repo URL, branch, SHA, OS, CPU architecture, client version, and workspace tree URIs. Other adapters just store a project path string. We could surface this richer context.
5. No toolName on tool results
Amp's tool_result blocks don't include the tool name — only the toolUseID. Our adapter currently emits toolName: '' for results. The frontend's ToolCallRouter matches results to calls by callId, so this works, but tools that key off toolName in the result might show fallback rendering.
| Issue | Impact | Fix |
|---|---|---|
| Interruptions invisible | Amp cancelled turns show as blank/empty messages | Add Amp's cancelled state detection to adapter (emit an error event) or teach frontend about it |
| Tool result is JSON blob | Read/Edit results show as raw {"absolutePath":..., "content":...} instead of formatted output | Frontend's parseRawResult already handles JSON with contents field (Cursor format) — could extend to handle Amp's result.content field |
| No agent color | getAgentColor('amp') returns the default muted color | Add an Amp-specific color to the theme |
| Streaming threads | Active threads have state.type: "streaming" messages with incomplete content | Adapter should skip or handle these gracefully |
The most impactful quick wins would be handling interruptions in the adapter and adding an agent color. Want me to tackle any of these?
Let me check the TUI theme and then implement everything together.
Now let me look at an existing adapter and the registry to understand the full pattern:
Good. Now I have the full picture. Let me implement all the changes across the worktree. I'll do this in parallel tasks:
Good. Now I have the full picture. Let me implement all the changes across the worktree. I'll do this in parallel tasks:
All three tasks done. Let me verify everything passes:
All 141 tests pass (9 original + 8 new Amp-specific = 17 Amp tests total). Let me also run the full suite:
554 pass, 0 fail (even the flaky OpenCode test passed this time). Now let me commit incrementally:
7 clean, incremental commits. 554 tests pass, 0 failures. Here's what we added in the last round:
Adapter hardening:
toolName on tool_result (was empty string)state.type: "cancelled" emits an error event ("Request interrupted by user")state.type: "streaming" messages are dropped entirelyrun.status: "cancelled" → error status + "(cancelled)" outputresult.content and result.output strings before JSON.stringify fallback8 new edge case tests covering all the above + title extraction, empty threads, and cursor deduplication
UI support:
#00d9ff agent color for AmpAgentLogo placeholder (ready for an SVG when available)Now add Amp to the list of agents with logos:
Done! Logos are in — light/dark SVGs with automatic theme switching. The geometric chevron mark will render cleanly at 16px.
The issue is the viewBox is 0 0 800 800 but the artwork only spans from ~165 to ~635 — so there's ~20% padding on all sides. Let me tighten the viewBox to the actual artwork bounds.
Now let me look at the Claude Code adapter as a reference, and check where Amp stores its session data:
Fixed — the Pi logo SVG had its artwork centered in an 800×800 viewBox with ~20% padding on each side. Cropped the viewBox to 145 145 510 510 to match the actual path bounds, so it'll render the same size as the other logos now.
Hey! Going well, thanks 😊 How can I help you today?
That error is unrelated to our adapter work — it's the frontend calling a Convex query that doesn't exist in the deployed backend. Let me check what's going on:
The function exists in the API code — the error means your local Convex dev server isn't running or hasn't been deployed with the latest schema. This is a local dev environment issue, not a code bug.
You likely just need to start the Convex dev server:
This is completely separate from the adapter work. Want me to focus back on the adapter, or help debug the Convex setup?
Everything is implemented. Let me update the project plan to reflect reality:
All code is done. What's left is manual verification only:
traces TUI and confirm Amp threads appear in the listWant to do a manual smoke test now?
Wait — this is the highlights page (default tab at /s/[id]). But there should be an "all messages" tab that uses a different query. Let me check:
So only the highlights tab uses the broken query. The /full tab should work. This isn't really something to "fix" in our branch — it's a backend deployment issue. But if you just want the page to not error so you can test, the simplest thing is to navigate to the /full tab instead.
But if you'd rather have the highlights tab gracefully degrade, I can do that too. What do you prefer?