aloita-extensions

A Pi extension: one Pi session connects to an Aloita server and works on tickets natively.

Packages

Package details

extension

Install aloita-extensions from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:aloita-extensions
Package
aloita-extensions
Version
0.4.16
Published
Aug 17, 2026
Downloads
496/mo · 360/wk
Author
frankvl76
License
MIT
Types
extension
Size
558.4 KB
Dependencies
1 dependency · 4 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

aloita-extensions

A Pi extension. One long-running Pi session connects to your Aloita server and works on tickets natively — no separate MCP server or subprocesses.

Install

Requires Pi (@earendil-works/pi-coding-agent) on your PATH and Node 18+.

pi install npm:aloita-extensions

Pi auto-loads the extension on every startup. Manage it with:

pi list                            # confirm it's registered
pi remove npm:aloita-extensions    # unregister
pi update --extensions             # update installed packages

Configure

Set your server URL and API key via environment variables:

export ALOITA_URL="https://your-aloita-server"
export ALOITA_API_KEY="kai_your_key_here"

Or run /aloita-configure inside Pi to set them (and other options) in an interactive settings screen — values persist to ~/.pi/aloita.json and apply to every Pi session on the machine.

A config.example.json ships with the package; copy it to ~/.pi/aloita.json (global) or <cwd>/.pi/aloita.json (project-local) and edit.

Launch pi. The footer shows the connection status when the socket is up.

Commands

Command Action
/aloita-status Show connection state, active ticket, queue.
/aloita-connect Force reconnect.
/aloita-disconnect Disconnect.
/aloita-next Drain the next queued ticket.
/aloita-configure Interactive settings UI.

Auto-start: how the settings interact

Two independent things exist — ticket pickup and the auto-start supervisor. They do not affect each other:

  • Ticket pickup happens whenever a Pi instance with this extension is running in (or resolves to) a project's folder. It works regardless of any auto-start setting. If you cd into a project folder and start pi yourself, it picks up that project's tickets even with every auto-start option off.
  • The auto-start supervisor (autoStartProjects, the "Auto-start supervisor" master switch) only controls whether this Pi session additionally launches child pi instances for other projects. Turning it off never stops ticket pickup — it only stops the launching of new instances.

Per setting:

Setting What it actually does
autoStartProjects (master switch) ON: this session polls /api/projects and spawns a child pi per allow-listed project. OFF: this session launches nothing. Ticket pickup is unaffected either way.
Project allow-list (autoStartProjectIds, the per-project auto-start on/off in /aloita-configure) Only projects in this list are ever launched. A project that is OFF is simply never launched automatically — but a manually started pi in its folder still picks up its tickets.
autoStartSeedExisting ("Launch for all existing projects") ON (default): when the supervisor starts, it launches every allow-listed project immediately — start one Pi and the rest of the fleet comes online. OFF: only projects created after the supervisor started are launched.
autoStartLinuxTerminal Linux only: auto (default) opens each child in a visible terminal window (parity with the console windows on Windows); none = invisible background process; any other value = explicit terminal command. Headless sessions (no DISPLAY/WAYLAND_DISPLAY) always fall back to invisible.

Every spawned child also runs the supervisor loop; duplicate launches are prevented by per-project pidfiles (<folder>/.pi/aloita.autostart.pid) — a project whose pidfile holds a live pid is never launched again.

Error recovery

When a run ends errored — an assistant turn finished with stopReason: "error", almost always a transient model-provider failure (rate limiting / overload) — the plugin automatically requeues the ticket at the front of the queue and re-runs it once (erroredAutoRetry, default true). The failed attempt still posts its usage (flagged as an error run) so the cost view shows one row per attempt; the ticket's final status and run diff come from the retry's outcome. A ticket that fails on the retry too is left in errorStatus (default In Progress) for manual triage — there is no retry loop, and runs cancelled via StopTask are never requeued. Set "erroredAutoRetry": false to restore the immediate-finalize behaviour. See docs/investigation-errored-runs-2026-08-17.md for the incident that motivated this.

Backpressure between runs (drainDelayMs, default 0 = off): when > 0, the automatic drain waits that many milliseconds before starting the next queued ticket after a run ends, so burst-triaged tickets don't hit the model provider back-to-back (useful under rate limiting; suggested 5–15 s). Only the automatic agent_end drain is delayed — the first ticket arriving on an idle session and manual /aloita-next remain immediate, and a StopTask during the window still drops the queued ticket.

Assigned model (complexity tiers)

When Aloita's complexity scan stamps a model on a ticket (custom fields assigned_tool / assigned_provider / assigned_model), the extension switches Pi to that model right before the ticket's prompt is delivered — so the run uses the tier's model, not whatever the session started with. If the assignment targets a different tool, or the model isn't in Pi's registry (check the model list and API keys on this machine), the current model is kept and the reason is logged/toasted.

Note: the previous model is not restored when the ticket finishes — the session stays on the tier's model until another ticket assigns a different one (or you switch manually via /model). This is intentional: a restore would add a second model switch per ticket for no functional gain, and the next assigned ticket would switch again anyway. Changing this would be a deliberate behavior change, not a bug fix.

Logging

Diagnostics go to ~/.pi/aloita.log (never stdout/stderr, which would corrupt Pi's TUI). Set the level via ALOITA_LOG_LEVEL (debug/info/warn/error/off, default info) and the path via ALOITA_LOG_FILE.

Developer notes

Import extensions: .ts for runtime imports, .js for import type

Cross-module imports inside src/ follow one rule:

  • Runtime value imports (anything that loads code, including mixed value + type imports) use the .ts extension:

    import { mergeConfigPatch } from "./config-store.ts";   // ✅ runtime — .ts
    
  • import type statements use the conventional .js extension (they are erased at compile time, so only tsc ever resolves them):

    import type { AloitaConfig } from "./config.js";        // ✅ type-only — .js
    
    import { mergeConfigPatch } from "./config-store.js";   // ❌ runtime import with .js
    

Why: the test suite runs with Node 22's --experimental-strip-types loader (npm testnode --test src/*.test.ts), which resolves specifiers exactly as written — only the .ts sources exist on disk, so a runtime .js specifier fails with a confusing Cannot find module only when a test transitively loads that module. Pi's extension loader (jiti) is lenient and resolves either form, which is why such an import can survive unnoticed in production while breaking the moment a test touches it. tsc accepts both via allowImportingTsExtensions.

Enforced by npm run lint:imports (and CI): it fails on any non-import type cross-module import in src/ that uses .js.

Verify scripts

scripts/verify/ holds one-shot investigation scripts from past tickets — see scripts/verify/README.md. They are not part of the build, tests, or CI.

License

MIT