Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
132 changes: 132 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"crates/agentkeys-types",
"crates/agentkeys-core",
"crates/agentkeys-web-core",
"crates/agentkeys-mock-server",
"crates/agentkeys-cli",
"crates/agentkeys-daemon",
Expand All @@ -19,6 +20,7 @@ members = [
[workspace.dependencies]
agentkeys-types = { path = "crates/agentkeys-types" }
agentkeys-core = { path = "crates/agentkeys-core" }
agentkeys-web-core = { path = "crates/agentkeys-web-core" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
Expand Down
5 changes: 5 additions & 0 deletions apps/parent-control/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ dist/
*.tsbuildinfo
.env*.local
.vercel

# Generated by dev.sh `build_wasm` (agentkeys-web-core via wasm-pack). Cached +
# rebuilt only when the Rust source hash changes; never committed.
lib/wasm/
public/wasm/
92 changes: 92 additions & 0 deletions apps/parent-control/lib/client/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client';

import { EmptyBackend } from './empty';
import type { ConnectionStatus } from './types';

// Lazy, client-only load of the WASM master-plane core (agentkeys-web-core),
// memoized per broker URL. The dynamic import keeps the wasm glue out of the
// server bundle; init() fetches the .wasm from /wasm/ (served from public/,
// written by dev.sh's build_wasm). Keying by URL means a second CoreBackend with
// a different broker gets its own instance; on failure the entry is evicted so
// the next call retries (a transient load/broker failure must not poison it).
type LoadedCore = import('@/lib/wasm/agentkeys-web-core/agentkeys_web_core').WebCore;
const coreByUrl = new Map<string, Promise<LoadedCore>>();
function loadCore(brokerUrl: string): Promise<LoadedCore> {
let p = coreByUrl.get(brokerUrl);
if (!p) {
p = (async () => {
const wasm = await import('@/lib/wasm/agentkeys-web-core/agentkeys_web_core.js');
await wasm.default('/wasm/agentkeys_web_core_bg.wasm');
return new wasm.WebCore(brokerUrl);
})();
coreByUrl.set(brokerUrl, p);
void p.catch(() => coreByUrl.delete(brokerUrl));
}
return p;
}

/**
* CoreBackend — the phone-first host (browser → WASM core → broker DIRECTLY, no
* daemon). X1 of docs/plan/web-flow/wire-real-paths.md: it loads the
* `agentkeys-web-core` WASM module and exposes the broker calls (cap-mint,
* pairing) the onboarding/pairing slices use.
*
* The `AgentKeysClient` READ endpoints (actors, audit, memory, …) are wired in
* the later W-phases, so they inherit EmptyBackend's disconnected behaviour for
* now (the UI shows honest empty states); `status()` exercises the full path
* (load WASM + probe the broker) and reports what happened.
*/
export class CoreBackend extends EmptyBackend {
private brokerUrl: string;

constructor(brokerUrl: string) {
super();
this.brokerUrl = brokerUrl.replace(/\/+$/, '');
}

async status(): Promise<ConnectionStatus> {
try {
await loadCore(this.brokerUrl);
} catch (e) {
return {
kind: 'disconnected',
reason: 'no-backend-configured',
detail: `WASM core failed to load: ${String(e)}`,
};
}
try {
const r = await fetch(`${this.brokerUrl}/healthz`, { cache: 'no-store' });
return {
kind: 'disconnected',
reason: r.ok ? 'no-backend-configured' : 'unreachable',
detail: r.ok
? `WASM core loaded; broker ${this.brokerUrl} reachable. The AgentKeysClient read endpoints wire in later W-phases (wire-real-paths.md).`
: `WASM core loaded; broker /healthz → ${r.status}.`,
};
} catch (e) {
return {
kind: 'disconnected',
reason: 'unreachable',
detail: `broker ${this.brokerUrl} unreachable: ${String(e)}`,
};
}
}

// ── Broker calls in the browser (X1). Beyond the AgentKeysClient interface;
// the onboarding/pairing slices call these directly via the CoreBackend.
async capMemoryPut(bearer: string, req: unknown): Promise<unknown> {
return (await loadCore(this.brokerUrl)).capMemoryPut(bearer, req);
}
async capMemoryGet(bearer: string, req: unknown): Promise<unknown> {
return (await loadCore(this.brokerUrl)).capMemoryGet(bearer, req);
}
async pairingClaim(bearer: string, req: unknown): Promise<unknown> {
return (await loadCore(this.brokerUrl)).pairingClaim(bearer, req);
}
async pendingBindings(bearer: string): Promise<unknown> {
return (await loadCore(this.brokerUrl)).pendingBindings(bearer);
}
async ackBinding(bearer: string, requestId: string): Promise<unknown> {
return (await loadCore(this.brokerUrl)).ackBinding(bearer, requestId);
}
}
10 changes: 9 additions & 1 deletion apps/parent-control/lib/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { CoreBackend } from './core';
import { DaemonBackend } from './daemon';
import { EmptyBackend } from './empty';
import type { AgentKeysClient } from './types';

export type BackendKind = 'empty' | 'daemon';
export type BackendKind = 'empty' | 'daemon' | 'core';

export function selectBackend(): AgentKeysClient {
const kind = (process.env.NEXT_PUBLIC_AGENTKEYS_BACKEND ?? 'empty') as BackendKind;
if (kind === 'core') {
// Phone-first host: the WASM core talks to the broker directly (no daemon). X1.
return new CoreBackend(
process.env.NEXT_PUBLIC_AGENTKEYS_BROKER_URL ?? 'https://broker.litentry.org',
);
}
if (kind === 'daemon') {
return new DaemonBackend(process.env.NEXT_PUBLIC_AGENTKEYS_DAEMON_URL);
}
Expand All @@ -15,3 +22,4 @@ export function selectBackend(): AgentKeysClient {
export * from './types';
export { EmptyBackend } from './empty';
export { DaemonBackend } from './daemon';
export { CoreBackend } from './core';
Loading
Loading