Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
872f87c
feat(channels): add NodeSocketDuplexStream and session channel primit…
grypez May 13, 2026
9526d74
chore(changelog): add changelog entries for channels PR
grypez May 18, 2026
9e1ba56
feat(session-types): add shared SessionSummary, PendingRequest, Sessi…
grypez May 14, 2026
705ca7e
feat(sessions): add session registry, channel factory, and stream soc…
grypez May 13, 2026
eb3e56a
feat(sessions): add history tracking and session.authorize
grypez May 18, 2026
1c0dbc9
chore(changelog): add changelog entries for sessions PR
grypez May 18, 2026
de519e0
feat(session-cli): add session subcommands to kernel-cli
grypez May 13, 2026
7a7d747
feat(modal-tui): add kernel-tui package and modal command to kernel-cli
grypez May 13, 2026
821c09e
feat(kernel-tui): add full multi-view TUI with sessions view
grypez May 13, 2026
38ad282
fix(kernel-tui): omit optional Text props instead of passing undefined
grypez May 13, 2026
0875c10
feat(kernel-tui): add ocap tui command and improve session error message
grypez May 13, 2026
46a3245
refactor(kernel-tui): use shared SessionApi/SessionSummary/PendingReq…
grypez May 14, 2026
a0d39a4
fix(kernel-tui): fix overflow, duplicate titles, and add Sessions header
grypez May 15, 2026
ec8a84e
feat(kernel-tui): add session detail view and history
grypez May 18, 2026
0bbfbac
fix(kernel-tui): poll session history while detail view is open
grypez May 18, 2026
961261d
refactor(kernel-tui): extract session data fetching into useSessionDa…
grypez May 18, 2026
c93ba9a
chore(changelog): add changelog entries for kernel-tui PR
grypez May 18, 2026
739f24b
test: add coverage for session registry, RPC session methods, and use…
grypez May 18, 2026
9b51a11
feat(kernel-tui): reverse entry order, prettify expanded JSON, split …
grypez May 18, 2026
2386bba
feat(kernel-tui): token-stable cursor, top-level decisions, and accep…
grypez May 20, 2026
bdba1e6
feat(caprock): add caprock Claude Code plugin package
grypez May 20, 2026
02c1219
feat(sheaves): add leastAuthority policy
grypez May 21, 2026
a87ec61
feat(kernel-utils): provision types and partial-order algebra
grypez May 21, 2026
ce92fd9
feat(caprock): sheaf-based permission-tracker vat
grypez May 21, 2026
1a62c4a
feat(caprock): rewrite hook to use invocation-based provision routing
grypez May 21, 2026
919be7a
feat(kernel): thread provision through session.decide
grypez May 21, 2026
ebd5074
fix(sheaves): drop extra context arg from noopPolicy call in leastAut…
grypez May 21, 2026
d0ae972
fix(caprock): break @endo dependency chain in hook process
grypez May 23, 2026
bb5e686
feat(session): thread ParsedInvocation through authorization pipeline
grypez May 23, 2026
d07e5fc
feat(kernel-tui): provision editor with per-arg pattern tuning
grypez May 23, 2026
b0e78db
test(caprock): integration test for hook binary startup
grypez May 23, 2026
27571db
feat(session): surface provisioned requests in TUI timeline
grypez May 23, 2026
bbfb371
feat(session): identify matched provision and add active-provisions p…
grypez May 23, 2026
5f267b1
fix(caprock): decode smallcaps ! escape in vat provision responses
grypez May 23, 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
8 changes: 8 additions & 0 deletions packages/caprock/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "caprock",
"version": "0.1.0",
"description": "Routes Claude Code tool invocations through an ocap-kernel permission vat (POLA enforcement).",
"repository": "https://github.com/MetaMask/ocap-kernel",
"license": "MIT"
}
10 changes: 10 additions & 0 deletions packages/caprock/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

[Unreleased]: https://github.com/MetaMask/ocap-kernel/
15 changes: 15 additions & 0 deletions packages/caprock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# `@ocap/caprock`

Claude Code plugin: routes tool invocations through an ocap-kernel permission vat (POLA enforcement)

## Installation

`yarn add @ocap/caprock`

or

`npm install @ocap/caprock`

## Contributing

This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme).
12 changes: 12 additions & 0 deletions packages/caprock/bin/harden-shim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* No-op harden shim for the hook process.
*
* The hook is not a vat — it must not run SES lockdown because full lockdown
* is incompatible with native tree-sitter bindings. @endo modules call
* harden() at module-evaluation time, so we install a benign identity
* function as the global before any @endo import evaluates.
*
* ESM evaluates modules depth-first in import order, so placing this as
* the first import in hook.ts guarantees it runs before @endo/promise-kit.
*/
(globalThis as { harden?: <T>(value: T) => T }).harden ??= (value) => value;
115 changes: 115 additions & 0 deletions packages/caprock/bin/hook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable n/no-process-env */
import { execFile, spawn } from 'node:child_process';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const execFileAsync = promisify(execFile);

const HOOK_BIN = fileURLToPath(
new URL('../dist/bin/hook.mjs', import.meta.url),
);
const PKG_DIR = fileURLToPath(new URL('..', import.meta.url));

/**
* Spawn hook.mjs with a JSON payload on stdin and collect all output.
*
* @param payload - The hook event payload to send.
* @param env - Extra environment variables.
* @param timeoutMs - Kill timeout in milliseconds.
* @returns stdout, stderr, and exit code.
*/
async function runHook(
payload: unknown,
env: NodeJS.ProcessEnv,
timeoutMs: number,
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
return new Promise((resolve, reject) => {
const child = spawn('node', [HOOK_BIN], {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, ...env },
});

let stdout = '';
let stderr = '';

child.stdout.on('data', (chunk: Buffer) => {
stdout += chunk.toString();
});
child.stderr.on('data', (chunk: Buffer) => {
stderr += chunk.toString();
});

const timer = setTimeout(() => {
child.kill();
reject(new Error(`Hook timed out after ${timeoutMs}ms`));
}, timeoutMs);

child.on('close', (code) => {
clearTimeout(timer);
resolve({ stdout, stderr, exitCode: code ?? -1 });
});

child.on('error', (error) => {
clearTimeout(timer);
reject(error);
});

child.stdin.write(JSON.stringify(payload));
child.stdin.end();
});
}

describe('hook binary', () => {
let ocapHome: string;

beforeAll(async () => {
await execFileAsync('yarn', ['build'], { cwd: PKG_DIR });
ocapHome = await mkdtemp(join(tmpdir(), 'caprock-hook-test-'));
}, 60_000);

afterAll(async () => {
await rm(ocapHome, { recursive: true, force: true });
});

it('loads without SES globals (SessionStart)', async () => {
const { stderr, exitCode } = await runHook(
{
hook_event_name: 'SessionStart',
session_id: 'hook-integration-test',
transcript_path: '/dev/null',
},
{ OCAP_HOME: ocapHome },
8_000,
);

expect(exitCode).toBe(0);
expect(stderr).not.toMatch(/harden is not defined/u);
expect(stderr).not.toMatch(/Cannot initialize @endo\/errors/u);
expect(stderr).not.toMatch(/missing globalThis\.assert/u);
}, 8_000);

it('loads without SES globals (PreToolUse)', async () => {
const { stdout, stderr, exitCode } = await runHook(
{
hook_event_name: 'PreToolUse',
session_id: 'hook-integration-test',
transcript_path: '/dev/null',
tool_name: 'Bash',
tool_input: { command: 'ls -la' },
},
{ OCAP_HOME: ocapHome },
8_000,
);

expect(exitCode).toBe(0);
expect(stderr).not.toMatch(/harden is not defined/u);
expect(stderr).not.toMatch(/Cannot initialize @endo\/errors/u);
expect(stderr).not.toMatch(/missing globalThis\.assert/u);
// With no daemon running the hook must not block — it passes through.
expect(stdout).toContain('"continue":true');
}, 8_000);
});
Loading
Loading