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
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).
14 changes: 12 additions & 2 deletions docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ even when no error diagnostic was reported.
| `AB7010`–`AB7015` | npm prepack inventory, artifact freshness, package bin targets, release-version agreement, and installed-dependency hygiene (`AB7014`: a dependency no packed file references; `AB7015`: a git, remote-tarball, path, or unrewritten workspace-protocol dependency specifier). |
| `AB7200`–`AB7202`, `AB7210`–`AB7211` | Development rebuilds and live host surfaces: rebuild admission and phase failures, development host install sync, and the dev-epoch contract gate (see below). |
| `AB7xxx` | Project preparation and development rebuilds (`AB7100`–`AB7102`: a development rebuild's compilation, publication, and cleanup; `AB7103`: the development package build; see below). |
| `AB7300`–`AB7331` | Read-only install Doctor: host probes, installed inventory, bundle comparison and registration proof, runtime endpoint health and identity, durable-state inventory, static bytes-at-rest validation, foreign-install detection (`AB7321`; see below), Cursor plugin hook registration / marketplace staging (`AB7322`–`AB7324`; see below), host load refusal (`AB7325`; see below), the Cursor Agent Plugins launch proof (`AB7326`; see below), a disabled Claude install (`AB7327`; see below), lifecycle receipts and activation states (`AB7328`–`AB7330`; see below), and the operator `.env` layer of an installed pack (`AB7331`; see below). `AB7311` and `AB7325` are also emitted by `build` and `validate --artifact` from the Claude load check (see "Claude Code host validation"). |
| `AB7300`–`AB7332` | Read-only install Doctor: host probes, installed inventory, bundle comparison and registration proof, runtime endpoint health and identity, durable-state inventory, static bytes-at-rest validation, foreign-install detection (`AB7321`; see below), Cursor plugin hook registration / marketplace staging (`AB7322`–`AB7324`; see below), host load refusal (`AB7325`; see below), the Cursor Agent Plugins launch proof (`AB7326`; see below), a disabled Claude install (`AB7327`; see below), lifecycle receipts and activation states (`AB7328`–`AB7330`; see below), the operator `.env` layer of an installed pack (`AB7331`; see below), and retained pre-#640 state (`AB7332`; see below). `AB7311` and `AB7325` are also emitted by `build` and `validate --artifact` from the Claude load check (see "Claude Code host validation"). |
| `AB8200`–`AB8209` | Workbench development runtime routes (`/api/runtime/**`): `AB8200` development runtime provider configuration, load, or lifecycle failure, `AB8201` runtime/session/run not available, `AB8202` invalid route path, `AB8203` invalid request shape, `AB8204` stale runtime generation or MCP session revision (409), `AB8205` runtime request could not be completed, `AB8206` Workbench runtime client failure, `AB8207` Agent Document decoding needs the optional `@agent-bundle/runtime` peer (503), `AB8208` stored Flight could not be decoded as an Agent Document (409), `AB8209` decoded Agent Document over the 16 MiB budget (413) or an invalid document response. |
| `AB8210`–`AB8214` | Workbench semantic lifecycle replay routes (`/api/lifecycles`, `/api/lifecycles/replays`): `AB8210` invalid path, `AB8211` malformed replay request or native envelope (400, carries the shared validator message), `AB8212` replay unavailable or could not be completed, `AB8213` stale manifest binding (409; the page repairs it with refresh → explicit re-run), `AB8214` replay over the 16 MiB budget (413). |
| `AB8215`–`AB8218` | Workbench read-only host discovery route (`/api/discovery`): `AB8215` invalid path, `AB8216` query string or non-`GET` method (400/405), `AB8217` report over the 16 MiB response limit (413), `AB8218` discovery not available (503). |
Expand Down Expand Up @@ -1334,7 +1334,7 @@ SQLite lock or shared-memory files.

| Code | Severity | Trigger |
| --- | --- | --- |
| `AB7316` | warning | An installed bundle's `state/` directory or one of its `*.sqlite`, `-wal`, or `-shm` files cannot be read with filesystem metadata operations. Repair permissions and rerun Doctor; Doctor never repairs state. |
| `AB7316` | warning | An installed bundle's effective or legacy state directory is not writable, or the directory or one of its `*.sqlite`, `-wal`, or `-shm` files cannot be read with filesystem metadata operations. Repair permissions and rerun Doctor; Doctor never repairs state. |

## Read-only Doctor operator env inventory (`AB7331`)

Expand All @@ -1349,6 +1349,16 @@ diagnostic.
| --- | --- | --- |
| `AB7331` | info / warning | Info: an installed copy (or the `--from` bundle) carries `.env` or `.env.local` at its plugin root; the message names the file and its variable count. Warning: the file exists but cannot be read, so the pack's shells skip it at launch — repair its permissions and rerun Doctor. |

## Read-only Doctor legacy state (`AB7332`)

Doctor resolves each installed copy's effective framework state root from its
canonical code root and declared environment. It reports that root's source,
existence, and writability separately from the pre-#640 in-tree location.

| Code | Severity | Trigger |
| --- | --- | --- |
| `AB7332` | info | `<plugin root>/state` still exists while the installed artifact resolves framework state elsewhere. Move any state that must be retained, or use `uninstall --purge-data --confirm-purge` to remove both roots. |

## Read-only runtime identity introspection (`AB7317`–`AB7318`)

| Code | Severity | Trigger |
Expand Down
18 changes: 10 additions & 8 deletions packages/agent-bundle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ manifests at files inside those payloads without compiling them. Payload files c
| `agent-bundle build` | Build a validated artifact from source, plus the declared `dist/` package build. |
| `agent-bundle prepack` | Run the release build, dry-run npm packing without scripts, and verify packaged outputs, artifact hashes, bins, and versions (`--output` and `--json` supported). |
| `agent-bundle install <host>` | Install a built bundle into Claude, Codex, or Cursor (`--from`, `--scope`, `--replace`/`--force`, `--mode local\|marketplace` for Cursor, and `--json` supported). Same-version content drift of an agent-bundle-managed install is replaced automatically; identical reruns are a no-op. |
| `agent-bundle uninstall <host>` | Remove a receipt-owned install and nothing else: the receipt's files and directories, the host registrations it recorded (`claude plugin uninstall --keep-data` + `marketplace remove`, `codex plugin remove` + `marketplace remove`, the Cursor local directory or staged marketplace). `--plan` prints the exact paths without changing anything; durable `state/` is kept unless `--purge-data --confirm-purge`; a missing receipt or content mismatch is refused unless `--force`; a rerun is `not-installed`. |
| `agent-bundle doctor` | Read-only host inspection: host probes, installed inventory, store receipts cross-checked against the host, and, with `--from`, the installed copy compared against the built artifact by version and content hash (`current`, `stale`, `version-mismatch`, `foreign`, `not-installed`) plus the lifecycle stage (placed → registered → enabled → active, unobservable stages typed `unavailable`). |
| `agent-bundle uninstall <host>` | Remove a receipt-owned install and nothing else: the receipt's files and directories, the host registrations it recorded (`claude plugin uninstall --keep-data` + `marketplace remove`, `codex plugin remove` + `marketplace remove`, the Cursor local directory or staged marketplace). `--plan` prints the exact paths without changing anything; the effective framework state root, web-data, and legacy `state/` are kept unless `--purge-data --confirm-purge`; a missing receipt or content mismatch is refused unless `--force`; a rerun is `not-installed`. |
| `agent-bundle doctor` | Read-only host inspection: host probes, installed inventory, effective and legacy state roots with existence and writability, store receipts cross-checked against the host, and, with `--from`, the installed copy compared against the built artifact by version and content hash (`current`, `stale`, `version-mismatch`, `foreign`, `not-installed`) plus the lifecycle stage (placed → registered → enabled → active, unobservable stages typed `unavailable`). |
| `agent-bundle validate` | Validate project source, or an artifact with `--artifact`. |
| `agent-bundle inspect` | Inspect the normalized model and each selected host projection's plan from source, with per-host component accounting: which skills, commands, rules, hooks, MCP surfaces, and scripts each host emits and, for every omission, whether the author excluded it or the host's pinned capability judgment (`degraded`/`unavailable`/`prohibited`, with reason) ruled it out. |
| `agent-bundle inspect --bundler` | Dump the synthesized Rslib/Rsbuild configs (post-`tools`-hatch merge) for every generated output. |
Expand Down Expand Up @@ -332,12 +332,14 @@ receipt and remove exactly what it owns:

Durable runtime state (`state/`: state kernel, notices journal; for a Cursor
copy of an Agent Plugins pack, also the `PLUGIN_DATA` directory the receipt
records) is kept by default; `--purge-data --confirm-purge` removes it
(`AB7008` without the confirmation). The typed `data.outcome` is honest per host: Cursor `kept` /
`purged` / `absent`; Claude `retained-by-host` (Claude 2.1.257 orphans the
cached copy for its ~14-day grace period; a purge also removes `state/` and
`plugins/data/<id>/`); Codex `removed-by-host` / `unavailable` (codex-cli
0.147.0 deletes the cached tree on `plugin remove` and has no keep-data option).
records), effective framework state, and web-data are kept by default;
`--purge-data --confirm-purge` removes them (`AB7008` without the confirmation).
The typed `data.outcome` is honest per host: Cursor `kept` / `purged` / `absent`;
Claude `retained-by-host` (Claude 2.1.257 orphans the cached copy for its ~14-day
grace period; a purge also removes external framework state, web-data, `state/`,
and `plugins/data/<id>/`); Codex reports external state as `kept` / `purged`,
while in-tree `state/` is removed by the host and cannot be kept (codex-cli
0.147.0 has no keep-data option).
`--plan` reports the same exact paths and host verbs without opening a writer.
A missing receipt (`AB7009`) or an owned-content, version, or `HEAD` mismatch
(`AB7007`) is refused unless `--force`; a receipt or manifest naming another
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"keepData": {
"evidence": [
"2026-09-03: `codex plugin remove` deletes the cached plugin tree, `state/` included, and codex-cli 0.147.0 documents no keep-data option; the uninstaller reports data outcome `unavailable` for --keep-data and `removed-by-host` for --purge-data instead of pretending to preserve state."
"2026-09-05: `codex plugin remove` deletes the cached plugin tree, `state/` included, and codex-cli 0.147.0 documents no keep-data option. Agent Bundle can preserve external framework state and web-data, but cannot preserve in-tree state through a Codex uninstall."
],
"reason": "Codex owns the cached tree and removes it wholesale; durable state cannot be preserved through a Codex uninstall.",
"state": "unavailable"
Expand Down
12 changes: 12 additions & 0 deletions packages/agent-bundle/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,18 @@ const humanDoctor = (result: DoctorReport): string => {
host.bundle?.durableState,
].filter((report): report is DoctorDurableStateReport => report !== undefined);
const uniqueReports = [...new Map(reports.map((report) => [report.directory, report])).values()];
for (const report of uniqueReports) {
out.push(
` state root: ${report.directory} (${report.exists ? 'exists' : 'missing'}, ` +
`${report.writable ? 'writable' : 'not writable'}, ${report.stateSource})\n`,
);
}
const legacyReports = host.inventory.findings
.map((finding) => finding.legacyDurableState)
.filter((report): report is DoctorDurableStateReport => report !== undefined);
for (const report of [...new Map(legacyReports.map((entry) => [entry.directory, entry])).values()]) {
out.push(` legacy state: ${report.directory} (exists, ${report.writable ? 'writable' : 'not writable'})\n`);
}
if (uniqueReports.length > 0) {
const stores = uniqueReports.reduce((total, report) => total + report.summary.stores, 0);
const bytes = uniqueReports.reduce((total, report) => total + report.summary.bytes, 0);
Expand Down
Loading
Loading