feat(runtime): expose the resolved plugin root as request.plugin and hand it to providers (#468) - #532
Conversation
🦋 Changeset detectedLatest commit: 8681171 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 158fcb2774
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| */ | ||
| export const resolvePluginRoot = (options: ResolvePluginRootOptions): ResolvedPluginRoot => { | ||
| const env = options.env ?? process.env; | ||
| const declared = env[PLUGIN_ROOT_ENV_ANCHOR]?.trim() ?? ''; |
There was a problem hiding this comment.
Preserve whitespace in the configured plugin-root path
When a plugin is installed in a valid path with leading or trailing whitespace, such as /opt/curator , trimming the environment value changes the filesystem location before both root and stateRoot are derived. Generated state then opens a different directory and routes/providers receive an incorrect install root. Preserve the original value for resolve() and use a trimmed copy only to determine whether the variable is blank.
Useful? React with 👍 / 👎.
87a82eb to
89d6174
Compare
…hand it to providers (#468)
…trim only for the blank check
89d6174 to
faa6599
Compare
…sing over request.terminal (#511)
faa6599 to
8681171
Compare
Fixes #468.
Why
The request context had no plugin-root axis:
workspaceis the caller's cwd, and the durable-state anchor was computed inline — three times inentry-shell.ts(state kernel, notice ledger, lineage journal) and once more in the standalone hook wrapper — so a plugin with its own on-disk files (movie-library'sscripts/state-root.ts,src/providers/tracker-session.ts) re-derivedAGENT_BUNDLE_PLUGIN_ROOTwith its own fallback heuristics and ended up with two anchors for one install.Design
resolvePluginRoot({ fallback })in@agent-bundle/runtime(plugin-root.ts):AGENT_BUNDLE_PLUGIN_ROOTwhen set to a non-empty, expanded value (source: 'native'); otherwise the shell's fallback ('derived') — the artifact root (parent ofmcp/,bin/,hooks/) for artifact shells,$PWD/.agent-bundlefor the npm bin. A value still carrying a${…}token is treated as unset and reported once on stderr, never joined into a path. Both roots are made absolute;stateRootis<root>/state. Returns{ root, stateRoot, source, identity }whereidentityis theObservedaxis.AgentRequestContext.plugin: Observed<AgentPluginIdentity>({ root, stateRoot }),AgentRequestInit.plugin;unavailable('not-provided')outside a generated scope.AGENT_REQUEST_STORE_VERSIONbumped to 5 (same rule as whenlineageandterminaljoined; see landing notes).const pluginRoot = resolvePluginRoot({ fallback: … })once; the SQLite kernel (generatedStateOwner), the notice-delivery store, the lineage journal, and the standalone hook'sretireLineageall openpluginRoot.stateRoot; everyrunAgentRequestthe module opens publishespluginRoot.identity. The MCP entry hands it tocreateGeneratedRouteMcpServer({ pluginRoot });requestIdentitypublishes it per tool call / resource read / prompt get and the shared event runtime per event; the Flight worker receives it on the render message (message.plugin ?? pluginRoot.identity).docs/entry-conventions.mdandentry-shell.test.tspin that kernel, ledger, journal, andplugin.stateRootare one value.AgentProviderContext.plugin(structuralAgentProviderObservedPluginRoot, soagent-bundleroot declarations stay runtime-import-free), passed by every generated provider loop and byexecuteProvidersin the harness. Pass request identity, lineage, and read-only state/notices handles to context providers #459 is untouched: the anchor reaches providers the wayinvocationalready does, nothing else.renderRoute,invokeCli,runScript, andopenInMemoryMcpServerresolve the axis with the same runtime function (fallback<project root>/.agent-bundle; module targets fall back to cwd) and acceptcontext.pluginlike every other axis. Harness state still mounts in a temp dir; the doc comment says so.dev/**is untouched.pluginRootEnvAnchor(the env contract) is unchanged;PLUGIN_ROOT_ENV_ANCHORis the runtime's spelling of the same name.Tests
rsc-runtime/tests/plugin-root.test.ts— native / relative / unset / blank / unexpanded-token resolution, the warning text, defaultprocess.env, and the request axis (not-providedby default, frozen identity when supplied).tests/route-unit/render-route.test.ts— the newplugin-rootfixture tool underAGENT_BUNDLE_PLUGIN_ROOTset (native), unset (derived<project>/.agent-bundle), and an unexpanded token (derived), plus thecontext.pluginseam.tests/projection/mcp-in-memory.test.ts— the real generated server publishes the anchor it resolved when it opened; a context override is forwarded.tests/entry-shell.test.ts— every generated shell declares onepluginRoot, mounts state / notices / lineage onpluginRoot.stateRoot, noAGENT_BUNDLE_PLUGIN_ROOTliteral remains, providers receiveplugin; worker source hash pin updated.mcp-in-memory,cli-dispatch,packed-stdio, manifest, contract matrix).Docs
website/docs/{en,zh}/guide/authoring/mcp.mdx(plugin-root anchor section: thepluginaxis, sources, providers, harness),website/docs/{en,zh}/reference/runtime-environment.mdx,docs/entry-conventions.md(state section, provider contract),docs/diagnostics.md(AB4940recovery text).pnpm docs:site:buildgreen.Verification
pnpm typecheck,pnpm lint,pnpm test:unit(3251 passed),pnpm test:route-unit,pnpm test:projection,pnpm docs:site:build.Review status
158fcb2(PR opened) and left one P2 thread onrsc-runtime/src/plugin-root.ts: trimming the configuredAGENT_BUNDLE_PLUGIN_ROOTmoved the anchor for a path with surrounding whitespace. Fixed in87a82ebbc: only the blank check reads a trimmed copy; the path is resolved exactly as written (pinned by a newplugin-root.test.tscase). No PR comments are posted from this side; the thread is answered here.87a82ebbcis not yet re-reviewed by the connector.Landing notes (rebase over
mainafter #534)request.terminal, Expose a terminal/IO capability to routes and rendered scripts (TTY-ness, color, columns) instead of each plugin re-detecting it #511) landed first and also bumpedAGENT_REQUEST_STORE_VERSIONto 4. This branch now bumps it to 5 (terminal= 4,plugin= 5);agent-request.test.tspins thatplugin.tsre-exports the same constant.test/cli.ts,test/render.ts, and the generated shells publish both axes side by side (pluginbesideterminal);mcp-server-runtime.tskeeps the feat(notices): address a notice to a lineage conversation or root (#458) #539 subscription comment with the 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 #468requestIdentityarity. Worker source hash re-pinned inentry-shell.test.ts.pnpm build,pnpm typecheck,pnpm lint, unit (entry-shell,plugin-root,agent-request), route-unitrender-route, projectionmcp-in-memory+cli-dispatch,pnpm docs:site:build.