Skip to content

feat(pi): the plannotator tool on Pi (0.29 PR 5) - #1714

Merged
backnotprop merged 4 commits into
mainfrom
feat/pi-plannotator-tool
Oct 5, 2026
Merged

backnotprop merged 4 commits into
mainfrom
feat/pi-plannotator-tool

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

PR 5 of the approved 0.29 agent-sessions design: the plannotator tool comes to Pi, so a Pi agent opens reviews the same way the Claude Code mod does (non-blocking, with Ask this session) instead of running the blocking CLI.

What it does

  • Registers the shared contract with pi.registerTool. Name, description, validation (parsePlannotatorToolInput), argument mapping (plannotatorToolArgs) and result text come from packages/shared/plannotator-tool.ts, vendored as generated/plannotator-tool.ts. parameters is PLANNOTATOR_TOOL_INPUT_SCHEMA itself, with no TypeBox copy: Pi has validated plain JSON Schema parameters since 0.79.1, the peer floor. executionMode: "sequential", so "edit, then open" in one message opens the edited file ([pi] plannotator_submit_plan can review a stale plan when batched with a write/edit (parallel tool execution race) #1622).
  • One launch path. The three slash commands and the tool now share launchCodeReview, launchAnnotate and launchLastMessage: in-process server, session bridge attached, decision delivered later as a followUp. The commands still notify on errors. The tool throws them, and Pi reports that as an error result.
  • list / close come from a per-instance registry (apps/pi-extension/plannotator-tool-host.ts). It records every review this Pi session opened (tool, /plannotator-* commands, plannotator_submit_plan) and is filtered by sessionManager.getSessionId(). The global sessions/ registry is never read.
    • unsent and decided come from the server's in-process hostControl.status().
    • close calls hostControl.close(), from feat(tool): contract v2 with session ids, list and close #1709: it acts as the reviewer's Close, the draft is kept, and nothing is delivered. Pi notifies "the agent closed …" instead.
    • Plan reviews are listed but never closed.
  • Decision heading: review, annotate and last decisions, from commands too, now start with plannotatorDecisionHeading (Plannotator: notes.md (pn-3f2a9c) — Feedback · 1 comment.).
  • Clear refusals: a list of files gets PLANNOTATOR_TOOL_BUNDLE_UNAVAILABLE_TEXT, and the bundles PR only needs to add the open path, since validation is already shared. reply gets PLANNOTATOR_TOOL_REPLY_UNAVAILABLE_TEXT. A session without UI (print/JSON mode) is refused.

Deviations / notes

  • Plan decision messages are unchanged (no heading). The submit result never named a session id, so plan review keeps its existing prompts.
  • No Bash take-over on Pi. Pi's tool_call event can only block a call, and the model reads that as an error. The core skill already says to use a plannotator tool when there is one, so no skill change was needed.
  • Remote mode: opening works. The URL is in the tool result and in the notification, and the Ask bridge stays off, as before. Close works in-process even though the HTTP host endpoints are off in remote mode.
  • Subagent rules from the mod do not apply: Pi has no in-process subagents.

Tests

  • apps/pi-extension/plannotator-tool.test.ts (7 tests) drives the real extension with real in-process annotate servers. It checks:
    • the schema equals the shared one and the installed Pi's validateToolArguments accepts and refuses calls correctly;
    • opening returns at once with the bridge attached, and feedback arrives as a followUp with the heading;
    • a gated bare approval is delivered;
    • list and close see only this Pi session (another session id and another instance get nothing), close keeps the draft, and nothing is sent;
    • command-opened reviews are listed, and close all skips a plan review;
    • review argument mapping;
    • refusals.
  • Results:
    • bun test apps/pi-extension apps/hook/hooks/mod packages/shared/plannotator-tool.test.ts: 495 pass.
    • bun run typecheck (re-vendors): OK.
    • bun pm pack --dry-run includes plannotator-tool-host.ts and generated/plannotator-tool.ts.
  • Live check, real Pi 0.85.1 and 1.0.2: rpc mode, isolated HOME and PLANNOTATOR_DATA_DIR, this worktree's extension, and a scripted faux model that calls the tool. On both versions:
    • The turn settled at once with no model call after terminate, and Ask AI listed "Ask this session · Pi".
    • list returned the review. Feedback arrived as a new turn: Plannotator: notes.md (pn-…) — Feedback · 1 comment.
    • close all reported "1 unsent comment saved as a draft". No turn followed, and the draft stayed on disk.
    • A gated approval arrived as … — Approved., and a file list was refused.

Co-Authored-By: Claude noreply@anthropic.com

backnotprop and others added 4 commits October 5, 2026 10:45
Pi registers the shared plannotator tool contract with pi.registerTool:
name, description, schema (the shared JSON Schema itself), validation,
argument mapping and result text all come from
packages/shared/plannotator-tool.ts, vendored as generated/plannotator-tool.ts.

- One launch path: /plannotator-review, /plannotator-annotate,
  /plannotator-last and the tool share launchCodeReview / launchAnnotate /
  launchLastMessage (in-process server, Ask this session bridge, decision as
  a followUp message). Opening returns at once with the pn- session id and
  url (terminate: true); a gated session the tool opened delivers a bare
  approval.
- list / close: a per-instance registry (plannotator-tool-host.ts) of every
  review this Pi session opened (tool, commands, plan review), scoped by the
  Pi session id. Close calls the server's in-process hostControl.close()
  (draft kept, nothing delivered); plan reviews are listed, never closed.
- Review, annotate and last decisions start with the shared decision heading
  naming the session.
- A list of files, reply, and sessions without UI answer clearly.

Tests: plannotator-tool.test.ts drives the real extension with real
in-process annotate servers and the installed Pi's validateToolArguments.
Live-checked with real Pi 0.85.1 and 1.0.2 (rpc mode, faux model).

Co-Authored-By: Claude <noreply@anthropic.com>
- Tool `last` skips the assistant message holding the calling tool call
  (Pi saves it before the tool runs), for the opened message and the picker.
- The open-review registry is process-wide (globalThis), scoped by Pi
  session id, so /reload and /resume keep listing and closing the session's
  reviews; another session still sees nothing.
- User-facing close notices say "the agent's last message"; agent-facing
  texts keep "your last message".
- Labels follow the mod: a non-approval code review is "Changes requested";
  a zero-annotation review (the PR-platform status post) starts no turn;
  plan reviews list as "Plan vN".
- Shared opened text and description are host-neutral ("a message in this
  conversation"); mod CONTRACT copy re-pasted.
- On one fixed port (remote mode, single PLANNOTATOR_PORT) the tool refuses a
  second open and names the open review instead of letting it be preempted.
- Tests: /plannotator-review and /plannotator-last delivery, platform no-op,
  agent-closing a code review, tool last, reload scoping, fixed port.

Co-Authored-By: Claude <noreply@anthropic.com>
…try key

- Revert the zero-annotation no-turn rule: PR description and PR comment
  notes and editor comments ride only in the feedback text, so Pi delivers
  every review decision whose feedback has content, as before. The heading
  and labels stay (`Changes requested`, with ` · N comments` when annotated).
- The process-wide registry lives under a versioned globalThis key
  (__plannotatorPiReviewRegistry_v1) and is replaced when the value there
  lacks the required methods.
- Fixed-port refusal for another session's review tells the agent to ask the
  user to finish or close that tab (it cannot close it).

Co-Authored-By: Claude <noreply@anthropic.com>
Brings in #1719 (platform flag), #1717, 0.28.4 and #1718 (bundles).
Resolution keeps both sides:
- Pi review delivery uses classifyReviewOutcome (the server's platform
  flag; nothing inferred from the annotation count) inside the shared
  launchCodeReview, with this branch's decision heading. The platform
  status post is labeled "Posted to the pull request".
- Bundles go through launchAnnotate, shared by /plannotator-annotate and
  the tool. A tool list target opens one annotate-bundle review in order
  (only the shared selection's bundle outcome opens; missing, folder or
  URL entries refuse). Its subject is plannotatorBundleSubject in the
  result, list, close and decision heading.
- Tests: bundle via the tool (order, list, feedback, close), a list that
  would open fewer files refuses, description-only review feedback through
  the tool path, and the platform post delivered verbatim.

Co-Authored-By: Claude <noreply@anthropic.com>
@backnotprop
backnotprop merged commit d3e1ec9 into main Oct 5, 2026
28 checks passed
backnotprop added a commit that referenced this pull request Oct 5, 2026
…annotator-tool

- packages/shared/plannotator-tool.ts header names both hosts: Pi
  (pi.registerTool) and OpenCode 2 (tools.add).
- One shared outcome for the PR-platform status post,
  PLANNOTATOR_OUTCOME_REVIEW_POSTED = "Review posted" (CONTRACT section; the
  mod copy re-pasted, byte-identical), used by OpenCode 2's review heading and
  by Pi instead of "Posted to the pull request" (wrong for GitLab merge
  requests); both hosts' tests assert the constant.
- cli-bridge.ts: one import from @plannotator/shared/plannotator-tool.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant