You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Expose the resolved plugin root / durable-state anchor on the request context (and to providers) instead of making plugins re-derive AGENT_BUNDLE_PLUGIN_ROOT #468
movie-library (feat/react-app-shell @ d6a247b) keeps an 80-line resolver, scripts/state-root.ts, whose only job is to answer "where is this plugin installed / where does its durable state live" — the same question every generated agent-bundle worker already answers privately:
scripts/state-root.ts:55-61 re-reads AGENT_BUNDLE_PLUGIN_ROOT, guards against a literal unexpanded ${...} token, then falls back to source-layout / artifact-layout heuristics / process.cwd().
agent-bundle.config.ts:23-26 documents the arrangement: "every adapter injects the well-known AGENT_BUNDLE_PLUGIN_ROOT anchor into generated stdio entries, and scripts/state-root.ts resolves it".
PORT-NOTES.md § Durable state: "The kernel anchors on AGENT_BUNDLE_PLUGIN_ROOT … and falls back to the artifact root, which is different from the plugin's own .runtime root (scripts/state-root.ts …)". Two anchors for one install, because the plugin cannot read the framework's.
PR build(deps): bump @mantine/core from 9.5.1 to 9.5.2 #8 framework-mode audit, finding 7: "scripts/run-stdio.sh + scripts/state-root.ts hand-roll launcher/state-root resolution that overlaps the framework's stdio entry, output.distPath, and defineState roots" — category (b), left in place because nothing public replaces it.
Any plugin with pre-existing on-disk state that is not a defineState store (caches, token files, a Python sidecar's data dir) has to do the same.
Root cause in agent-bundle (main @ 284141958)
The request context has no plugin-root / durable-anchor axis; the anchor is computed inside each generated shell and never surfaced:
packages/rsc-runtime/src/agent-request.ts:199-220 — AgentRequestContext exposes invocation, host, session, actor, workspace, lineage, progress, signal, providers, state. workspace (:83-85) is { root: string } and is populated with the caller's cwd (packages/agent-bundle/src/build/entry-shell.ts:331, :528 — workspace: available({ root: cwd }, 'derived')), i.e. the user's project, not the plugin install.
packages/agent-bundle/src/build/entry-shell.ts:188, :712, :987 — three separate const durableAnchor = process.env.AGENT_BUNDLE_PLUGIN_ROOT ?? <fallback> computations (state kernel, notice ledger, lineage registry), each private to the emitted source.
packages/agent-bundle/src/core/types.ts:792 / adapters/types.ts:117-122 — pluginRootEnvAnchor is a public env contract; there is no public runtime accessor, and agent-bundle/meta (packages/agent-bundle/src/meta.ts) carries only name/version.
None of the framework's own anchor computations apply movie-library's ${ guard (state-root.ts:57-60): a host that passes manifest env through literally would give the kernel join('${CLAUDE_PLUGIN_ROOT}', 'state') under cwd.
Workaround in movie-library
scripts/state-root.ts (resolvePluginRoot / resolveStateRoot, five-tier fallback), consumed by the provider, the layouts' footer, the event route, and every legacy domain module.
Proposed fix
Expose the resolved anchor once and let routes, layouts, providers, and event routes read it:
interfaceAgentRequestContext{// newreadonlyplugin: Observed<{readonlyroot: string;// resolved AGENT_BUNDLE_PLUGIN_ROOT, or the fallback the shell usedreadonlystateRoot: string;// join(root, 'state') — where defineState / notices / lineage live}>;}
source: 'native' when AGENT_BUNDLE_PLUGIN_ROOT was set and expanded, 'derived' for the artifact/cwd fallback; unavailable with a typed reason for an unexpanded ${...} token (and have the kernel treat that token as unset instead of creating a literal ${...} directory).
Compute it in one place in the emitted shells (state kernel, notice ledger, lineage registry, and the request context all read the same value) instead of three inline expressions.
Document in docs/entry-conventions.md (state section) and the runtime reference.
Acceptance
(await agent()).plugin.root / .stateRoot are available in a generated MCP tool route, a rendered CLI command, a hook-backed event route, and the route-unit harness (fixture seam for tests), on portable/claude/codex/cursor artifacts and under mcp run.
A provider receives the same field.
entry-shell.test.ts proves the kernel, notice ledger, lineage registry, and plugin.stateRoot agree on one path, and that an unexpanded ${...} anchor is reported unavailable rather than joined into a path.
movie-library can delete scripts/state-root.ts's anchor tier and read plugin.root in src/providers/tracker-session.ts.
Symptom in movie-library
movie-library (
feat/react-app-shell@d6a247b) keeps an 80-line resolver,scripts/state-root.ts, whose only job is to answer "where is this plugin installed / where does its durable state live" — the same question every generated agent-bundle worker already answers privately:scripts/state-root.ts:55-61re-readsAGENT_BUNDLE_PLUGIN_ROOT, guards against a literal unexpanded${...}token, then falls back to source-layout / artifact-layout heuristics /process.cwd().src/providers/tracker-session.ts:78— the request-context provider exposesstateRoot: resolveStateRoot(env)so layouts, thesession/startevent route (src/events/session/start.tsx:47) and the CLI can print where state lands.agent-bundle.config.ts:23-26documents the arrangement: "every adapter injects the well-known AGENT_BUNDLE_PLUGIN_ROOT anchor into generated stdio entries, and scripts/state-root.ts resolves it".AGENT_BUNDLE_PLUGIN_ROOT… and falls back to the artifact root, which is different from the plugin's own.runtimeroot (scripts/state-root.ts…)". Two anchors for one install, because the plugin cannot read the framework's.scripts/run-stdio.sh+scripts/state-root.tshand-roll launcher/state-root resolution that overlaps the framework's stdio entry,output.distPath, anddefineStateroots" — category (b), left in place because nothing public replaces it.Any plugin with pre-existing on-disk state that is not a
defineStatestore (caches, token files, a Python sidecar's data dir) has to do the same.Root cause in agent-bundle (
main@284141958)The request context has no plugin-root / durable-anchor axis; the anchor is computed inside each generated shell and never surfaced:
packages/rsc-runtime/src/agent-request.ts:199-220—AgentRequestContextexposesinvocation,host,session,actor,workspace,lineage,progress,signal,providers,state.workspace(:83-85) is{ root: string }and is populated with the caller's cwd (packages/agent-bundle/src/build/entry-shell.ts:331,:528—workspace: available({ root: cwd }, 'derived')), i.e. the user's project, not the plugin install.packages/agent-bundle/src/build/entry-shell.ts:188,:712,:987— three separateconst durableAnchor = process.env.AGENT_BUNDLE_PLUGIN_ROOT ?? <fallback>computations (state kernel, notice ledger, lineage registry), each private to the emitted source.packages/agent-bundle/src/routes/public.ts:96-102—AgentProviderContext = { invocation, signal }; a provider cannot read the anchor either (Pass request identity, lineage, and read-only state/notices handles to context providers #459 widens providers to identity/lineage/state, but does not add a plugin-root/anchor field).packages/agent-bundle/src/core/types.ts:792/adapters/types.ts:117-122—pluginRootEnvAnchoris a public env contract; there is no public runtime accessor, andagent-bundle/meta(packages/agent-bundle/src/meta.ts) carries only name/version.${guard (state-root.ts:57-60): a host that passes manifest env through literally would give the kerneljoin('${CLAUDE_PLUGIN_ROOT}', 'state')under cwd.Workaround in movie-library
scripts/state-root.ts(resolvePluginRoot/resolveStateRoot, five-tier fallback), consumed by the provider, the layouts' footer, the event route, and every legacy domain module.Proposed fix
Expose the resolved anchor once and let routes, layouts, providers, and event routes read it:
source: 'native'whenAGENT_BUNDLE_PLUGIN_ROOTwas set and expanded,'derived'for the artifact/cwd fallback;unavailablewith a typed reason for an unexpanded${...}token (and have the kernel treat that token as unset instead of creating a literal${...}directory).AgentProviderContext(fits naturally alongside Pass request identity, lineage, and read-only state/notices handles to context providers #459).docs/entry-conventions.md(state section) and the runtime reference.Acceptance
(await agent()).plugin.root/.stateRootareavailablein a generated MCP tool route, a rendered CLI command, a hook-backed event route, and the route-unit harness (fixture seam for tests), on portable/claude/codex/cursor artifacts and undermcp run.entry-shell.test.tsproves the kernel, notice ledger, lineage registry, andplugin.stateRootagree on one path, and that an unexpanded${...}anchor is reportedunavailablerather than joined into a path.scripts/state-root.ts's anchor tier and readplugin.rootinsrc/providers/tracker-session.ts.