Skip to content

Extension system with server-backed panels#397

Open
Anton-Horn wants to merge 9 commits into
mainfrom
feat/extension-system
Open

Extension system with server-backed panels#397
Anton-Horn wants to merge 9 commits into
mainfrom
feat/extension-system

Conversation

@Anton-Horn

@Anton-Horn Anton-Horn commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Adds the extension system end to end: extensions add panels to Cate via a web frontend and an optional local server, served through a token-injecting proxy.

What's in it

  • Runtime capabilities: HTTP/WS tunneling + a managed server host (src/runtime/capabilities/{server,tunnel}.ts)
  • Main: ExtensionManager, ExtensionServerManager, proxy server, catalog download/install, and the cateHost reverse-API dispatch + per-extension storage
  • Preload: cateHost bridge injected into extension webview guests
  • Renderer: ExtensionPanel, ExtensionsSettings, useCateHostActionResponder
  • Shared: manifest/types, cate-host API typings, IPC channels
  • Docs: docs/extensions.md

cate-extensions

The extension catalog is its own repo (0-AI-UG/cate-extensions). It's gitignored here and only checked out locally for offline dev/tests. The two suites that use it (distribution.test.ts, kitchensinkServer.test.ts) skip when that checkout is absent. The Kitchen Sink demo's TS conversion is in 0-AI-UG/cate-extensions#1.

Tests

typecheck clean; extension + runtime-capabilities suites green.

End-to-end extension system that adds panels to Cate via a web frontend
and an optional local server, served through a token-injecting proxy.

- runtime capabilities: HTTP/WS tunneling + a managed server host
  (src/runtime/capabilities/{server,tunnel}.ts)
- main: ExtensionManager, ExtensionServerManager, proxy server, catalog
  download/install, and the cateHost reverse-API dispatch + storage
- preload: cateHost bridge injected into extension webview guests
- renderer: ExtensionPanel, ExtensionsSettings, useCateHostActionResponder
- shared: manifest/types, cate-host API typings, IPC channels
- docs/extensions.md describing the system

The extension catalog lives in its own repo (0-AI-UG/cate-extensions);
it's gitignored here and only checked out locally for dev/tests, which
skip when that checkout is absent.
@Anton-Horn Anton-Horn force-pushed the feat/extension-system branch from b6f994b to 778d1cd Compare June 16, 2026 19:10
- ci.yml: check out + build 0-AI-UG/cate-extensions before unit tests so
  the distribution and kitchensink-server suites run against the real
  artifact instead of skipping.
- kitchensinkServer.test: derive the server entry from the manifest
  command (works for server.js or compiled dist/server.js), guard the
  manifest read so the file imports without the catalog, and annotate the
  WebSocket socket 'data' chunk as Buffer (fixes the CI typecheck failure
  under a stricter @types/node).
- distribution.test: read the built dist/catalog index and assert the
  manifest's declared server entry exists, layout/language-agnostic.
- eslint: ignore examples/ and the gitignored cate-extensions/ checkout
  (their own JS/TS projects), plus generated dist-runtime/ and local
  .cate/ worktrees, so lint only covers app source. This was the ubuntu
  CI lint failure (examples/ sample extensions tripping no-undef).
- ci.yml: run the catalog checkout + build only on non-Windows. The
  catalog repo's bash build.sh resolves a manifest path that breaks under
  Git-Bash/MSYS on Windows; the extension tests skipIf the catalog is
  absent, so the Windows leg stays green.
Add cate.agent.* so an enabled extension can run one agent turn through
the bundled pi. Gated by a dedicated `agent` scope, first-use user
consent, and one run per extension at a time. The run is an owner-bound,
visible agent session that resolves with the final assistant text
(on pi's terminal agent_end). Reachable from both server-backed
extensions (CATE_API) and frontend panels (cateHost bridge).

Also lands the production-readiness fixes from the review: manifest
scope enforcement, sender-derived guest identity, workspace path
confinement, verified and traversal-safe artifact extraction, and
quit/crash process hygiene.
…missions

Remove documentation for cate.* methods that aren't implemented yet
(workspace/theme.onChange, panel lifecycle + setBadge, editor getters,
the commands namespace, and canvas methods beyond createPanel). They
return 'unsupported' today; re-document if/when implemented.

Settings: list each extension's declared cateApi scopes as readable
permission chips (catalog + sideload rows), with the `agent` scope
highlighted as the most sensitive.
Replace the one-shot cate.agent.run with a turn-based session API:
open() returns a handle (pi's session file, so a conversation resumes
with no Cate-side state), send() runs one turn and returns the full
assistant message, dispose() tears down the live client. run() stays as
open->send->dispose sugar.

Drives the same create()/PiRpcClient/dispose() path a panel uses, so pi
owns all conversation state and Cate only holds the live handle. One live
session per extension, one turn in flight, the anti-runaway guard.

Also surface failed turns: a turn that ends on stopReason error (an
unsupported model, auth, bad request) now rejects with pi's reason instead
of returning silent empty text, and the handler passes that reason to the
extension. This was the cause of the persistent "(no text)".
… API

kitchensinkPanel.test.tsx runs the real shipped panel script in jsdom against
a mock cate bridge (mirrors frontendkitPanel) and drives the full surface,
including the agent flow: open-on-first-send, handle persisted to storage,
resume from a stored handle, error rendering, and dispose-on-end.

cateHost.test.tsx locks the preload wire contract: each cate.agent.* call maps
to the right { method, args } and guest identity on CATE_HOST_INVOKE. This was
the one untested layer between an extension calling cate.* and dispatch.
The real extensions live in the separate cate-extensions repo, not in-tree.
A canvas node persisted in `.cate` with a missing or invalid `size`/`origin`
(corrupt, half-written, or older-schema data) was seeded straight into the
store, so CanvasNode/useNodeResize crashed the whole canvas with an opaque
"Cannot read properties of undefined (reading 'width')".

loadWorkspaceCanvas now sanitizes loaded geometry: repair origin/size/counters
where safe, drop the unrecoverable (no panelId), guard viewport/zoom, and log
what changed. The next save persists the repaired state, healing the file.
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