Skip to content

Conversation

@rekmarks
Copy link
Member

@rekmarks rekmarks commented Jan 14, 2026

  • Add kref-presence.ts with makePresenceManager() and convertKrefsToStandins()
  • Add endoify-node.js to kernel-shims for Node.js test environments
  • Split vitest configs for unit vs integration tests
  • Rationalize globalThis.kernel
  • Enable E() calls on vat objects from extension background
  • Expose captp.resolveKref() and captp.krefOf() on globalThis

Part 4 of 4 in PR stack
Depends on: #753


Note

Enables E()-style calls on vat objects and improves test/runtime setup across packages.

  • Add kref-presence utilities: new makePresenceManager and convertKrefsToStandins; exported from @metamask/kernel-browser-runtime; unit and integration tests added
  • Extension/Omnium integration: background now sets globalThis.kernel to a promise, attaches CapTP PresenceManager on globalThis.captp/omnium (exposes resolveKref/krefOf), pings kernel on action, and greets bootstrap vat after launching default subcluster
  • Kernel facade/RPC tweaks: queueMessage converts kref strings to standins; launchSubcluster handler returns JSON-compatible result with nullable bootstrapResult; tests updated
  • Node test environment: new @metamask/kernel-shims/endoify-node (optional @libp2p/webrtc peer) and packages/tests switched to it; remove old local endoify imports
  • Testing/CI: split Vitest configs into unit vs integration (using endoify-node); add test:integration script; GitHub Actions runs build + integration
  • Misc: coverage thresholds updated; small echo-caplet response tweak; dependency/depcheck and resolutions adjustments

Written by Cursor Bugbot for commit 8b56b6b. 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
}
}
return arg; // Pass primitive through
});
Copy link

Choose a reason for hiding this comment

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

Nested presences not converted to kref strings in args

Medium Severity

The sendToKernel function only converts top-level presence objects to kref strings, not presences nested within objects or arrays. When calling E(presence).method({ nested: anotherPresence }), the nested presence won't be converted to its kref string before being passed to CapTP. This creates an asymmetry with convertKrefsToStandins on the kernel side, which does recursively convert nested kref strings. The nested presence may be incorrectly serialized by CapTP since it doesn't know about presences created by the presence manager.

Fix in Cursor Fix in Web

@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/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/caplet-implementation branch from 34a1f08 to 4caf528 Compare January 15, 2026 21:29
@rekmarks rekmarks force-pushed the rekm/kref-presence branch 2 times, most recently from 88b8378 to c7dd594 Compare January 16, 2026 03:57
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 and others added 16 commits January 20, 2026 12:47
Implement Phase 1.2 of the omnium plan - Define Caplet Structure:

- Add modular controller architecture with POLA attenuation via makeFacet()
- Add storage abstraction layer (StorageAdapter, NamespacedStorage)
- Add Chrome storage adapter for platform storage
- Add CapletController for managing installed caplets
- Add Caplet types with superstruct validation
- Wire CapletController into background.ts and expose on globalThis.omnium.caplet
- Add comprehensive unit tests for all controller code
- Update PLAN.md to reflect implementation
Consolidate CapletControllerState from multiple top-level keys
(installed, manifests, subclusters, installedAt) into a single
`caplets: Record<CapletId, InstalledCaplet>` structure.

Changes:
- Add ControllerStorage abstraction using Immer for state management
- Controllers work with typed state object instead of storage keys
- Only modified top-level keys are persisted (via Immer patches)
- Remove state corruption checks (no longer possible with atomic storage)
- Fix makeFacet type - use string | symbol instead of keyof MethodGuard
- Update PLAN.md to reflect new storage architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add abstract Controller class with state management via ControllerStorage
- Convert CapletController to extend Controller base class
- Use makeFacet() pattern for returning hardened exo methods
- Add base-controller tests (12 tests)
- Add semver deep import type declaration
- Add storage permission to manifest.json

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Convert ControllerStorage from factory to class with static make() method
- Implement synchronous update() with debounced fire-and-forget persistence
- Fix critical debounce bug: accumulate modified keys across debounce window
- Implement bounded latency (timer not reset, max delay = one debounce interval)
- Add immediate writes when idle > debounceMs for better responsiveness
- Add clear() and clearState() methods to reset storage to defaults
- Remove old namespaced-storage implementation
- Refactor all tests to use actual ControllerStorage with mock adapters
- Add shared makeMockStorageAdapter() utility in test/utils.ts
- Update controllers to create their own storage from adapters

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove strict reverse DNS format requirement for CapletId to allow more flexibility during early development. Now accepts any non-empty ASCII string without whitespace, removing restrictions on hyphens, underscores, uppercase, and segment count.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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 and others added 17 commits January 20, 2026 14:09
Set globalThis.kernel in the extension and omnium to the kernel itself. Remove
ping and getKernel methods from background console interface. The kernel
exposes ping().
…ects

Implement slot translation pattern to enable E() (eventual sends) on vat
objects from the extension background. This creates presences from kernel
krefs that forward method calls to kernel.queueMessage() via the existing
CapTP connection.

Key changes:
- Add background-kref.ts with makeBackgroundKref() factory
- Add node-endoify.js to kernel-shims for Node.js environments
- Update kernel-facade to convert kref strings to standins
- Fix launch-subcluster RPC result to use null for JSON compatibility
- Integrate resolveKref/krefOf into omnium background

The new approach uses @endo/marshal with smallcaps format (matching the
kernel) rather than trying to hook into CapTP internal marshalling, which
uses incompatible capdata format.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ntegration

Split the vitest configuration into two separate files to fix issues
with tests running from the repo root:
- vitest.config.ts: Unit tests with mock-endoify
- vitest.integration.config.ts: Integration tests with node-endoify

Add test:integration script to run integration tests separately.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…helpers

- Remove packages/nodejs/src/env/endoify.ts re-export, use @metamask/kernel-shims/node-endoify directly
- Update vitest configs to use kernel-shims for setup files
- Remove inline endoify imports from test files (now handled by vitest setup)
- Fix test helpers to handle SubclusterLaunchResult return type from launchSubcluster()
- Add kernel-shims dependency to kernel-test and nodejs-test-workers packages
- Set coverage thresholds to 0 temporarily

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…e configs

- Fix accidentally broken nodejs vat worker (which broke all tests relying
  on it)
- Rename node-endoify.js to endoify-node.js for consistency
- Update package.json export from ./node-endoify to ./endoify-node
- Update all vitest configs to use the new export path
- Update depcheckrc.yml ignore pattern

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import and initialize makeBackgroundKref to enable E() calls on vat objects
- Expose captp.resolveKref and captp.krefOf on globalThis for console access
- Refactor startDefaultSubcluster to return the bootstrap vat rootKref
- Add greetBootstrapVat function that automatically calls hello() on the
  bootstrap vat after subcluster launch on startup
- Update global.d.ts with captp type declaration for IDE support

Co-Authored-By: Claude <noreply@anthropic.com>
- Rename background-kref.ts to kref-presence.ts
- Rename makeBackgroundKref to makePresenceManager
- Rename BackgroundKref type to PresenceManager
- Rename BackgroundKrefOptions to PresenceManagerOptions
- Update all imports and references across affected packages
- Update JSDoc comments to reflect new naming
- All tests pass for kernel-browser-runtime, extension, omnium-gatherum

Co-Authored-By: Claude <noreply@anthropic.com>
…nvertKrefsToStandins

- Move convertKrefsToStandins from kernel-facade.ts to kref-presence.ts for better organization
- Export convertKrefsToStandins for use by kernel-facade
- Add comprehensive unit tests for convertKrefsToStandins (20 tests covering kref conversion, arrays, objects, primitives)
- Add unit tests for makePresenceManager (3 tests for kref resolution and memoization)
- Add integration test in kernel-facade.test.ts verifying kref conversion in queueMessage

Co-Authored-By: Claude <noreply@anthropic.com>
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 9ab3158 to 84ed1b2 Compare January 20, 2026 22:19
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