Skip to content
Lenso
English
Esc
navigateopen⌘Jpreview
On this page

Build a Host in TypeScript

Prepare real runtime inputs, verify the Host lifecycle, and identify current Bun Plugin blockers.

Lenso’s TypeScript/Bun Host uses @lenso/cli/host for composition, generated host.js for lifecycle control, and Rust lenso-host-runtime for the Kernel and Bun/Process Adapters. The Host API exposes start, inspect, and stop; Plugins own business entrypoints.

Verification status and prerequisites

The following results were obtained from a fresh directory on macOS ARM64 on 2026-09-07. This is not yet a working end-to-end Bun Plugin Host quickstart. First verify the real lifecycle with an empty Host, then check the Plugin blocker below.

Path Observed result
Bun Plugin scaffold, check, pack Passed; produced Bundle 4
Add that Bundle to a TypeScript Host Runtime profile admission rejected it
Empty Host build, check, show, prepare Passed
Generated entrypoint start, inspect, stop, start again Passed; recovered the same Generation
Physical termination termination: confirmed, but forced: true
Business invocation through CLI plugin dev Failed with a nested runtime error

You need Git, the Rust toolchain, Bun, and macOS ARM64. Run these commands in a fresh directory. Validation used Bun 1.4.1-canary.1; it does not establish support for every Bun version or another platform.

mkdir host-walkthrough
cd host-walkthrough
bun add --dev @lenso/cli@0.16.1

Obtain the runtime inputs

The npm CLI ships a platform resolver and generated control library, not the complete Host runtime. Build the two binaries from the pinned sources and lockfiles used in this verification, rather than relying on a global CLI:

git clone https://github.com/LioRael/lenso-bun-adapter.git runtime-source
git -C runtime-source checkout 3c1f2f011f4f9d496db337f296ad686ab0a985e0
cargo build --locked --manifest-path runtime-source/Cargo.toml -p lenso-host-runtime

git clone https://github.com/LioRael/lenso-runtime-rust.git owner-source
git -C owner-source checkout 1e3915a
cargo build --locked --manifest-path owner-source/Cargo.toml \
  -p lenso-runner --bin lenso-process-owner --features process-owner
Preparation input Source
--runtime runtime-source/target/debug/lenso-host-runtime, version 0.1.5
--owner owner-source/target/debug/lenso-process-owner, from lenso-runner 0.2.13
--resolver node_modules/@lenso/cli/vendor/darwin-arm64/lenso
Control library Passed by the npm launcher; do not substitute an older global lenso
--bun Required when selecting Bun implementations; supply a real target executable
--notices Nonempty third-party notices applicable to the files you distribute

If CARGO_TARGET_DIR is set, use that output directory instead of the default paths above. For this local empty-Host demonstration, use the source license as the notices input:

cp runtime-source/LICENSE THIRD_PARTY_NOTICES.txt

This is a local demonstration input, not a complete redistribution notice for the runtime. Before shipping to others, collect applicable notices for the actual binaries, dependencies, and Bun, and use release builds for the intended target.

Build an empty Host

This empty-host.ts needs no hypothetical example Bundle. It verifies the control path and intentionally runs no business Plugin.

import { defineHost } from "@lenso/cli/host";
export default defineHost({ id: "example.empty-host", plugins: [] });
bun --bun run lenso app build --source empty-host.ts --target aarch64-apple-darwin --out build/empty
bun --bun run lenso app check --root build/empty
bun --bun run lenso app show --root build/empty --json

Expect zero Instances and bindings. Each build/prepare needs a fresh output directory. Build output only contains authority and the Bundle inventory; lenso run cannot start it.

Prepare and start

bun --bun run lenso app prepare \
  --build build/empty --target aarch64-apple-darwin \
  --runtime runtime-source/target/debug/lenso-host-runtime \
  --owner owner-source/target/debug/lenso-process-owner \
  --resolver node_modules/@lenso/cli/vendor/darwin-arm64/lenso \
  --notices THIRD_PARTY_NOTICES.txt --out dist/empty
mkdir -p state/app

The empty Host does not need --bun. A distribution selecting Bun implementations must also supply its exact Bun executable. The distribution lock verifies every immutable file; do not rewrite hashes to bypass verification failures. Use state/app as the mutable Root. Do not run against build/empty or copy a competing Host authority into the Root.

Create this script to wait for readiness, inspect the App, and stop through the generated entrypoint:

import { start } from "./dist/empty/host.js";

const app = await start({ root: `${import.meta.dir}/state/app` });
try {
  console.log(JSON.stringify(await app.inspect()));
} finally {
  const outcome = await app.stop();
  console.log(JSON.stringify(outcome));
  if (outcome.shutdown !== "suspended" ||
      outcome.ownership.termination !== "confirmed") {
    process.exitCode = 1;
  }
}
bun lifecycle.ts
bun lifecycle.ts

start returns after the Ready Gate opens. Inspection includes revision, generation, instances, and diagnostics. Restart should recover the same Generation; revision can change. The observed outcome was shutdown: suspended, termination: confirmed, and forced: true. The first two establish durable suspension and confirmed process termination. forced means cleanup escalated; this is not fully graceful exit. The script preserves that field in its output.

For a long-running session, use bun dist/empty/host.js --root "$PWD/state/app"; Ctrl+C/SIGTERM requests shutdown. The default ownership registry is .lenso-owners next to the Root. Repeated starts must share that registry; do not change it or delete locks to bypass ownership conflicts. start also accepts registry, startupMs, stopMs, and confirmationMs. These bound waiting, not business shutdown guarantees.

Add a real Bun Plugin: current blocker

These commands generate and pack a real Bundle instead of referencing a nonexistent company.notes artifact:

bun --bun run lenso plugin new example.host-echo --runtime bun
bun --bun run lenso plugin check --repo-root example.host-echo --json
bun --bun run lenso plugin pack --repo-root example.host-echo --output echo.lenso-plugin --json
import { defineHost, pluginBundle } from "@lenso/cli/host";
export default defineHost({
  id: "example.echo-host",
  plugins: [pluginBundle("./echo.lenso-plugin")],
});
bun --bun run lenso app build --source app.ts --target aarch64-apple-darwin --out build/echo

On the verified CLI version, the final command reports V4 Bundle has no implementation admitted by Host policy. The Bundle declares lenso.bun-authoring@2, while the current builder’s V2 admission uses lenso.plugin-authoring@2. Selecting --target javascript-bun does not fix this profile rejection. Do not edit the manifest, forge digests, or use empty-Host success as a substitute for Plugin acceptance.

Business calls belong to the Plugin’s public entrypoint or development-time plugin dev; app.inspect() only reads structure and there is no app.invoke(). The verified plugin dev --operation execute attempt failed with Cannot start a runtime from within a runtime, so no successful business return was obtained. The separate document-sync test Host is not proof that this distribution path works.

bun --bun run lenso plugin dev --repo-root example.host-echo \
  --operation execute \
  --request-json '{"name":"example.host-echo","arguments_json":"{\"text\":\"hello\"}"}'

Multiple Instances, extensions, and configuration

This is a declaration example requiring your own admitted Store/Copy Bundles; it is not an executable continuation of the echo example. Assume Store offers the store Slot and Copy declares both named dependencies.

import { defineHost, pluginBundle } from "@lenso/cli/host";
const store = pluginBundle("./store.lenso-plugin");
export default defineHost({
  id: "company.copy-host",
  plugins: [
    { plugin: store, instance: "source", configuration: { namespace: "source" } },
    { plugin: store, instance: "destination", configuration: { namespace: "dest" } },
    pluginBundle("./copy.lenso-plugin"),
  ],
  slots: [{ id: "store", cardinality: "many" }],
  dependencies: [
    {
      consumer: { plugin: "company.copy" }, requirement: "source",
      allow: [{ plugin: "company.store", instance: "source" }],
      default: { plugin: "company.store", instance: "source" },
    },
    {
      consumer: { plugin: "company.copy" }, requirement: "destination",
      allow: [{ plugin: "company.store", instance: "destination" }],
      default: { plugin: "company.store", instance: "destination" },
    },
  ],
});

Hosts are closed by default. Existing configuration must pass the Plugin Schema; new Instances and replacement Releases need explicit authorization. Extensible Slots use allow: [pluginBundle(...)] and maxInstances (1–256). Replacing a one Slot also needs replaceable: true. configurationSchema constrains the merged configuration in addition to the Plugin Schema; it is not an OS sandbox. See the Host declaration reference for full authorization examples and supported Schema keywords.

Paths resolve relative to the declaration file. Declarations support static objects, arrays, constants, and relative default imports. They do not evaluate environment reads, dynamic imports, spreads, arbitrary calls, or business modules. The Host declaration profile accepts Request and at most 256 Instances/Slots; Bun SDK Stream/Event support does not expand it.

Recovery, upgrades, and troubleshooting

After stopping, retain the external Root and shared registry and restart the unchanged distribution. The evidence establishes exact-Generation recovery, not arbitrary version upgrades. Build/prepare an upgrade into a new directory, confirm the old process has terminated, and verify compatibility with durable state; back up the Root and persistent data. There is no public generic hot-upgrade command. Do not overwrite a running distribution or edit its lock.

Symptom Check and action
Host Catalog is unavailable Confirm build succeeded; do not inspect output that was never produced
V4 Bundle has no implementation admitted by Host policy Compare execution class, runtime profile, and target; echo has the reproduced blocker above
Cannot start a runtime from within a runtime Reproduced in CLI dev; longer timeouts cannot fix the runtime integration
prepared Host needs the generated npm control library Invoke prepare through the project’s npm launcher
distribution target ... does not support ... Supply artifacts matching the actual platform/architecture
failed integrity / not a regular file Restore original files and check symlinks/content; do not bypass verification
Ownership conflict Inspect the original process and shared registry; do not delete locks or switch registries to force concurrency
Startup timeout Ready was not confirmed; inspect profiles, Bundles, and Root, not just process existence
shutdown: failed, unconfirmed termination, or forced: true Preserve business shutdown and physical termination separately; do not report graceful success

This verification covers declaration checks, real empty-Host lifecycle, and the reported failures. It does not establish a complete Bun Plugin distribution, crash injection, or cross-version upgrades. Rerun the same workflow and update the status table after the integration is corrected.

Last updated on September 6, 2026

Was this page helpful?