feat: /plannotator-last — annotate the last agent message#325
Merged
Conversation
Adds a new slash command that extracts the last rendered assistant message
from Claude Code's session log and opens it in the annotation UI.
Session log parser (apps/hook/server/session-log.ts):
- Parses Claude Code JSONL logs at ~/.claude/projects/{slug}/*.jsonl
- Finds the last assistant message.id with text content blocks
- Skips noise entries (progress, system, file-history-snapshot, queue-operation)
- Filters system-generated user messages by prefix to avoid false turn boundaries
- Walks backward through empty turns when back-to-back user messages exist
- No anchoring — reads from end of log since <command-message> isn't written
until after the binary completes
New files:
- apps/hook/commands/plannotator-last.md — slash command definition
- apps/hook/server/session-log.ts — Claude-Code-specific log parser
- apps/hook/server/session-log.test.ts — 30 tests covering streaming chunks,
tool call turns, sub-agent noise, stop hooks, thinking blocks, and edge cases
Modified:
- apps/hook/server/index.ts — annotate-last subcommand
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These duplicated coverage already provided by focused unit tests: - "full conversation" → covered by "grabs last message.id in multi-tool turn" - "stop hook interrupted" → covered by "skips progress and system noise" - "long tool-only sequence" → covered by "skips tool-only assistant entries" Kept the thinking block test (unique coverage). 27 tests remain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Uses Pi's session manager API to find the last assistant message — walks backward through ctx.sessionManager.getEntries(), finds the last entry with role "assistant" and text content, opens it in the annotation UI. Reuses existing isAssistantMessage(), getTextContent(), startAnnotateServer(), and runBrowserReview() from the extension. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lers Adds annotate-last command that fetches session messages via client.session.messages(), finds the last assistant message with text parts, and opens it in the annotation UI. Refactors command handling: extracts review, annotate, and annotate-last handlers from the inline event hook into commands.ts module. Reduces index.ts by ~120 lines and makes adding future commands cleaner. New files: - apps/opencode-plugin/commands.ts — extracted command handlers - apps/opencode-plugin/commands/plannotator-last.md — command metadata Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds "annotate-last" mode to the annotate server, passed through to the UI via /api/plan response. The editor uses this to show "Copy message" instead of "Copy plan", and "annotations on the message" in the completion overlay. - packages/server/annotate.ts: new `mode` option on AnnotateServerOptions - packages/editor/App.tsx: annotateSource state derived from mode - packages/ui/components/Viewer.tsx: copyLabel prop for button text - All three harnesses pass mode: "annotate-last" in their callers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detects Codex via CODEX_THREAD_ID env var (injected by Codex into every spawned process). Uses the thread ID to find the rollout file in ~/.codex/sessions/, parses the Codex rollout JSONL format to extract the last assistant message. Also adds `plannotator last` alias for shorter usage in Codex bang commands (!plannotator last). New files: - apps/hook/server/codex-session.ts — Codex rollout parser - apps/hook/server/codex-session.test.ts — 9 tests Modified: - apps/hook/server/index.ts — Codex detection + `last` alias Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ontent - exportAnnotations now accepts a title param: "Message Feedback" for annotate-last, "File Feedback" for file annotation, "Plan Feedback" for plan review (default) - Adds top spacer when content starts with a paragraph (not a heading) and has no frontmatter, fixing tight spacing in annotate-last mode Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sandbox-pi.sh: builds extension, creates temp project, installs via `pi install`, launches Pi with sample files - sandbox-codex.sh: compiles binary, creates temp project, launches Codex. Test with `!plannotator last` Both follow the same pattern as sandbox-opencode.sh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The opencode build copies HTML from hook/dist/ — without building hook first, the sandbox could use stale HTML. Pi and Codex sandboxes already had this step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The .md body was being sent to the agent as a prompt, causing it to respond with "Opening annotation UI..." before the event handler could fetch messages. That response became the "last message" instead of the actual one. Empty body = agent stays silent, event handler intercepts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moves plannotator-last from the passive event hook to the command.execute.before hook. This intercepts the command before the agent sees it, clears output.parts so the agent stays silent, fetches session messages, opens the annotation UI, then sends feedback via client.session.prompt() — same pattern as review/annotate. Previously the agent would respond to the command body before the event handler could fetch messages, polluting the session history. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Origin "codex" was falling through to the default "Coding Agent" label. Added "codex" to the origin union type across annotate server, editor, and removed the `as any` cast in the hook. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add writeRemoteShareLink to annotate-last onReady callback so remote sessions get a reachable URL - Add subject parameter to exportAnnotations so feedback says "message" or "file" instead of "plan" when appropriate - Add 'codex' to origin type unions in useAgents, Settings, UpdateBanner, and App.tsx fetch handler Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Take our extracted commands.ts approach and update it to use the new runGitDiffWithContext API from main, threading ctx.directory through CommandDeps for correct cwd in tmux/server/attach flows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Owner
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
…stripped) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/plannotator-lastcommand across all four harnesses (Claude Code, Pi, OpenCode, Codex)Harness implementations
~/.claude/projects/{slug}/*.jsonlsession logsctx.sessionManager.getEntries()APIclient.session.messages()SDK,command.execute.beforehook~/.codex/sessions/rollout JSONL filesCODEX_THREAD_IDenv varUI changes
mode: "annotate-last"flows from server → UIOther changes
commands.tsmodulecommand.execute.beforehook to intercept command before agent respondsTest plan
/plannotator-lasttested extensively in live session/plannotator-lastconfirmed working/plannotator-lastconfirmed working via sandbox!plannotator lastconfirmed working, parser validated against real rollout filesplannotator lastalias works (shorter for Codex bang commands)🤖 Generated with Claude Code