qagent-desktop-extensions

Sidecar scaffold (reference)

The canonical, drop-in scaffold every repository-vendored loopback sidecar uses. It makes the secure path the only path: the scaffold owns the HTTP dispatch loop and validates the WebUI-injected X-Hermes-Sidecar-Token deny-by-default — you cannot write an unauthenticated route by accident. See docs/SIDECAR_CONTRACT.md for the full contract.

Files

File Vendored? You edit it?
sidecar_base.py byte-identical (CI-checked) no
sidecar.py (entrypoint) byte-identical (CI-checked) no
sidecar.json per-extension config yes — {id, port, proxy_auth}
routes_impl.py per-extension yes — your routes live here

sidecar_base.py and sidecar.py are kept identical across every vendored sidecar extension by scripts/sync-sidecar-base.mjs --check in CI. To adopt or update:

cp examples/sidecar-scaffold/sidecar_base.py extensions/<id>/sidecar/
cp examples/sidecar-scaffold/sidecar.py       extensions/<id>/sidecar/
# then write extensions/<id>/sidecar/sidecar.json + routes_impl.py
node scripts/sync-sidecar-base.mjs --check   # confirm byte-identity
node scripts/check-sidecar-usage.mjs         # confirm no rogue server

External runtimes declare sidecar.runtime.kind: "external" plus their source repository and implement the language-neutral token-v1 contract in their own language. They do not copy this Python scaffold. See the contract’s runtime ownership section before choosing a mode.

Writing routes

Only routes_impl.py is yours. Everything auth-related is handled for you:

def register(app):
    @app.route("GET", "/api/items/{item_id}")   # path params
    def get_item(req):
        return app.json({"id": req.params["item_id"]})

    @app.route("POST", "/api/upload")
    def upload(req):
        return (200, {"Content-Type": "image/png"}, req.body)   # binary ok

Running

The .service unit’s ExecStart must use /usr/bin/python3 -S [-u] sidecar.py (CI enforces this). The pinned interpreter and mandatory -S keep PATH, sitecustomize, and .pth startup hooks from replacing the checked scaffold. Use one unprefixed ExecStart; if Type is present, it must be simple. The token file is provisioned by WebUI. For a custom state directory, set HERMES_WEBUI_STATE_DIR and place WorkingDirectory at its matching extensions/<id>/<runtime.path> location. If this extension is installed outside that default tree, declare the extension’s exact entry directory as HERMES_EXT_INSTALL_DIR and place WorkingDirectory at its <runtime.path> child. This sidecar-only variable is deliberately distinct from WebUI’s HERMES_WEBUI_EXTENSION_DIR, which can mean either one entry or a gallery root. Custom directories must be absolute or %h-anchored and produce a reviewer warning. The defaults already agree.