No description
  • TypeScript 95%
  • JavaScript 4.8%
  • Shell 0.2%
Find a file
Antoine Viallon bb873ade61
feat(askpass): un pi imbrique adopte le pont herite au lieu d'en demarrer un
Un sous-agent est un processus pi neuf qui heritait de l'environnement du parent
puis ecrasait le socket et le jeton par les siens : ses demandes de mot de passe
restaient dans un pont sans interface, et pendaient. Il detecte desormais un pont
herite vivant (un ping, jamais un prompt) et l'adopte : aucun serveur a lui,
environnement intact, GNUPGHOME du parent conserve, rien de demoli a l'arret. La
demande remonte donc a la session interactive, et la reponse redescend.

Au passage :
- le prompt affiche l'origine (« depuis le sous-agent « worker » »), posee par le
  runner dans PI_ASKPASS_ORIGIN, pour que la decision soit eclairee ;
- le client a enfin un delai par defaut (330 s, juste au-dessus des 300 s du
  serveur) : une demande que personne ne peut satisfaire echoue au lieu de
  bloquer son appelant indefiniment ;
- le serveur repond a __ping sans prompter, et refuse toujours un mauvais jeton.

Verifie : adoption en sous-agent (aucun socket supplementaire, la demande atteint
le serveur du parent), ping teste, 11 tests serveur + 7 tests du pont verts.
2026-09-17 14:52:03 +02:00
.githooks feat(justai-auth): corporate gateway provider via Keycloak device flow 2026-09-15 10:55:49 +02:00
agents chore: import pi plugins (extensions, agents, prompts) 2026-09-10 12:21:08 +02:00
bin feat(askpass): un pi imbrique adopte le pont herite au lieu d'en demarrer un 2026-09-17 14:52:03 +02:00
extensions feat(askpass): un pi imbrique adopte le pont herite au lieu d'en demarrer un 2026-09-17 14:52:03 +02:00
plans docs(plans): le constat 34 (jeton askpass) est corrige 2026-09-16 17:11:57 +02:00
prompts chore: import pi plugins (extensions, agents, prompts) 2026-09-10 12:21:08 +02:00
.gitignore chore: run the unit tests in a pre-push hook 2026-09-14 15:18:22 +02:00
AGENTS.md docs(agents): require fully-qualified non-core kubectl API groups 2026-09-17 00:24:13 +02:00
README.md feat(subagent): list available agent definitions in list_agents 2026-09-17 00:23:12 +02:00

my-pi-plugins

Personal plugins for the pi coding agent: extensions, custom agents and prompt templates, tracked so the work survives and can be shared.

This repository is ~/.pi/agent (the pi agent dir), so the files are used in place. Everything that is local state or a secret is gitignored — see .gitignore.

Layout

  • extensions/ — TypeScript extensions loaded by pi:
    • subagent/ — delegate tasks to isolated subagent processes. Foreground (single / parallel / chain) and background runs, a live agent roster plus the available agent definitions (list_agents [agentScope] — running agents with their ids and the user/project agent definitions the tool can invoke), peer messaging (send_to_agent / ask_agent / answer_peer / check_messages), and a question bridge so subagents can ask the orchestrator or the user mid-task. Includes a TUI sidebar/widget showing active subagents and their latest output. Background subagents run with persistent pi sessions and are checkpointed on a runtime reload, then automatically resumed afterwards (per orchestrator session, so multiple concurrent pi sessions stay isolated). With the sidebar/ extension loaded it contributes a Subagents panel; otherwise it falls back to the overlay/widget. ctrl+shift+o (or /agents-view) opens a subagent's live transcript — reasoning, text, tool calls and results — in a centered, scrollable overlay.

    • sidebar/ — a real right-column sidebar (pi has no sidebar API, so this uses the terminal-compositor technique: narrow terminal.columns, paint the freed columns inside pi's synchronized render frame). Robust repaint: the whole region is repainted every frame in the same sync block (no reliance on pi's erase sequences), stale rows are cleared, cursor is saved/restored, and wrap is disabled. Panels are contributed by other extensions through a globalThis-shared registry (registry.ts); built-in Session panel plus the subagent Subagents panel. Control: /sidebar on|off|width <10-120> (persisted in sidebar.json). Do not run it alongside pi-sidebar-tui (two compositors fight over the same columns). The separator is drawn for the full terminal height; the sidebar columns are wiped before each pi render so scrollback never keeps sidebar text.

      Widget API — extensions/sidebar/api.ts:

      import { addSidebarWidget } from "../sidebar/api.ts";
      const dispose = addSidebarWidget({
        id: "build", title: "Build", order: 30,
        render: (view) => `branch ${view.branch ?? "?"}`, // string | string[] | null
      });
      

      Other packages (separate module roots) use the same API via the global handle: globalThis.piSidebar.addWidget({ id, title, render }). render runs on every paint and may return a string, lines, or null to hide.

    • openrouter-blacklist.ts — auto-avoids flaky OpenRouter upstream providers. On a transient provider error (429/5xx with a provider_name in the error body, e.g. DeepInfra), it blacklists that (provider, model) pair and injects it into the provider.ignore array of later OpenRouter requests (merged with any openRouterRouting). Grace grows exponentially per repeated strike (60s, 2m, 4m, … capped at 2h) and strikes decay after 6h of quiet. State: ~/.pi/agent/openrouter-blacklist.json; manage with /openrouter-blacklist [clear [provider]]. Logic in lib/openrouter-blacklist.ts.

    • store/ — shared agent store (store_put/get/list/search/delete/sync). db.ts is the storage engine (SQLite cache + authoritative project JSON); index.ts wires the tools and memory discoverability. Project scope is git-friendly JSON at <repo>/.pi/store/<ns>/<key>.json with a SQLite cache; user scope is SQLite-only. JSON is authoritative and external edits are imported live. Discoverability: a stable system-prompt pointer (sentinel <!-- pi-store-pointer -->) lists namespaces, and before_agent_start injects the top keyword matches as a hidden <agent-store-recall> XML message — explicitly marked as data, not user input or instructions. Ranking is BM25 via SQLite FTS5 (bm25() with key/tags/body column weights, unicode61 + light stemming), plus a small-corpus bonus 1/(3-N) for 1–2 entry stores (where FTS5's classic idf is exactly 0), and a fuzzy pass (prefix + edit distance over the FTS vocabulary) for typos. Results are capped at the top 5; when more entries clear the relevance bar, a virtual <omitted count="N"/> element reports how many were not shown.

    • reload.ts — LLM-callable runtime reload with scoped consent (once / session / this project), a styled warning prompt, and an optional resumeWith message so the agent can continue after reloading.

    • auto-reserve.ts — model-derived compaction reserve: proactively compacts after a completed turn when context usage would leave less room than min(model.maxTokens, capFraction × contextWindow) + margin (default 25% cap), instead of pi's fixed 16384 reserveTokens. Never fires on load. /auto-reserve [on|off|margin <n>|cap <fraction>] inspects/adjusts it.

    • ultracompact.ts — survive a context that grew far past the model window. When a compaction's own summarization input wouldn't fit (or on /ultracompact [instructions]), it summarizes the history in chunks (map-reduce) and returns the merged result via session_before_compact, so no single LLM call overflows. The pure engine lives in lib/summarize.ts (unit-tested) and it also auto-activates for oversized automatic/overflow compactions.

    • opencode-import/ — load a session from the local opencode SQLite DB and convert it into a real pi session (text, thinking, tool calls/results, images, usage). /import-opencode [query] [--all] [--id <ses_…>] [--dir <path>] [--limit N] [--tail N] lists/imports, then asks whether to switch into the new session; an import_opencode tool does the same for the agent (without switching).

    • sessions.ts — /sessions [query] [--here] [--limit N] [--plain] lists pi sessions from every project (unlike project-scoped /resume), newest first, and switches to the selected one. The list_sessions tool exposes the same listing to the agent. Shell counterpart: bin/pi-sessions.

    • askpass/ — turn sudo and SSH password/passphrase prompts into a masked prompt inside pi, including remote sudo over ssh. Env-based only (no built-in tool overridden, no command-string heuristic): SUDO_ASKPASS + a sudo shim (adds -A), SSH_ASKPASS + SSH_ASKPASS_REQUIRE=force, and an ssh shim that (for remote commands) prepends a POSIX sudo function emitting a control marker on stderr — the local shim answers it with the password on ssh's stdin (sudo -S), so the secret only travels encrypted (never argv/env/terminal/logs). Helpers: bin/pi-askpass, bin/pi-ssh-askpass, over a Unix socket. Secrets are cached in memory for the session (never persisted) per kind@host (ssh-key@<path>, ssh-password@user@host, sudo@<host>, pinentry@<keygrip>); a failed command drops the entry. Every tool result gets an [askpass] … note telling the agent what happened (cached / typed / cancelled) — never the secret. /askpass [status|on|off|clear|forget <kind@host>].

      The bridge token is never put in pi's environment: every command pi runs inherits that environment, so a token there would be readable by any tool call (and copied into any transcript that prints the environment). The server writes it to a 0600 file next to the socket and publishes only the path (PI_ASKPASS_TOKEN_FILE); the raw PI_ASKPASS_TOKEN survives as a fallback for the case where no file can be written. The client refuses — before connecting — a socket that is not a socket, not owned by the current user, or writable by others, refuses a token file readable by others, and compares the token in constant time. The honest limit is written down where it belongs: a token cannot keep out a command running as the same user; what keeps that in check is visibility (the [askpass] note and the command itself) and /askpass forget.

      GPG: each session gets a private GNUPGHOME overlay holding a read-only copy of the keyring, served by its own gpg-agent (distinct socket) whose pinentry-program is bin/pi-pinentry; the global agent is never touched. GPG signing/decryption work with the passphrase prompted in pi; keyring writes (--import, --gen-key, --edit-key, passphrase change) fail by design, and a hidden <gpg-overlay> note tells the agent so. Robust cleanup on shutdown plus a stale-overlay sweep (pid liveness).

    • justai-auth/ — corporate gateway provider (JustAI/Brio). Registers the one-api gateway (JUSTAI_GATEWAY, default https://llm-gw.justai.ovh) as an OpenAI-compatible provider and authenticates it through Keycloak (JUSTAI_ISSUER realm JustAI, public client JUSTAI_CLIENT_ID, default pi-clients). /login justai runs the OIDC device flow — including the PKCE challenge this client enforces — then exchanges the access token at POST /api/pi/token for a short-lived gateway key. The model catalog is discovered live from /v1/models through pi's refreshModels hook, so JUSTAI_API_KEY (or an existing credential) lists models without a browser. Config: config.ts; flow: oauth.ts; mapping: models.ts.

    • lsp/ — language-server support (TypeScript preset, generic/configurable). Tools: lsp_diagnostics (file or whole project), lsp_hover, lsp_definition, lsp_references, lsp_symbols, lsp_workspace_symbols. Servers are resolved from the project's node_modules/.bin (walking up) or PATH — nothing is auto-installed — and the TypeScript preset points the server at the workspace so it uses the project's own typescript/tsconfig. Diagnostics are also appended to write/edit tool results (disable with "diagnosticsOnEdit": false in ~/.pi/agent/lsp.json).

    • credits.ts — status bar showing the current provider's remaining credits (OpenRouter account balance, falling back to per-key limits) and the in/out price (USD per 1M) of the model that last answered — the actual routed model (responseModel) and the actual upstream provider. The real provider and cost are captured from OpenRouter's SSE chunks (see lib/openrouter-stream.ts), so the rates reflect whoever actually served the request (e.g. @DeepInfra), not the catalogue list price; before any answer the list price is shown instead. Prices are colour-graded blue → green → bold red (in: 0→0.20→5.0; out: 0→0.80→25.0), truecolor with a 256-colour fallback, styled via the current theme. Refreshes on model change, session start, after each run and every ~2 min; /credits forces a refresh.

    • lib/openrouter-stream.ts — wraps the openai-completions stream for OpenRouter providers to tee the SSE body and capture the provider and real cost fields pi drops (responseId/responseModel only). Installed on session_start for every registered openrouter* provider; openRouterStats(generationId) and effectiveRates(stats) feed credits.ts.

    • lib/model-metrics.ts (barrel) — pick the cheapest model clearing a quality bar. Split into lib/metrics-types.ts (types + defaults), lib/metrics-sources.ts (Artificial Analysis + local JSON, id normalization, default store) and lib/metrics-rank.ts (profiles, cost, aliases). Pluggable metrics sources (Artificial Analysis free API + local JSON), named profiles (software-engineering, research, architecture, codebase-analysis), canonical model-id matching (…/Qwen3.8-27B-Uncensored-GGUF:IQ4_XS → qwen3.8-27b), and findCheapestModel / resolveAlias. Cost rule: pi-cost 0 is a hard override (free), otherwise Artificial Analysis cost-per-task. Local providers are auto-detected and win ties. Alias-backed candidates are self-excluded to avoid recursion. Aliases are baked snapshots (2026-09-11) and can be overridden in ~/.pi/agent/model-metrics.json. Subagents can target one via the model param or agent frontmatter (e.g. model: research); candidates are restricted to enabledModels. Aliases are also registered as selectable pi models under the provider aliases (e.g. --model aliases/research); selecting one immediately substitutes the concrete model it resolves to, so requests then take the native path (streaming, auth, caching) and the model is pinned for the session (and restored on resume). A fallback stream covers the brief window before substitution. If a chosen model fails to launch, the subagent retries the next-ranked candidate.

    • question.ts, questionnaire.ts — ask the user structured questions.

    • display-image/ — show an image file inline in the user's terminal via the display_image tool (PNG/JPEG/GIF/WebP/BMP, optional caption, width/height in cells), without sending the pixels to the model — use read when the model must see the image. pi does not detect Konsole/Yakuake as image-capable even though they implement the Kitty graphics protocol, and an extension cannot teach the app otherwise: pi bundles its own pi-tui, so setCapabilityOverrides() from an extension only reaches the extension's module instance. The tool therefore decides the protocol from the environment (kitty/ghostty/wezterm/warp/iTerm2, plus Konsole ≥ KDE Gear 22.04) and emits the Kitty/iTerm2 escape sequence itself. It also avoids pi-tui's calculateImageCellSize, which is declared in the .d.ts but not re-exported by the package index (it is undefined at runtime); the sizing math is inlined in image-file.ts, and render() never throws because an exception inside pi's render loop is an uncaughtException that kills the process. Non-PNG files are converted to PNG for the Kitty protocol with ffmpeg when available (iTerm2 carries them as-is; ffmpeg's stderr is captured so diagnostics never spray the TUI). Fullscreen guard: pi exposes no TUI-mode API to extensions (no mode field in the render context, nothing on ctx.ui, no PI_* env), so the mode is resolved the way pi does — --tui-mode > project .pi/settings.json > global settings.json > regular — cached for 1s, with a forceMode override. Images are hidden in fullscreen by default because pi's alt-screen crops/repaints images through its own metadata registry, which never sees extension-rendered images. Config display-image.json (enabled, protocol auto|kitty|iterm2|none, maxWidthCells, maxBytes, convertToPng, showInFullscreen, forceMode); control /display-image [status|on|off|protocol <p>|width <cells>|convert <on|off>|fullscreen <on|off>]. Pure path/sniffing/format/sizing/mode helpers live in image-file.ts (unit-tested).

    • slow-tools.ts — pi does not surface tool durations to the model, so this timestamps each tool call and, when one runs longer than a threshold (default 300s), appends a [slow tool] … took 5m42s (342s). note to its result so the model knows the operation was slow. Config slow-tools.json; /slow-tools [on|off|threshold <seconds>].

    • max-output.ts — clamp the max output tokens sent to every model (default 128k = 131072) via before_provider_request, only lowering an existing value. Handles max_tokens / max_completion_tokens / max_output_tokens and Google's generationConfig.maxOutputTokens. Config max-output.json; /max-output [on|off|tokens <n>].

    • background-tasks.ts, undo.ts, context-meter.ts, coach.ts, jina.ts, openrouter-multi/ — task management, undo, context monitoring, coaching, web search/read, and multi-model routing.

  • agents/ — custom subagent definitions (planner, reviewer, scout, web-researcher, worker).
  • prompts/ — reusable prompt templates (implement, implement-and-review, scout-and-plan).
  • plans/ — design docs written before building (e.g. openrouter-topup.md).
  • bin/ — standalone shell tools (~/.pi/agent/bin is on $PATH):
    • pi-sessions — list saved pi sessions from the terminal, the CLI counterpart of /sessions. pi-sessions [list] [--here] [--limit N] [--query S] [--sort modified|created|messages|title] [--json] [--paths], plus pi-sessions path <id> and pi-sessions resume <id> [--exec]. Reads the JSONL files directly (no dependency on pi internals).

Tests

Pure modules that do not touch pi's virtual modules have dependency-free unit tests using Node's built-in test runner (with native TypeScript type stripping):

node --experimental-strip-types --test \
  extensions/askpass/tests/*.test.ts extensions/askpass/tests/*.test.cjs \
  extensions/justai-auth/tests/*.test.ts \
  extensions/display-image/tests/*.test.ts

They cover the askpass socket server (cache, token, token-file lifecycle and permissions, forget, watchdog, activity events), the shared bridge client (env/env-file resolution, token file, refusal of an unsafe socket, real-binary lookup), the GPG overlay (read-only keyring copy, generated gpg-agent.conf, cleanup), and the JustAI provider (OIDC device flow with PKCE, token refresh, gateway exchange, model mapping).

Pre-push hook

The same tests run automatically before every push (a dependency-free stand-in for Husky — this repo has no npm dependencies). Enable it once per clone:

git config core.hooksPath .githooks

The hook is .githooks/pre-push; bypass a single push with git push --no-verify.

Not in this repo

Secrets and runtime state are intentionally excluded: auth.json, jina-api-key, openrouter-keys.yaml, models-store.json, settings.json, trust.json, sessions/, store-cache/, and reload-runtime-*.json.