Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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 .changeset/619-compile-evidence-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'agent-bundle': patch
---

Record compile evidence beside the emitted files: `agent-bundle build`
writes `agent-bundle.compile-evidence.json` at the artifact root;
`agent-bundle validate --artifact` verifies it against the manifest
file table (`AB6039`). (#638)
6 changes: 6 additions & 0 deletions .changeset/637-state-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"agent-bundle": minor
"@agent-bundle/runtime": minor
---

Separate the generated plugin's state root from its code root: make every artifact-hosted shell (the stdio MCP entry, Flight worker, artifact CLI bin and render worker, rendered script workers, and standalone hook wrappers) keep the SQLite state kernel, notice ledger, and lineage journal under `~/.agent-bundle/state/<plugin>-<digest>` (`$XDG_STATE_HOME/agent-bundle/<plugin>-<digest>` when `XDG_STATE_HOME` is an absolute path) instead of `<plugin root>/state`, so a read-only install launches and two installs never share state; keep `AGENT_BUNDLE_PLUGIN_ROOT` naming the installed code root and let `AGENT_BUNDLE_STATE_ROOT` override the state root; add `stateAnchor` and `home` options and `stateSource` to `resolvePluginRoot`, and export `PLUGIN_STATE_ROOT_ENV_ANCHOR`, `pluginStateSegment`, `userStateHome`, and `userDataStateRoot` from `@agent-bundle/runtime` and `pluginStateRootEnvAnchor` from `agent-bundle`. State existing installs wrote beneath the plugin root is not migrated: after upgrading and rebuilding, an installed plugin starts from an empty state root; set `AGENT_BUNDLE_STATE_ROOT=<old root>/state` to keep using it. `agent-bundle dev` and Workbench MCP sessions pin `AGENT_BUNDLE_STATE_ROOT` to `<epoch>/state`, so their state still lives beside the build epoch; `uninstall --purge-data` and `doctor` still address only the legacy `<root>/state` directory (#641). (#640)
5 changes: 5 additions & 0 deletions .changeset/641-state-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": patch
---

Make `uninstall --purge-data` remove the effective framework state root and make `doctor` report its source, existence, writability, `AB7316` permission failures, and `AB7332` retained pre-#640 state (#641).
6 changes: 6 additions & 0 deletions .changeset/canonical-npm-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"agent-bundle": minor
"create-agent-bundle": minor
---

Package the validated composite root as the npm root, remove the obsolete package installer wrapper, point generated CLI bins at the manifest-declared executable so every command including `web` has artifact parity, expose manifest file kinds through `PackageOutputFile.kind`, keep npm metadata outside host install comparisons, update generated projects for the `dist/` publish root, and report `AB4767` when no selected target emitted that executable (#639).
5 changes: 5 additions & 0 deletions .changeset/runtime-rebundle-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": patch
---

Keep generated stdio entries startable when private runtime modules are added (#636).
75 changes: 56 additions & 19 deletions docs/diagnostics.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/effect-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,8 @@ Wiring rules:
- **Protocol stdout stays raw.** MCP stdio JSON-RPC (`mcp-entry.ts`,
`mcp run`), hook result JSON (`adapters/hook-contract.ts`), the emitted
routed-CLI shell (`cli-entry.ts`'s `writeOut`/`writeErr` ports and the
`entry-shell.ts` bin template), generated installers (`install-entry.ts`,
`install/surface.ts`), and child/worker stderr forwarding keep their direct
`entry-shell.ts` bin template), installers (`install/surface.ts`), and
child/worker stderr forwarding keep their direct
`process.stdout`/`process.stderr` adapters: emitted artifacts must not carry
a platform runtime, and byte-exact protocol frames are not terminal text.
- **The route-facing terminal capability is plain Node, not `Terminal`.**
Expand Down
128 changes: 72 additions & 56 deletions docs/entry-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,20 @@ rendering.

`agent-bundle build` always emits the composite plugin root. When the project
declares `bin`/`lib` (or provides them by convention), the CLI build also
produces the node-consumable package build under `dist/` — the outputs
`package.json` `bin` and `exports` point at:
produces the node-consumable npm root under `dist/`. It copies the validated
composite artifact there and adds `package.json`, standard package docs, and
package-only entries:

| Config | Output | Notes |
| --- | --- | --- |
| `bin: { '<name>': './src/cli.ts' }` | `dist/bin/<name>.js` | Self-executing ESM bundle, `#!/usr/bin/env node` shebang, executable bit. |
| `bin: { '<name>': './src/cli.ts' }` | `dist/bin/<name>.js` | Authored self-executing ESM bundle, `#!/usr/bin/env node` shebang, executable bit. |
| generated `src/cli/**` routes | `dist/bin/<plugin-name>.mjs` | The manifest-declared artifact executable, copied byte-for-byte; `package.json` `bin` points directly at it. |
| `lib: { entry: './src/index.ts', dts: true }` | `dist/<stem>.js` + `dist/**/*.d.ts` | Single-entry ESM profile, node target, es2022 syntax. |

- When package outputs are built inside the project and the plugin root
carries at least one of the `claude`, `codex`, or `cursor` projections, the
framework also emits one self-contained
package-relative installer. It is `dist/bin/<plugin-name>.js` when that name
is free, otherwise `dist/bin/<plugin-name>-install.js`; if both are occupied,
a numeric suffix (`-install-2`, `-install-3`, …) guarantees a free name.
Declare the matching `package.json` `bin` value. Its grammar is
`install <host> [--scope <scope>] [--json]`; help lists only built hosts.
The baked URL resolves the shipped plugin root from `import.meta.url`,
never the caller's working directory, and delegates to the same
`installBundle` implementation as `agent-bundle install`.
- The npm root is not a parallel plugin compilation and contains no nested
`artifact/` directory. Generated routed CLI commands, including `web`, run
from the one artifact executable; no package-relative installer wrapper is
generated.
- `agent-bundle prepack [--root <root>] [--output <artifact>] [--json]` runs
the release build and `npm pack --dry-run --json --ignore-scripts`, then
gates the exact package/artifact inventory, manifest hashes, package bin
Expand Down Expand Up @@ -150,23 +145,33 @@ Volatile lifetimes use the memory driver. Request lifetime opens and releases
fresh project and notice stores per invocation; process lifetime shares them
for the generated worker or executable process.

Workspace-durable generated MCP workers store under
`$AGENT_BUNDLE_PLUGIN_ROOT/state`. If that host-provided anchor is absent,
the worker derives the artifact root from the parent of its own `mcp/`
directory. The npm package's routed CLI bin and rendered scripts use
`$AGENT_BUNDLE_PLUGIN_ROOT/state` when present and otherwise
`$PWD/.agent-bundle/state`; the artifact-hosted routed CLI bin
(`bin/<name>.mjs` in the plugin root) derives the artifact root from the parent of its
own `bin/` directory instead, like the MCP worker. Each generated process
resolves that anchor exactly once (`resolvePluginRoot` from
`@agent-bundle/runtime`, #468): the state kernel, the notice ledger, the
lineage journal, and every request scope the process opens read the same
value, published as `(await agent()).plugin` — `{ root, stateRoot }` with
`source: 'native'` from `AGENT_BUNDLE_PLUGIN_ROOT` or `'derived'` from the
fallback — and handed to conventional providers as `plugin` beside
`invocation` and `signal`. An anchor still carrying an unexpanded `${…}`
token is treated as unset (reported once on stderr), never joined into a
path. Notice authorization is deliberately permissive
Workspace-durable artifact shells — generated MCP workers, artifact CLI bins
and their render workers, rendered script workers, and standalone hook
wrappers — call `resolvePluginRoot`
with `stateAnchor: 'user-data'`. `AGENT_BUNDLE_PLUGIN_ROOT` still names the
code root and otherwise falls back to the artifact root derived from the
shell's own location. An expanded, non-blank `AGENT_BUNDLE_STATE_ROOT`
independently overrides the framework state root and is made absolute with
`resolve()`; otherwise the state root is
`~/.agent-bundle/state/<plugin>-<digest>`, or
`$XDG_STATE_HOME/agent-bundle/<plugin>-<digest>` when `XDG_STATE_HOME` is an absolute path (a relative value is ignored).
`<plugin>` is the code root's safe basename (or `plugin`) and `<digest>`
is the first 16 hexadecimal characters of SHA-256 over that code root's
realpath, so symlinked spellings share one state root while distinct installs
do not. `resolvePluginRoot` uses `os.homedir()` unless its `home` test seam is
supplied. The npm package's routed CLI bin and rendered scripts keep the default
`stateAnchor: 'root'`: `$AGENT_BUNDLE_PLUGIN_ROOT/state` when supplied and
otherwise `$PWD/.agent-bundle/state`.

Each generated process resolves both roots exactly once
(`resolvePluginRoot` from `@agent-bundle/runtime`, #468): the state kernel,
notice ledger, lineage journal, and every request scope the process opens read
the same `stateRoot`, published with the code `root` as
`(await agent()).plugin` and handed to conventional providers as `plugin`
beside `invocation` and `signal`. `source` records whether the code root was
native or derived; `stateSource` does the same independently for the state
root. An unexpanded `${…}` token in either root override is treated as unset
(and reported once on stderr), never joined into a path. Notice authorization is deliberately permissive
in generated mounting v1 (`authorized`); recipient/principal matching remains
enforced by the ledger — every generated scope mounts the request's `lineage`
on the notice principal, so `recipient.conversation` / `recipient.root` are
Expand Down Expand Up @@ -272,7 +277,7 @@ interface AgentProviderContext {
host: Observed<{ name }>; // exactly what the route reads on `await agent()`
session: Observed<{ sessionId }>;
workspace: Observed<{ root }>;
plugin: Observed<{ root; stateRoot }>; // the resolved plugin root (#468)
plugin: Observed<{ root; stateRoot }>; // resolved code and framework state roots (#468)
lineage: Observed<AgentLineage>; // own chain plus the live `tree` (#457)
state?: { lifetime; read(options?) }; // the mounted state handle, `read` only
notices?: { inbox(); published() }; // the request's notice handle, reads only
Expand Down Expand Up @@ -718,7 +723,7 @@ Per surface, the value the generated request scope mounts:

| Surface | `hostSurface` | `stdout` / `stderr` | Source |
| --- | --- | --- | --- |
| Routed CLI executable (`dist/bin/<name>.js`, plugin-root `bin/<name>.mjs`), plain or rendered command, projected MCP command | `cli` | Probed from the executable's own process; a rendered command's worker thread receives the executable's probe, never its own pipes. Machine output owns fd 1, so `stdout` describes where the rendered document lands and `stderr` the channel a route may write to itself. | `native` |
| Routed CLI executable (npm-root and plugin-root `bin/<name>.mjs`), plain or rendered command, projected MCP command | `cli` | Probed from the executable's own process; a rendered command's worker thread receives the executable's probe, never its own pipes. Machine output owns fd 1, so `stdout` describes where the rendered document lands and `stderr` the channel a route may write to itself. | `native` |
| Rendered script (`scripts/<name>.mjs` from `src/scripts/<name>.tsx`) | `script` | Probed, as above. | `native` |
| Generated MCP server (any transport) | `mcp` | `none` on both, `color: 'none'`, `sharesTarget: false` — stdout is the protocol wire and stderr the host's log. Never probed, whatever the descriptors are. | `derived` |
| Event route (shared runtime or standalone hook process) | `hook` | `none` on both — stdout is the host's hook envelope. Never probed. | `derived` |
Expand Down Expand Up @@ -787,7 +792,8 @@ through Node's top-level failure path (stack to stderr, exit code 1).
(#511), probed once before `main` runs by the dependency-free
`agent-bundle/terminal-capability` module the envelope aliases in — plain
scripts and bins load no Effect runtime and no `@agent-bundle/runtime` for it.
Its `hostSurface` is `cli` for a package bin (`dist/bin/<name>.js`) and
Its `hostSurface` is `cli` for a package bin (`dist/bin/<name>.js` for an authored bin,
`dist/bin/<name>.mjs` for the manifest-selected routed CLI) and
`script` for an artifact script (`scripts/<name>.mjs`); a module shipped on
both surfaces sees the surface it was launched from. A `main` declared with
one parameter keeps working — the second argument is simply unread.
Expand Down Expand Up @@ -825,8 +831,8 @@ The compiler statically projects `inputSchema` onto argv (the bounded grammar
and every policy rule are documented in
[Diagnostics](diagnostics.md#route-graph-state-layout-and-provider-conventions-ab4800ab4842-ab4940ab4942)), generates nested
help (`--help` at every level, `--version` at the root), and emits
`dist/bin/<plugin-name>.js` with the shebang and executable bit through the
same Rslib synthesis as every other bin. At run time the shell resolves the
`bin/<plugin-name>.mjs` in the composite artifact with the shebang and
executable bit. At run time the shell resolves the
command path, parses and coerces argv, validates through the module's own
zod schemas, executes the default function inside the typed Agent request
context (`invocation.kind: 'cli'`), writes one canonical JSON line to
Expand Down Expand Up @@ -874,7 +880,7 @@ is 2 in every mode.
A `.tsx` command route swaps the default function for an async default
Server Component with the same `{ input, signal }` props and renders through
the runtime dispatcher's public `stream()` against a sibling
`dist/bin/<plugin-name>-flight.mjs` react-server worker (one warm worker per
`bin/<plugin-name>-flight.mjs` react-server worker (one warm worker per
invocation; raw Flight bytes never reach the terminal). The four output
modes: an interactive TTY updates progress in place and prints the final
document as Markdown; piped output emits exactly one final Markdown document
Expand All @@ -886,11 +892,10 @@ machine output owns stdout. Rendered scripts
(`src/scripts/<name>.tsx`) share the same shell and output contract with
`{ argv, signal }` component props and status-derived exit codes.

#### The routed CLI inside the plugin root
#### The routed CLI inside every distribution

The package bin only reaches users who install the npm package. Hooks,
skills, and script routes ship with the **plugin root**, so the build also
emits the same compiled command graph into that root whenever a selected host's
Hooks, skills, script routes, and npm consumers all use the **plugin root**,
so the build emits one compiled command graph there whenever a selected host's
adapter publishes the `cli` capability — all built-in hosts do (`claude`,
`codex`, `cursor`, `portable`) — because the artifact root is
already a plain directory Node executes `mcp/` and `scripts/` files from.
Expand All @@ -906,15 +911,16 @@ artifact/
mcp/…
```

The artifact bin is a self-contained ESM module with no shebang or
executable bit — invoke it as `node <plugin-root>/bin/<plugin-name>.mjs
<args>`, exactly like `scripts/*.mjs`. Help, argv parsing, output modes,
exit codes, and signals are identical to the package bin. One deliberate
difference: workspace-durable state without a host-supplied
`AGENT_BUNDLE_PLUGIN_ROOT` anchors on the **artifact root** (the parent of
`bin/`, the same fallback the generated MCP worker beside it uses) rather
than `$PWD/.agent-bundle/state`, so a co-installed CLI and server observe
one store. The npm package bin keeps its `cwd` fallback.
The artifact bin is a self-contained executable ESM module with a Node
shebang. The npm root copies it unchanged and points `package.json` `bin`
directly at it, so help, argv parsing, output modes, exit codes, signals, and
the full command set are byte-for-byte the same. Both forms resolve plugin
code from the root above `bin/`. Workspace-durable state without a host-supplied
`AGENT_BUNDLE_STATE_ROOT` uses `stateAnchor: 'user-data'`, deriving
`~/.agent-bundle/state/<plugin>-<digest>` (or the `XDG_STATE_HOME` equivalent)
from the artifact code root. The generated MCP worker beside it makes the
same derivation, so a co-installed CLI and server observe one store without
writing beneath a read-only artifact.

Reaching the bin from the other surfaces:

Expand Down Expand Up @@ -945,7 +951,8 @@ capability without that layout, or with a `cliBin` layout naming another
directory or omitting `.mjs`, is rejected at registration. A target without the
capability omits the bin and reports `AB4765`; a host-emitted file at the
same path (a Claude `claude.bin` directory shipping `<plugin-name>.mjs`) is
`AB4766`. The package build's `dist/bin/<plugin-name>.js` is unchanged.
`AB4766`. The npm root copies this manifest-declared executable; it does not
compile another routed CLI.

#### Project generated MCP tools into the CLI (power tier)

Expand Down Expand Up @@ -1312,8 +1319,12 @@ lowers every host-pack surface and package-build entry. The framework-owned
Rspack kept external, and the service reads that evidence before trusting an
asset. `AB6005` rejects anything Rspack kept external except a Node built-in,
`pnpapi`, or an emitted sibling of the same artifact, whatever spelling the
bundle uses. The emitted-module walk remains behind that check as defense in
depth. A `require`,
bundle uses. `agent-bundle build` writes that evidence as
`agent-bundle.compile-evidence.json` at the artifact root (listed in
`agent-bundle.manifest.json` as a `generated` file); `agent-bundle validate
--artifact` re-checks a listed record against the file table without reading
JavaScript (`AB6039`). The emitted-module walk remains behind that check as
defense in depth. A `require`,
`createRequire(…)(…)`, or `import.meta.resolve(…)` call the compiler does not
resolve is not a module dependency; content the compiler did not compile is
opaque and must declare what it needs. Run-time path references are kept the
Expand Down Expand Up @@ -1484,8 +1495,9 @@ treats a missing file as the normal case and an unreadable one as skipped.
The dotenv grammar has no `${VAR}` interpolation. Under `mcp run` the plugin
root is the project root, so the shell's pass is a no-op; `--env-file` and
`--no-env` are handed down as `AGENT_BUNDLE_ENV_FILE` so the shell follows the
operator's choice. The npm package bin reads no pack file. Doctor reports the
presence and variable count of each file (`AB7331`).
operator's choice. Because the npm bin is this same artifact executable, it
reads the same pack files. Doctor reports the presence and variable count of
each file (`AB7331`).

Two details keep the installed order equal to the `mcp run` table above:

Expand Down Expand Up @@ -1653,7 +1665,11 @@ input, and a refresh rebinds that retained result. `<plugin> web` keeps the
installed artifact immutable: framework-owned per-server web state
(`${PLUGIN_DATA}` in declared env) lives under the user's home
(`~/.agent-bundle/web-data/<plugin>-<digest>/<server>`), never inside the
plugin root, so a read-only install still launches.
plugin root. The spawned server's SQLite state kernel, notice ledger, and
lineage journal likewise use `stateAnchor: 'user-data'` and live under
`~/.agent-bundle/state/<plugin>-<digest>` (or the `XDG_STATE_HOME` equivalent)
unless `AGENT_BUNDLE_STATE_ROOT` overrides it, so a read-only install still
launches.

## `agent-bundle/app` — the App-side bridge client

Expand Down
Loading