Skip to content

Simplify extensions to thin wrappers: server-owned prompts, vendor trim, dumb-pipe CLI#801

Merged
backnotprop merged 20 commits into
feat/single-server-runtimefrom
feat/pi-extension-simplify
May 28, 2026
Merged

Simplify extensions to thin wrappers: server-owned prompts, vendor trim, dumb-pipe CLI#801
backnotprop merged 20 commits into
feat/single-server-runtimefrom
feat/pi-extension-simplify

Conversation

@backnotprop
Copy link
Copy Markdown
Owner

@backnotprop backnotprop commented May 27, 2026

Summary

  • Server owns feedback prompt generation. Plan, review, and annotate servers now compose the final agent-ready prompt and return it as result.prompt. Extensions and the CLI pipe it through via result.prompt ?? result.feedback — they never rebuild or reformat prompts. The only exception is plan mode prompts, which are host-specific (each extension has its own planning workflow).
  • CLI is a dumb pipe. Removed all prompt function imports, Jina config resolution, review arg parsing, and improve-context local composition from the CLI. Improve-context now calls the daemon's /daemon/improve-context endpoint.
  • Pi vendor trim: 20+ → 9 files. Replaced full shell-style arg parsers with simple includes() checks and raw-args binary calls. Removed turndown, @joplin/turndown-plugin-gfm, @pierre/diffs dependencies (zero runtime deps). Stubbed agents.ts to just the Origin type.
  • OpenCode prompt cleanup. Removed local prompt composition for review approved/denied, annotate file/folder, and annotate-last. All pipe result.prompt from the server.

Architecture

All three extension surfaces are now symmetric thin wrappers:

Extension (CLI / Pi / OpenCode)
  → find/start daemon
  → spawn session with raw args
  → pipe result.prompt to host agent

No local prompt generation. No file processing. No HTML conversion. No URL fetching. The daemon handles everything.

Plan mode is the exception — Pi/OpenCode keep their planning workflows (phase management, tool gating, checklist tracking, prompt injection) because those require host-specific APIs.

Files changed

Surface Change
packages/server/index.ts Plan server composes denied prompt via getPlanDeniedPrompt
packages/server/review.ts Review server composes approved/denied prompt
packages/server/annotate.ts Annotate server composes file/folder/message prompt
packages/server/daemon/server.ts New /daemon/improve-context endpoint
packages/server/daemon/client.ts getJson made public for endpoint access
packages/shared/plugin-protocol.ts Added prompt?: string to all result types
apps/hook/server/index.ts Removed all prompt imports, pipes result.prompt
apps/pi-extension/index.ts Stripped annotate/review to raw-args binary calls
apps/pi-extension/plannotator-browser.ts Added startCodeReviewSessionFromArgs, startAnnotationSessionFromArgs
apps/pi-extension/vendor.sh 20+ files → 9 files
apps/opencode-plugin/commands.ts Removed prompt imports, pipes result.prompt
apps/opencode-plugin/index.ts Removed getAnnotateMessageFeedbackPrompt wrapping

Test plan

  • bun run typecheck passes
  • bun test passes (1,331 tests, 0 failures)
  • bash apps/pi-extension/vendor.sh runs cleanly
  • ls apps/pi-extension/generated/ shows exactly 9 files
  • grep -c 'parseAnnotateArgs\|parseReviewArgs' apps/pi-extension/index.ts returns 0
  • All result.prompt ?? result.feedback paths verified across CLI, Pi, OpenCode
  • Improve-context daemon call has error handling (silent passthrough on failure)
  • Plan file rule correctly empty for non-Gemini origins

Replace ~130 lines of local file resolution, URL fetching, HTML
conversion, and folder detection with a single call to
startAnnotationSessionFromArgs() that passes raw args to the binary.
The daemon handles all content resolution — matching how OpenCode and
the CLI already work.

Remove unused imports: htmlToMarkdown, urlToMarkdown, isConvertedSource,
hasMarkdownFiles, resolveUserPath, FILE_BROWSER_EXCLUDED,
resolveAtReference, resolveUseJina, basename.
Reduce generated/ from ~42 files to 14 (11 vendored shared modules +
3 tiny stubs for transitive deps). Replace full vendored copies of
resolve-file, at-reference, and vcs-core with minimal stubs containing
only the functions/types Pi actually uses. Inline DiffType and
VcsSelection types in plannotator-browser.ts and plannotator-events.ts.

Remove turndown, @joplin/turndown-plugin-gfm, and @pierre/diffs
dependencies — HTML/URL conversion is now handled by the binary.
Pass the original command string (with @-prefixes intact) to the binary
instead of the parsed/stripped filePath. The daemon's resolveAnnotateInput
handles @-reference resolution from raw args.

Use the binary's result.mode and result.filePath to build the feedback
prompt — "Folder: /abs/path" for folders, "File: path" for files —
instead of hardcoding "File".
The annotate and review servers now compose the final agent-ready prompt
at decision time and include it as `result.prompt`. Extensions pipe it
through to the agent session instead of building prompts locally.

Server changes:
- annotate.ts: compose prompt using mode/filePath/origin at feedback time
- review.ts: compose prompt with approved/denied/PR logic at feedback time
- plugin-protocol.ts: add prompt? field to PluginAnnotateResult and
  PluginReviewResult

Extension changes (Pi + OpenCode):
- Replace all getAnnotateFileFeedbackPrompt, getAnnotateMessageFeedbackPrompt,
  getReviewApprovedPrompt, getReviewDeniedSuffix calls with result.prompt
- Remove prompt function imports from both extensions
- Pi: remove dead anchorMessageFeedback/excerptText helpers
- OpenCode: update test to verify server prompt passthrough
CLI review output now uses result.prompt instead of rebuilding the
prompt locally. Remove getReviewApprovedPrompt and getReviewDeniedSuffix
imports from CLI.

OpenCode annotate-last now pipes result.prompt directly instead of
double-wrapping with getAnnotateMessageFeedbackPrompt. Remove the
import.
Plan server now composes denial prompts at /api/deny time using
getPlanDeniedPrompt/getPlanToolName/buildPlanFileRule and returns
result.prompt. All 4 CLI hook paths (Copilot, Codex, Gemini, Claude
Code) now use result.prompt instead of building prompts locally.

Remove resolveUseJina from CLI — daemon resolves Jina settings from
its own config. CLI just passes the noJina flag through.

CLI now has zero prompt function imports and zero config-based prompt
generation. Only remaining loadConfig call is for the PFM improve-context
hook (documented exception — reads hook files from local filesystem).
Add /daemon/improve-context endpoint that reads improvement hooks and
composes PFM context server-side. CLI now calls the daemon instead of
reading hook files and loading config locally.

Remove dead imports: hostnameOrFallback, parseReviewArgs, loadConfig,
resolveUseJina, readImprovementHook, composeImproveContext.
Remove dead parseReviewArgs() call in review handler.

CLI now has zero @plannotator/shared imports for processing — only
types, agent config for origin detection, and goal-setup normalization.
Replace parseAnnotateArgs with 3 includes() checks — Pi passes raw
args to the binary anyway. Add startCodeReviewSessionFromArgs that
passes raw args (matching annotate pattern). Remove parseReviewArgs.

Replace vendored agents.ts (35 lines, display names, badge classes)
with 2-line type stub — only Origin type needed by plugin-protocol.

Removed from generated/:
- annotate-args.ts, review-args.ts (replaced by simple string checks)
- at-reference.ts, resolve-file.ts, vcs-core.ts (transitive stubs, no longer needed)
- agents.ts full file (replaced with type-only stub)
… plan file rule, OpenCode guard

- Fix runtime crash in improve-context: ensureDaemonClient returns DaemonClient
  directly, not an object with .client — use client.getJson() instead
- Add try/catch around improve-context daemon call so hook failures silently
  pass through instead of crashing (matches old local-read behavior)
- Make DaemonClient.getJson public for daemon endpoint access
- Fix emitAnnotateOutcome to use result.prompt ?? result.feedback (CLI annotate
  paths were ignoring server-composed prompts)
- Revert planFileRule to "" in plan server — slug is not a real file path, and
  non-Gemini origins never had a plan file rule
- Fix OpenCode annotate guard: check result.prompt || result.feedback instead
  of just result.feedback
@backnotprop backnotprop changed the title Simplify Pi extension to thin CLI wrapper Simplify extensions to thin wrappers: server-owned prompts, vendor trim, dumb-pipe CLI May 27, 2026
… excerpt

The server holds the original assistant message for the session lifetime.
When composing annotate-last prompts, include a blockquoted excerpt so the
agent knows which message the annotations apply to — even if the conversation
has moved on by the time feedback is submitted.
ensureDaemonClient calls process.exit(1) on failure instead of throwing,
so the try/catch never caught anything. Use discoverDaemon directly —
if a daemon is already running, call the endpoint; if not, exit 0 silently.
No daemon startup, no retries. Improve-context is best-effort.
Add bestEffort option to ensureDaemonClient — throws instead of calling
process.exit, so callers can try/catch gracefully. The improve-context
handler now auto-starts the daemon like every other command, but silently
passes through if the daemon can't start.

Document fact: daemon starts on install and is always running.
cleanupDaemonStateForSessionCommand had its own error handler that called
process.exit, bypassing the try/catch in the improve-context handler.
Now it re-throws under bestEffort mode so the caller can handle it.
The contract changed: the binary now returns result.prompt and clients
stopped building prompts locally. Old binaries that don't return prompt
must be rejected so ensurePlannotatorBinary triggers a re-install.
- Define DiffType and VcsSelection once in plannotator-browser.ts,
  import in plannotator-events.ts instead of duplicating
- Remove isCurrentPiSessionDifferentFrom (no longer imported)
- Remove hasSessionMovedPastEntry (no longer imported)
Custom workflows using --json or --hook expect raw user feedback in
the output fields, not the server-composed prompt. Keep result.feedback
for structured output modes, use result.prompt only for plaintext
(agent-facing) mode.
The CLI reads planFilename from Gemini's event input and now passes it
through to the daemon session. The plan server uses it in buildPlanFileRule
so denied Gemini plans include "Your plan is saved at: <file>" instructions.
Non-Gemini origins pass undefined, which produces an empty rule (unchanged).
Pi can't import from packages/shared at runtime (npm package), so the
type is inlined. Was missing merge-base, all, worktree, and p4 variants.
@backnotprop backnotprop merged commit ebe989a into feat/single-server-runtime May 28, 2026
@backnotprop backnotprop deleted the feat/pi-extension-simplify branch May 28, 2026 01:44
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