Slice 6 (qa-native) of the Design image companion (SCR v1.3.1; AC-16,
AC-41, AC-52, AC-54). Test code only; no product source changes.
- `scripts/test-design-companion-native.mjs`: isolated OpenCode daemon,
scripted chat model, fake image model; AC-16 and native AC-52 (re-read
35 assistant messages later). Added to the PR build on
Windows/Ubuntu/macOS.
- `--ui <Design ZIP>` → `scripts/test-design-companion-ui.mjs` +
`fixtures/design-companion.tsx`: real UI S8 path with the installed
extension and real-transcript Markdown rendering (AC-52, AC-54).
- `scripts/panel-extension-native-probe.mjs`,
`test-panel-extension-native.mjs --api4` (also off Windows), new
`test-panel-extension-electron.mjs`: sandbox invariants with negative
controls (AC-41).
- `fixture-exemptions.ts`: registers the Images conversation and API 4
browser tests required by `fixture-wiring`.
Runs: Windows (OpenCode 2.2.1 native + UI path; WebView2 and Electron
runners), Ubuntu VM (WebKitGTK and Electron runners). **Not run:** macOS
WKWebView — the VM stops at its login screen.
#500 asks for a view of a repository's issues and pull requests next to the sessions working on them. Panel extensions are the place for such a view, but they run sandboxed: no network, no project folder, no credentials. Extension API 3 adds one bounded, host-owned capability, `repository.workItems.read`, so an addon lists and opens a project's issues and pull/merge requests without receiving any of those. Worktree actions stay out of scope for a later, separately consented capability.
Host (server/src/forge/): the project folder's `upstream` remote, else `origin`, else any forge remote, strictly validated (host or bracketed IPv6, HTTPS port, path segments), names the repository. github.com, gitlab.com, codeberg.org and gitea.com are read through the user's signed-in `gh api`, `glab api` or `tea api` (tea's stderr status checked, its login matched to the exact authority), falling back to their anonymous REST API for public repositories; any other host is identified and read only through a CLI signed in to it, never contacted anonymously. CodeNomad stores no token. CLIs run without a shell, quietly, within 15 seconds and 4 MiB; anonymous bodies are bounded while streaming. Each read returns one kind, at most 20 items up to page 50, with the open total; GitHub search words are literal quoted terms, and only items under the repository's web address are published. Discovery and page reads are shared loads cancelled with their last caller, never joined once cancelled and never evicted while running; every process or fetch runs within one host-wide budget (four running, sixteen waiting).
Extension API 3: exact permission set, disabled install, dedicated consent text in ten locales; `codenomad.workItems.list({ kind, query, page, fresh })` and `open(kind, number)`. Routes recheck consent before and after each read, cancel the read of a disconnected caller and drop results when the project closed. `open` asks the server for the item's page on the repository's own forge and opens it through the host's opener, one at a time and at most once a second measured at the opener. API 3 panels receive the host colours; the store's consent check is generalised to a permission and also serves API 2.
Docs: PANEL_EXTENSIONS.md and the extensions help page in ten languages.
Validation: forge.test.ts (remotes, CLI output formats, provider mapping and totals, literal search and foreign items, self-hosted access, bounded streams, shared and cancelled loads, budget, pagination), route tests (consent, digest, bounds, revocation, closing, caller disconnection), work-items-channel.test.ts (delayed answers open one tab, three on the first version), API 3 manifests, the extension browser suite, test-panel-extension-native.mjs --api3/--api2, the root typecheck and help checks. Live, an isolated CLI server with the Contribute addon installed from its ZIP listed NeuralNomadsAI/CodeNomad (gh) and gitlab-org/cli (glab) with totals, labels, search, exact numbers and opening, in English and French; Codeberg read anonymously. Three review rounds ended with no findings.
Refs #500.
Add the shared, explicit API 2 session.assets.read capability required by the independently distributed MCP Assets addon. Keep API 1 context-only, preserve one general activation checkbox, and require consent tied to the exact installed package digest.
Use the bundled native history integration for bounded metadata pages and exact embedded tool-result bytes, with session/Location ownership and final consent revalidation. Fence native/backend disconnects, generation changes and stale responses; refresh after failed tool results and direct pruning. Admit bounded source dimensions before host bitmap decoding and preserve thumbnail aspect ratio. Include the new native modules in the packed plugin as well as the shipped bundle.
Two autonomous gatekeeper rounds ended with zero P0–P3 findings on 9da8513326583c7e2b56c5f86554817d478d0cbf; the initial three P2s have regression coverage. Local validation passed 16 browser, 14 server/image, seven package/presence and three bundled-installation checks, both shared typechecks, and the outside-checkout native fixture with official OpenCode 2.0.24. CI native plugin tests passed on Windows/Linux/macOS, as did runtime-contract and compatibility jobs.
Admin merge is explicitly requested by the maintainer. Global CI is not green: its three permission-fallback-diff Copy patch timeouts also occurred in the parent PR #872 (run 37632060353), and Windows reported two unchanged cross-host election-test failures. Preserve these failures as follow-up diagnostics rather than claiming full CI success or modifying unrelated paths in this PR. No protection settings are changed.
## What changes
I add independently distributed right-panel UI extensions and an
official online catalogue in Customize right panel. Users browse/search
available addons, choose one and confirm installation directly from its
exact GitHub Release, without a manual ZIP download, rebuilding
CodeNomad or restarting OpenCode. Manual ZIP installation remains
available.
I publish the curated index, contribution rules and Session info demo at
https://github.com/NeuralNomadsAI/CodeNomad-Extensions. That repository
can host several official addons; the index can also reference
author-owned repositories. Separate means independent of the main
application repository, not a mandatory repository per addon.
I show the manifest and exact archive SHA-256 before trust confirmation.
Installation starts disabled; users enable it for all projects in the
server profile or for the exact opened folder. Replacement requires the
previous digest and revokes every activation grant. Removal is explicit.
Packages and grants stay outside project/OpenCode storage.
I keep author code out of the main renderer. API 1 exposes only session
ID, locale and appearance through a per-mount MessageChannel. It does
not expose filesystem, transcript, images, credentials, native commands
or the OpenCode proxy. Frames are sandboxed without same-origin access
and receive a restrictive CSP; obsolete responses and ports are fenced
on project/session changes, disconnects, revocation and replacement.
I document the package, permissions, API compatibility, scope,
release/checksum and maintainer rules in `dev-docs/PANEL_EXTENSIONS.md`,
with a standalone starter in `examples/panel-extension/`. GitHub's
source ZIP is not an installable package. There is no arbitrary URL
installer or automatic update.
I load catalogue metadata only during discovery. Inspection and
installation each revalidate a fresh index, exact ZIP digest and full
manifest; unsupported APIs/permissions cannot install. Downloads are
bounded, deadline-limited, credential-free HTTPS requests to GitHub's
exact release hosts, with constrained redirects. A withdrawn or changed
selection cannot become a different trusted package. Offline catalogue
failures leave installed addons and ZIP installation available.
## Scope and limits
I keep #801's assets gallery for a separate follow-up. It needs a
bounded, authorized image/history capability rather than access to
internal stores. This PR only establishes external distribution and a
minimal context API.
Windows Tauri may inject native bridge objects into subframes: object
presence is not a grant. My isolated WebView2 fixture proves parent
command execution while direct/raw child calls do not reach it, and
verifies context delivery plus DOM/network restrictions. A trusted
hostile extension can still stall its renderer or attempt
self-navigation; this is not process isolation or a universal offline
guarantee. The installer warns users to trust the author.
I have not run a native Electron/macOS/Linux qualification or changed
the shared daemon. The runtime implementation is shared between desktop
hosts; native GUI validation here is Tauri only.
## Validation
- UI and server typechecks.
- 14 focused server/registry/manifest tests.
- Real right-panel browser tests for ZIP consent, activation scopes,
replacement, revocation, context changes, stale reads and frame
isolation.
- Five catalogue/server checks and seven right-panel browser scenarios
cover metadata-only browse/search, online consent, withdrawal,
incompatibility, offline ZIP fallback, hashes/manifests, redirect
restrictions and size budgets.
- Live public GitHub index and Release download matched SHA-256
`cf80226a40dd077a6a8cc2a4b65bbb46bf33619d62007982bd7dcea34ae4545f`;
installation and activation used a disposable isolated store.
- `node scripts/test-panel-extension-native.mjs` on Windows, using the
application's locked Tauri dependencies and an isolated browser profile:
parent native call allowed; child native calls, parent DOM and network
APIs blocked; session context delivered.
- UI build, Windows Tauri release build (`--no-bundle`) and
packaged-resource smoke passed. Full browser suite results are recorded
in the follow-up validation comment.
Existing oversized files touched only for integration:
`packages/server/src/api-types.ts` (~735 lines),
`packages/server/src/index.ts` (~713),
`packages/server/src/server/http-server.ts` (~2,438).