Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
00e6204
feat(ocap-kernel): Add system vat infrastructure
rekmarks Jan 28, 2026
d7c7b42
feat(ocap-kernel): Add KernelFacetService and SystemSubclusterManager
rekmarks Jan 28, 2026
65aac80
feat(ocap-kernel): Update Kernel.ts for system subclusters
rekmarks Jan 28, 2026
a5dedcc
feat(kernel-browser-runtime): Add host subcluster utilities
rekmarks Jan 28, 2026
42e0d47
refactor(ocap-kernel): Move kernel-facet.ts to src root
rekmarks Jan 28, 2026
f5f4cc3
test: Add unit tests for system vat infrastructure
rekmarks Jan 28, 2026
a6e2883
refactor(ocap-kernel): Simplify type annotations in SystemSubclusterM…
rekmarks Jan 28, 2026
9fa93e0
feat(kernel-browser-runtime): Use kernel host subcluster for CapTP bo…
rekmarks Jan 28, 2026
30714ae
refactor: Extract and export KernelFacet type from ocap-kernel
rekmarks Jan 28, 2026
04a3f87
refactor: Fix type errors in kernel-host-vat.test.ts
rekmarks Jan 28, 2026
9746e0b
refactor(ocap-kernel): Unify VatSyscall for regular and system vats
rekmarks Jan 28, 2026
baf4ecc
refactor(kernel-browser-runtime): Remove unused captp directory
rekmarks Jan 28, 2026
9bc5267
feat(nodejs): Add host subcluster utilities
rekmarks Jan 28, 2026
6c75628
feat(kernel-facet): Add getVatRoot and rootKref for presence restoration
rekmarks Jan 28, 2026
69c5d8c
test(nodejs): Add e2e tests for system subclusters
rekmarks Jan 28, 2026
b78bc0d
refactor(ocap-kernel): Use transport-based system vat architecture
rekmarks Jan 29, 2026
eccc209
chore: Delete browser-runtime integration test leftovers
rekmarks Jan 29, 2026
516be8a
fix(ocap-kernel): Fix system vat integration and add integration tests
rekmarks Jan 29, 2026
7ec9fd9
fix(ocap-kernel): Avoid deadlock in E(kernelFacet) calls from within …
rekmarks Jan 29, 2026
1bc692c
fix: Use ES2022 target to fix privateMap.get error under SES/lockdown
rekmarks Jan 29, 2026
3a94d2a
test(nodejs): Fix e2e tests for system subclusters
rekmarks Jan 29, 2026
97e4642
refactor(ocap-kernel): Use push-based system vat connections
rekmarks Jan 29, 2026
1250ddb
refactor(ocap-kernel): Remove syscallHandlerHolder abstraction
rekmarks Jan 29, 2026
985e454
refactor(ocap-kernel): Simplify system vat abstraction
rekmarks Jan 29, 2026
974eb28
refactor(ocap-kernel): Unify system vat registration model
rekmarks Jan 30, 2026
5b383a5
refactor(ocap-kernel): Move getCrankResults to VatSyscall
rekmarks Jan 30, 2026
e728195
refactor: Simplify host-vat and SystemVatSupervisor
rekmarks Jan 30, 2026
d793c7c
refactor(ocap-kernel): Unify VatHandle and SystemVatHandle error patt…
rekmarks Jan 30, 2026
83ac8f1
refactor(ocap-kernel): Unify VatHandle and SystemVatHandle via BaseVa…
rekmarks Jan 30, 2026
3f1aba7
refactor(ocap-kernel): Move DeliveryObject to types.ts and simplify c…
rekmarks Jan 30, 2026
a5b96f1
feat(kernel-browser-runtime): Add cross-process system vat supervisor
rekmarks Jan 30, 2026
3b1e3de
refactor(kernel-browser-runtime): Use RPC pattern for host-vat messaging
rekmarks Jan 30, 2026
aaf6d53
fix(nodejs): Resolve delivery object type error
rekmarks Jan 30, 2026
5cef59e
feat(extension): Wire up host vat in background script
rekmarks Jan 30, 2026
3f79b04
fix(extension): Update e2e test kref values for host vat offset
rekmarks Jan 30, 2026
83ac142
feat(omnium-gatherum): Wire up host vat in background script
rekmarks Jan 30, 2026
78bead7
feat(omnium-gatherum): Rename getCapletRoot to getRoot in public API
rekmarks Jan 30, 2026
e7aa623
test(nodejs): Add e2e test for host vat promise resolution
rekmarks Jan 30, 2026
8a8a63f
chore: Delete accomplished claude plan
rekmarks Jan 30, 2026
d101b17
refactor(ocap-kernel): Unify vat and system vat cleanup in cleanupTer…
rekmarks Jan 31, 2026
7be05e6
refactor(ocap-kernel): Remove logging from kernel-facet
rekmarks Jan 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@metamask/kernel-ui": "workspace:^",
"@metamask/kernel-utils": "workspace:^",
"@metamask/logger": "workspace:^",
"@metamask/ocap-kernel": "workspace:^",
"@metamask/streams": "workspace:^",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
71 changes: 19 additions & 52 deletions packages/extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { E } from '@endo/eventual-send';
import {
makeBackgroundCapTP,
makeCapTPNotification,
isCapTPNotification,
getCapTPMessage,
} from '@metamask/kernel-browser-runtime';
import type { CapTPMessage } from '@metamask/kernel-browser-runtime';
import { makeBackgroundHostVat } from '@metamask/kernel-browser-runtime';
import defaultSubcluster from '@metamask/kernel-browser-runtime/default-cluster';
import { delay, isJsonRpcMessage, stringify } from '@metamask/kernel-utils';
import { delay, isJsonRpcMessage } from '@metamask/kernel-utils';
import type { JsonRpcMessage } from '@metamask/kernel-utils';
import { Logger } from '@metamask/logger';
import { ChromeRuntimeDuplexStream } from '@metamask/streams/browser';
Expand All @@ -21,7 +15,7 @@ let bootPromise: Promise<void> | null = null;
// With this we can click the extension action button to wake up the service worker.
chrome.action.onClicked.addListener(() => {
globalThis.kernel !== undefined &&
E(globalThis.kernel).ping().catch(logger.error);
E(globalThis.kernel).getStatus().catch(logger.error);
});

// Install/update
Expand Down Expand Up @@ -87,65 +81,38 @@ async function main(): Promise<void> {
// Without this delay, sending messages via the chrome.runtime API can fail.
await delay(50);

// Create stream for CapTP messages
// Create stream for JSON-RPC messages to kernel
const offscreenStream = await ChromeRuntimeDuplexStream.make<
JsonRpcMessage,
JsonRpcMessage
>(chrome.runtime, 'background', 'offscreen', isJsonRpcMessage);

// Set up CapTP for E() based communication with the kernel
const backgroundCapTP = makeBackgroundCapTP({
send: (captpMessage: CapTPMessage) => {
const notification = makeCapTPNotification(captpMessage);
offscreenStream.write(notification).catch((error) => {
logger.error('Failed to send CapTP message:', error);
});
},
});
// Create host vat - captures kernelFacet from bootstrap automatically
const hostVat = makeBackgroundHostVat({ logger });

// Get the kernel remote presence
const kernelP = backgroundCapTP.getKernel();
globalThis.kernel = kernelP;

// Handle incoming CapTP messages from the kernel
const drainPromise = offscreenStream.drain((message) => {
if (isCapTPNotification(message)) {
const captpMessage = getCapTPMessage(message);
backgroundCapTP.dispatch(captpMessage);
} else {
throw new Error(`Unexpected message: ${stringify(message)}`);
}
});
drainPromise.catch(logger.error);
// Connect to kernel via offscreen pipe
hostVat.connect(offscreenStream);

try {
await E(kernelP).ping();
await startDefaultSubcluster();
} catch (error) {
offscreenStream.throw(error as Error).catch(logger.error);
}
globalThis.kernel = hostVat.kernelFacetPromise;

try {
await drainPromise;
} catch (error) {
const finalError = new Error('Offscreen connection closed unexpectedly', {
cause: error,
});
backgroundCapTP.abort(finalError);
throw finalError;
}
// Verify connectivity and start default subcluster
await E(kernel).getStatus();
await startDefaultSubcluster();
}

/**
* Idempotently starts the default subcluster.
* Must be called after globalThis.kernel is set.
*/
async function startDefaultSubcluster(): Promise<void> {
const status = await E(globalThis.kernel).getStatus();
const { kernel } = globalThis;
if (kernel === undefined) {
throw new Error('Kernel not initialized');
}

const status = await E(kernel).getStatus();
if (status.subclusters.length === 0) {
const result = await E(globalThis.kernel).launchSubcluster(
defaultSubcluster,
);
const result = await E(kernel).launchSubcluster(defaultSubcluster);
logger.info(`Default subcluster launched: ${JSON.stringify(result)}`);
} else {
logger.info('Subclusters already exist. Not launching default subcluster.');
Expand Down
5 changes: 2 additions & 3 deletions packages/extension/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { KernelFacade } from '@metamask/kernel-browser-runtime';
import type { KernelFacet } from '@metamask/ocap-kernel';

// Type declarations for kernel dev console API.
declare global {
Expand All @@ -8,15 +8,14 @@ declare global {
*
* @example
* ```typescript
* const kernel = await kernel.getKernel();
* const status = await E(kernel).getStatus();
* ```
*/
// eslint-disable-next-line no-var,id-length
var E: typeof import('@endo/eventual-send').E;

// eslint-disable-next-line no-var
var kernel: KernelFacade | Promise<KernelFacade>;
var kernel: Promise<KernelFacet> | KernelFacet;
}

export {};
28 changes: 14 additions & 14 deletions packages/extension/test/e2e/control-panel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,19 @@ test.describe('Control Panel', () => {
const v3Values = [
'{"key":"e.nextPromiseId.v3","value":"2"}',
'{"key":"e.nextObjectId.v3","value":"1"}',
'{"key":"ko5.owner","value":"v3"}',
'{"key":"v3.c.ko5","value":"R o+0"}',
'{"key":"v3.c.o+0","value":"ko5"}',
'{"key":"v3.c.kp4","value":"R p-1"}',
'{"key":"v3.c.p-1","value":"kp4"}',
'{"key":"ko5.refCount","value":"1,1"}',
'{"key":"kp4.refCount","value":"2"}',
'{"key":"ko7.owner","value":"v3"}',
'{"key":"v3.c.ko7","value":"R o+0"}',
'{"key":"v3.c.o+0","value":"ko7"}',
'{"key":"v3.c.kp7","value":"R p-1"}',
'{"key":"v3.c.p-1","value":"kp7"}',
'{"key":"ko7.refCount","value":"1,1"}',
'{"key":"kp7.refCount","value":"2"}',
];
const v1koValues = [
'{"key":"v1.c.ko4","value":"R o-1"}',
'{"key":"v1.c.o-1","value":"ko4"}',
'{"key":"v1.c.ko5","value":"R o-2"}',
'{"key":"v1.c.o-2","value":"ko5"}',
'{"key":"v1.c.ko6","value":"R o-1"}',
'{"key":"v1.c.o-1","value":"ko6"}',
'{"key":"v1.c.ko7","value":"R o-2"}',
'{"key":"v1.c.o-2","value":"ko7"}',
];
await expect(
popupPage.locator('[data-testid="message-output"]'),
Expand Down Expand Up @@ -263,16 +263,16 @@ test.describe('Control Panel', () => {
popupPage.locator('[data-testid="message-output"]'),
).not.toContainText(value);
}
// ko3 (vat root) reference still exists for v1
// ko6, ko7 (bob, carol vat roots) references still exist for v1
for (const value of v1koValues) {
await expect(
popupPage.locator('[data-testid="message-output"]'),
).toContainText(value);
}
// kp4 reference dropped to 1
// kp7 reference dropped to 1
await expect(
popupPage.locator('[data-testid="message-output"]'),
).toContainText('{"key":"kp4.refCount","value":"1"}');
).toContainText('{"key":"kp7.refCount","value":"1"}');
await popupPage.click('button:text("Control Panel")');
await popupPage.locator('[data-testid="accordion-header"]').first().click();
// delete v1
Expand Down
2 changes: 1 addition & 1 deletion packages/extension/test/e2e/object-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ test.describe('Object Registry', () => {

test('should revoke an object', async () => {
const owner = 'v1';
const v1Root = 'ko3';
const v1Root = 'ko5';
const [target, method, params] = [v1Root, 'hello', '["Bob"]'];

// Before revoking, we should be able to send a message to the object
Expand Down
4 changes: 2 additions & 2 deletions packages/extension/test/e2e/remote-comms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ test.describe('Remote Communications', () => {
await expect(targetSelect).toBeVisible();
const options = await targetSelect.locator('option').all();
expect(options.length).toBeGreaterThan(1);
await targetSelect.selectOption({ value: 'ko3' });
expect(await targetSelect.inputValue()).toBe('ko3');
await targetSelect.selectOption({ value: 'ko5' });
expect(await targetSelect.inputValue()).toBe('ko5');

// Set method to doRunRun (the remote communication method)
const methodInput = popupPage1.locator('[data-testid="message-method"]');
Expand Down
4 changes: 2 additions & 2 deletions packages/kernel-browser-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@
"test:build": "tsx ./test/build-tests.ts",
"test:clean": "yarn test --no-cache --coverage.clean",
"test:dev": "yarn test --mode development",
"test:integration": "vitest run --config vitest.integration.config.ts",
"test:verbose": "yarn test --reporter verbose",
"test:watch": "vitest --config vitest.config.ts",
"test:dev:quiet": "yarn test:dev --reporter @ocap/repo-tools/vitest-reporters/silent"
},
"dependencies": {
"@agoric/swingset-liveslots": "0.10.3-u21.0.1",
"@endo/captp": "^4.4.8",
"@endo/marshal": "^1.8.0",
"@endo/promise-kit": "^1.1.13",
"@metamask/json-rpc-engine": "^10.2.0",
"@metamask/kernel-errors": "workspace:^",
"@metamask/kernel-rpc-methods": "workspace:^",
Expand All @@ -85,7 +86,6 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.4",
"@endo/eventual-send": "^1.3.4",
"@metamask/auto-changelog": "^5.3.0",
"@metamask/eslint-config": "^15.0.0",
"@metamask/eslint-config-nodejs": "^15.0.0",
Expand Down
14 changes: 14 additions & 0 deletions packages/kernel-browser-runtime/src/host-vat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Host vat utilities for cross-process system vat communication.
*
* The host vat enables a system vat supervisor to run in a different process
* than the kernel. The kernel runs in a Worker, and the supervisor runs in
* the background script. They communicate over a stream using JSON-RPC messages
* and the optimistic syscall model (fire-and-forget with ['ok', null]).
*/

export { makeKernelHostVat } from './kernel-side.ts';
export type { KernelHostVatResult } from './kernel-side.ts';

export { makeBackgroundHostVat } from './supervisor-side.ts';
export type { BackgroundHostVatResult } from './supervisor-side.ts';
Loading
Loading