mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
main
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
82a8f236ec |
feat(opencode): restore the slash commands on OpenCode 2 (#1434)
* feat(opencode): restore the slash commands on OpenCode 2 OpenCode's V2 plugin API gained native command execution upstream (anomalyco/opencode issue #2185, PR #44765): ctx.command.transform lets a plugin add a command whose execute callback fully owns the invocation. That shape currently ships on the beta and dev dist-tags of @opencode-ai/plugin while next and latest still carry the older context, so the capability is duck-typed at runtime and never imported. On a host that exposes it the V2 adapter registers /plannotator-review, /plannotator-annotate and /plannotator-last and runs the same handleCliCommand machinery OpenCode 1 uses, passing the raw argument tail straight through to the CLI. On a host without it nothing new is registered and behavior is byte-identical to before. Also wires ctx.session.switchAgent (same API generation, same probe) so an agent switch chosen in the review UI is applied instead of only warned about, and accepts both agent.list() response shapes: the HTTP client types it as a { location, data } envelope while the in-process plugin domain answers with a bare array, where reading .data threw and silently emptied the agent list. The shared command stubs get model-mediated fallback bodies for OpenCode 2 hosts on the stale channels. They carry no shell interpolation on purpose: OpenCode 1 evaluates a template's !`...` before the V1 plugin's command.execute.before hook can clear the parts, so a bang template there would launch a second Plannotator session on every OC1 invocation. A source level test pins that. AI-assisted (Claude) under maintainer direction. * fix(opencode): probe the command draft and reclaim the names from the stubs Review found the capability probe was wrong in the direction that matters. ctx.command.transform exists on pre-#44765 hosts too: our own pinned @opencode-ai/plugin@0.0.0-next-16775 declares CommandDraft as { list, get, update, remove } with no add. The probe therefore returned true on next and latest, draft.add was undefined, and because transforms are stored and replayed the TypeError landed in the batched reload flush and aborted it before commit, plausibly taking every command registration on the host down with it. Capability is now read from the draft handed to the callback, which is the only witness, and the registration call is wrapped so no transform rejection can fail plugin setup. The stubs also shadowed the native definitions on new hosts. Command definitions land in a name-keyed map where add is Map.set, transforms replay in registration order, and OpenCode's own ConfigCommandPlugin activates in the post group after package plugins while scanning the exact directory the installer writes the three stubs to. A setup-time registration is therefore always overwritten on a normal install. The plugin now re-registers the same transform once activation settles, so its definitions are last in the replay order, and calls ctx.command.reload() explicitly because a late registration only adds its reload to the already-flushed boot batch. Ownership is read back from ctx.command.list() by description, which is why the native descriptions and the stub frontmatter are deliberately distinct. If the reclaim cannot run the stubs keep the names and the commands still work through their fallback bodies. Also: a failing switchAgent no longer costs the reviewer their feedback on the command path, feedback is delivered as "queue" rather than replaying the invocation's admission mode minutes later when a steer would land mid-turn, and the agent-list comment no longer asserts a bare-array response that could not be reproduced upstream (accepting both shapes is still right, since reading .data blindly throws into a catch that degrades silently). Tests: the real old-host draft shape registers nothing and throws nothing, the shadowing contest is modelled against upstream's replay semantics, the OpenCode 1 parts-clearing invariant is pinned for all three commands in both plan-agent and manual mode now that the stubs carry real instructions, and the V2 smoke asserts the plugin did not activate as failed and that all three commands resolve. The smoke now also installs the stubs into its sandbox config dir so the contest actually happens there. scripts/opencode2-native-commands-smoke.sh runs the same smoke against a dev-channel build with native commands required; CI cannot, because it pins a next build. AI-assisted (Claude) under maintainer direction. * fix(opencode): keep the reclaim ticking and stop an unbuilt checkout failing setup The reclaim ended the loop when the draft-probe flag read false, but that flag only flips when the transform replays, which under boot batching is the flush after every plugin has loaded. Plannotator loads before the post-group config plugins, so the first tick legitimately reads false and the loop exited for good: the reclaim was inert in exactly the shape production has. The tick is skipped now instead, with a test that flips the flag between ticks. The V1 entry called resolveBundledHtmlPath synchronously during plugin construction, outside the .catch that was there to absorb a missing asset, so an unbuilt checkout threw out of construction before any code path that needs the HTML. The Test workflow runs bun test with no build step, so the new OpenCode 1 interception tests failed there. Both preloads are guarded; the lazy getters still raise a clear error if something actually needs the file. The smoke's failed-plugin guard read entry.state.status, but Plugin.Info carries status and error at the top level, so a failed activation slipped through. Reads the top level first and keeps the nested one as a fallback. Comment corrections: State.batch clears its active flag before flushing, so a late transform registration materializes on its own; the explicit reload() is redundant-but-defensive rather than required. The reclaim schedule is a list of deltas the loop awaits in turn, so the ticks land near 0.3s, 1.5s, 5.5s and 15.5s, not at the raw numbers. AI-assisted (Claude) under maintainer direction. |
||
|
|
6b775ea1ed |
feat: /plannotator-last — annotate the last agent message (#325)
* feat: add /plannotator-last command to annotate last assistant message
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>
* chore: remove 3 redundant real-world scenario tests
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>
* feat: add /plannotator-last command to Pi extension
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>
* feat: add /plannotator-last to OpenCode plugin + extract command handlers
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>
* feat: context-aware UI labels for annotate-last mode
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>
* feat: add Codex support to annotate-last command
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>
* fix: context-aware feedback title + top spacing for paragraph-first content
- 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>
* chore: add sandbox scripts for Pi and Codex testing
- 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>
* fix: add hook build step to opencode sandbox script
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>
* fix: remove command body from plannotator-last to prevent agent response
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>
* fix: use command.execute.before hook for OpenCode annotate-last
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>
* fix: add Codex to origin type and agent name mapping
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>
* fix: remote share link, plan-specific prose, and codex type unions
- 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>
* fix: correct JSDoc for projectSlugFromCwd (leading dash is kept, not stripped)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: use RenderedMessage type instead of inline structural type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|