Skip to content

Conversation

@rekmarks
Copy link
Member

@rekmarks rekmarks commented Jan 14, 2026

  • Add Phase 1a: Single echo caplet with buildRootObject pattern
  • Add Phase 1b: Store and retrieve caplet root krefs
  • Add caplet manifest loading and installation
  • Return bootstrapRootKref from launchSubcluster
  • Add omnium.loadCaplet() for dynamic caplet loading
  • Fix vatPowers.logger in browser vats

Part 3 of 4 in PR stack
Depends on: #752
Followed by: #754


Note

Introduces a structured launch result and initial caplet plumbing.

  • Kernel: launchSubcluster now returns SubclusterLaunchResult with subclusterId, bootstrapRootKref, and bootstrapResult; Kernel and SubclusterManager updated accordingly with tests
  • Browser runtime: kernel facade maps to { subclusterId, rootKref }; new getVatRoot(krefString); CapTP kref marshalling tables added (currently disabled); integration/unit tests updated
  • RPC: launchSubcluster handler/spec now return a JSON-safe object (bootstrapResult nullable)
  • Omnium: add omnium.loadCaplet() to fetch manifest/bundle; CapletController stores rootKref, exposes getCapletRoot; echo caplet vat + manifest and integration tests; Vite copies caplet assets; build adds build:vats
  • Misc: pass vatPowers.logger to iframe vats; update NodeJS/test helpers and types for new API

Written by Cursor Bugbot for commit d5bfd65. This will update automatically on new commits. Configure here.

@rekmarks rekmarks requested a review from a team as a code owner January 14, 2026 22:28
@rekmarks rekmarks marked this pull request as draft January 14, 2026 23:26
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 451a6dd to 605f8d9 Compare January 14, 2026 23:45
@rekmarks rekmarks force-pushed the rekm/controller-architecture branch 2 times, most recently from 6736065 to 89e5113 Compare January 15, 2026 00:00
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 605f8d9 to f08383c Compare January 15, 2026 00:01
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from f08383c to 34a1f08 Compare January 15, 2026 06:25
@rekmarks rekmarks force-pushed the rekm/controller-architecture branch from 89e5113 to ee93a06 Compare January 15, 2026 06:31
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 34a1f08 to 4caf528 Compare January 15, 2026 21:29
github-merge-queue bot pushed a commit that referenced this pull request Jan 20, 2026
Answers @kumavis's challenge of "What're you afraid of CapTP or
something?" by replacing our kernel JSON-RPC API with `E()` on a facet
of the kernel. This makes it easy to expose as much of the kernel API as
we want via eventual send, and allows us to benefit from pipelining
internally. In addition, it facilitates the removal of the command
stream and the related RPC API logic. Finally, a number of
rationalizations are applied to the extension and omnium.

This PR is part of a stack followed by: #752, #753, and #754



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces CapTP-based communication and removes the JSON-RPC command
stream.
> 
> - Add CapTP utilities in `kernel-browser-runtime`:
`makeBackgroundCapTP`, `makeKernelCapTP`, `makeKernelFacade`, and
JSON-RPC `captp` notification helpers; export new `KernelFacade` and
`CapTPMessage` types
> - Update `kernel-worker` to use CapTP for background↔kernel messaging;
initialize kernel without a command stream; route internal RPC only for
panel/internal comms
> - Refactor `@MetaMask/ocap-kernel` to drop command stream handling and
kernel RPC entrypoints; `Kernel.make` now accepts only platform services
and the database; adjust `stop()` accordingly
> - Migrate extension and omnium backgrounds/offscreens to CapTP over
`ChromeRuntimeDuplexStream`, use global `E` and `kernel`/`omnium`
helpers; remove `env/dev-console` and background trusted prelude files;
add TS globals
> - Add unit and integration tests for CapTP (`background-captp`,
kernel-side CapTP, and E() end-to-end); introduce
`vitest.integration.config.ts` and `test:integration` scripts; CI gains
an "Integration Tests" job
> - Update deps (add `@endo/captp`, `@endo/eventual-send`), tsconfigs,
build constants, and minor coverage thresholds
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
cbf22fd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
@rekmarks rekmarks force-pushed the rekm/controller-architecture branch from d014ffd to 1ab49c9 Compare January 20, 2026 22:09
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 9ab3158 to 84ed1b2 Compare January 20, 2026 22:19
github-merge-queue bot pushed a commit that referenced this pull request Jan 22, 2026
Introduces "controllers" to Omnium. A controller is an object (class)
responsible for a slice of application state. We inherit this
nomenclature from MetaMask.

In detail:
- Add modular controller architecture with POLA attenuation via
`makeFacet()`
- `makeFacet()` is tentative, and may be removed later in the stack in
favor of just public and private methods.
- Add `ControllerStorage` abstraction with `immer`-based state
management and debounced persistence
- Add platform storage adapters and Chrome-specific implementation
- Add `Controller` abstract base class
- Add `CapletController` for managing installed caplets

**Part 2 of 4 in PR stack**
Depends on: #751
Followed by: #753 



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces a modular controller layer and integrates it into the
extension background, enabling managed, persisted state and caplet
lifecycle operations.
> 
> - **Controller framework:** Adds abstract `Controller`,
`ControllerStorage` (immer-based with debounced, per-key persistence),
and a Chrome storage adapter
> - **Caplet management:** New `CapletController` with
`install/uninstall/list/get`, manifest validation, and kernel subcluster
launch/terminate via `initializeControllers`
> - **Background integration:** Refactors globals to `omnium` (`E`,
`getKernel`, `ping`) and exposes `omnium.caplet` methods; adds error
propagation to offscreen stream
> - **Types/utilities:** Exports `Promisified` type; caplet types/guards
with superstruct and semver
> - **Config/tests:** Adds deps (exo, superstruct, immer, semver),
grants `storage` permission in manifest, and comprehensive unit/e2e
tests
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0e330b6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Base automatically changed from rekm/controller-architecture to main January 22, 2026 19:08
rekmarks and others added 13 commits January 22, 2026 12:07
Implements Phase 1a of the caplet system, establishing the foundational
architecture for caplet vats with a working echo-caplet example. This
validates the caplet vat contract and installation lifecycle before
tackling service injection complexity.

Changes:
- Add comprehensive caplet vat contract documentation
- Create echo-caplet.js demonstrating buildRootObject pattern
- Add bundle build script using @endo/bundle-source
- Implement caplet integration tests (8 new tests, all passing)
- Create test fixtures for caplet manifests
- Refactor makeMockStorageAdapter to support shared storage
- Add plan in .claude/plans for follow-up work

Key achievements:
- Caplet vat contract fully documented with examples
- Echo-caplet bundles successfully (696KB)
- Install/uninstall lifecycle tested and working
- Service lookup by name validated
- State persistence across controller restarts verified
- 100% code coverage for CapletController maintained

Deferred to future work (Phase 1b):
- Kref capture mechanism
- Service parameter injection
- Consumer caplet implementation
- Two-caplet communication

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements Phase 1b functionality to store caplet root kernel references (krefs) and expose them via omnium.caplet.getCapletRoot().

This enables: omnium.caplet.install(manifest), omnium.caplet.getCapletRoot(capletId), and E(presence).method() for calling vat methods from background console.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit type annotation for kernelP and use spread operator for optional rootKref field.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove KrefWrapper type from kernel-browser-runtime types
- Make rootKref a required string field in LaunchResult (not optional)
- Make rootKref required in InstalledCaplet and omnium LaunchResult
- Add assertions in kernel-facade for capData, subclusterId, and rootKref
- Remove isKrefWrapper function (inline check kept in makeKrefTables)
- Update tests to use simplified types and improved mocks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tests for validation errors in kernel-facade launchSubcluster:
- Throws when kernel returns no capData
- Throws when capData body has no subclusterId
- Throws when capData slots is empty (no root kref)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add omnium.manifests.echo so users can install caplets from the console:
  await omnium.caplet.install(omnium.manifests.echo)

Changes:
- Create src/manifests.ts with echo caplet manifest using chrome.runtime.getURL
- Add echo-caplet.bundle to vite static copy targets
- Expose manifests in background.ts via omnium.manifests
- Update global.d.ts with manifests type and missing getCapletRoot

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add omnium.loadCaplet(id) to dynamically fetch caplet manifest and bundle
- Fix vatPowers.logger missing in browser vats (iframe.ts)
- Fix SubclusterLaunchResult to return bootstrapRootKref directly
  instead of trying to extract it from bootstrap() return slots

The bootstrapRootKref is the kref of the vat root object, which is
already known when the vat launches. Previously we incorrectly tried
to get it from the slots of the bootstrap() method return value.

Next step: Wire up CapTP marshalling so E(root).echo() works with
the caplet root presence.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use nullable() instead of optional() for bootstrapResult field, and
define a JSON-compatible LaunchSubclusterRpcResult type that uses null
instead of undefined for JSON serialization.

Also update tests to match the new behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 84ed1b2 to 675ff3e Compare January 22, 2026 20:57
@github-actions
Copy link
Contributor

github-actions bot commented Jan 22, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 87.66%
⬇️ -0.71%
5701 / 6503
🔵 Statements 87.56%
⬇️ -0.68%
5793 / 6616
🔵 Functions 86.74%
⬇️ -0.76%
1485 / 1712
🔵 Branches 83.9%
⬇️ -0.53%
2059 / 2454
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/kernel-browser-runtime/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-browser-runtime/src/kernel-worker/captp/kernel-captp.ts 13.88%
⬇️ -86.12%
0%
⬇️ -100.00%
12.5%
⬇️ -87.50%
13.88%
⬇️ -86.12%
55-166
packages/kernel-browser-runtime/src/kernel-worker/captp/kernel-facade.ts 90%
⬇️ -10.00%
100%
🟰 ±0%
87.5%
⬇️ -12.50%
90%
⬇️ -10.00%
43
packages/kernel-browser-runtime/src/rpc-handlers/launch-subcluster.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-browser-runtime/src/vat/iframe.ts 0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
11-42
packages/kernel-test/src/utils.ts 86.95%
🟰 ±0%
70.58%
🟰 ±0%
94.44%
🟰 ±0%
86.66%
🟰 ±0%
43, 112, 117, 158-173
packages/ocap-kernel/src/Kernel.ts 94.66%
🟰 ±0%
87.5%
🟰 ±0%
92.3%
🟰 ±0%
94.66%
🟰 ±0%
111, 217-220, 402, 472
packages/ocap-kernel/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/vats/SubclusterManager.ts 98.5%
⬆️ +0.02%
94.44%
🟰 ±0%
100%
🟰 ±0%
98.5%
⬆️ +0.02%
221-223
packages/omnium-gatherum/src/background.ts 0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
23-246
packages/omnium-gatherum/src/global.d.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/omnium-gatherum/src/controllers/index.ts 0% 100% 0% 0% 66-99
packages/omnium-gatherum/src/controllers/caplet/caplet-controller.ts 100% 100% 100% 100%
packages/omnium-gatherum/src/controllers/caplet/types.ts 100% 100% 100% 100%
Generated in workflow #3298 for commit d5bfd65 by the Vitest Coverage Report Action

Add comprehensive unit test coverage for the getCapletRoot method:
- Test successful retrieval of caplet root and getVatRoot call verification
- Test error case when caplet not found
- Test error case when caplet has no root object (empty rootKref)

Co-Authored-By: Claude <noreply@anthropic.com>
@rekmarks rekmarks marked this pull request as ready for review January 23, 2026 03:43
@rekmarks rekmarks marked this pull request as draft January 23, 2026 03:44
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

uninstall: async (capletId: string) =>
E(capletController).uninstall(capletId),
list: async () => E(capletController).list(),
load: loadCaplet,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadCaplet exposed at wrong path in implementation

High Severity

The loadCaplet function is exposed as omnium.caplet.load but the type definition declares it as omnium.loadCaplet. This mismatch causes the documented API omnium.loadCaplet('echo') to fail at runtime with an undefined error, while only omnium.caplet.load('echo') would work. The function should be added as a top-level property of omnium, not nested under caplet.

Fix in Cursor Fix in Web

// Return wrapped kref for future CapTP marshalling to presence
// TODO: Enable custom CapTP marshalling tables to convert this to a presence
return { kref: krefString };
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCapletRoot returns plain object not CapTP presence

High Severity

The getCapletRoot method promises to return a CapTP presence (per JSDoc at line 298) that can be invoked with E(), but the implementation returns a plain object { kref: string } instead. The getVatRoot method in kernel-facade returns this plain object with a TODO comment indicating the feature is incomplete. This means calling methods on the returned "presence" like E(await omnium.caplet.getCapletRoot('echo')).echo('hello') fails because the object isn't actually a CapTP presence.

Additional Locations (1)

Fix in Cursor Fix in Web

throw Error(`this can't happen but eslint is stupid`);
}
return kunser(bootstrapResultRaw);
return bootstrapResult && kunser(bootstrapResult);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper silently returns undefined instead of throwing error

Medium Severity

The runTestVats helper now returns undefined when bootstrapResult is undefined, whereas the previous implementation (and the kernel-test version) throws an error. This silently changes error handling behavior and could mask test failures when a bootstrap method unexpectedly returns nothing. Tests that previously would fail fast with an explicit error now receive undefined and may continue executing with incorrect state.

Fix in Cursor Fix in Web

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.

2 participants