fix(cli): mount request context providers for plain routed CLI commands (#313 audit) - #366
Merged
Merged
Conversation
…ds (#313) Plain `.ts` routed CLI commands ran `runAgentRequest` with no providers, so `(await agent()).providers` was empty on that surface while MCP, event, rendered CLI, and rendered script scopes mounted the conventional registry. The generated executable now runs the same ordered, fail-closed provider loop before every plain request, with the framework-owned `processLifetime` value. The rendered-session bridge also never posted `invocation` to its worker, so providers behind rendered CLI commands and rendered scripts observed `invocation: undefined`; the bridge forwards it now, and the routed-CLI executable proof covers provider values on plain CLI, rendered CLI, and rendered script surfaces. Also aligns the binary-gated Codex install proof with the `interface.logo` field the Codex adapter emits since #364.
🦋 Changeset detectedLatest commit: ba3fc6e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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: |
Owner
Author
|
Full |
This was referenced Sep 3, 2026
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…ness The public harness (renderRoute, renderRouteEvents, invokeCli, in-memory MCP) now discovers and executes src/providers/* for every manifest-backed request scope exactly like the generated entries: same deterministic key order, same surface-specific invocation, same fail-closed messages, seeded processLifetime. context.providers still wins when passed. The execution contract the codegen emits and the harness runs now lives in one module and is pinned together. Refs #313, #366.
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…ness The public harness (renderRoute, renderRouteEvents, invokeCli, in-memory MCP) now discovers and executes src/providers/* for every manifest-backed request scope exactly like the generated entries: same deterministic key order, same surface-specific invocation, same fail-closed messages, seeded processLifetime. context.providers still wins when passed. The execution contract the codegen emits and the harness runs now lives in one module and is pinned together. Refs #313, #366.
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…ness The public harness (renderRoute, renderRouteEvents, invokeCli, in-memory MCP) now discovers and executes src/providers/* for every manifest-backed request scope exactly like the generated entries: same deterministic key order, same surface-specific invocation, same fail-closed messages, seeded processLifetime. context.providers still wins when passed. The execution contract the codegen emits and the harness runs now lives in one module and is pinned together. Refs #313, #366.
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…der mounting and deterministic examples-real edits (#399) * feat(test): mount conventional providers in the agent-bundle/test harness The public harness (renderRoute, renderRouteEvents, invokeCli, in-memory MCP) now discovers and executes src/providers/* for every manifest-backed request scope exactly like the generated entries: same deterministic key order, same surface-specific invocation, same fail-closed messages, seeded processLifetime. context.providers still wins when passed. The execution contract the codegen emits and the harness runs now lives in one module and is pinned together. Refs #313, #366. * test(workbench): wait on the watcher rebuild instead of retrying examples-real edits Each source edit paired a file write with an immediate manual rebuild, so the watcher's own rebuild of the same write raced it for a second epoch whose timing depended on load; { retry: 2 } absorbed the fallout. Edits now go through replaceWatchedSourceAndAwaitRebuild: one atomic replacement, then a wait on the coordinator's published build attempt, so one edit is exactly one build and the retries are gone. Refs #122, #200, #329. * test(route-unit): reconcile the #371 provider seam pin with harness auto-mounting Explicit context.providers still mounts verbatim; a module rendered directly now observes the framework-owned processLifetime like a generated scope without providers, and manifest routes execute conventional providers. * test(workbench): replace the logs-real source edit atomically A truncating writeFile can split into two watcher invalidations under load, logging "Project source changed." twice and tripping the strict locator; the shared atomic replacement makes one edit one invalidation. * fix(test): derive executable surface for harness invocations and bump registry version renderRoute now hands providers and the request scope the surface the generated entries record — a routed CLI command's space-joined command path and a script's path-derived name — instead of the route id, so providers that branch on `command`/`name` behave identically in the harness and the artifact. The test registry version moves to 4 because the layout gained `providerLoaders`. The audiobook-curator degraded-catalog test opts out of the auto-mounted library provider explicitly. * fix(test): pin the tooling tool in the packed projection and ignore example artifacts The packed stdio projection lists the route-harness tools, which now include `tooling`. Every example's build writes `examples/<name>/artifact/`; the previous commit accidentally tracked three of them, so ignore the whole family and untrack the accidental copies. * fix(build): mount the compiled event route id as operationId in the Flight worker The generated worker resolved event routes by their hook identity and mounted that identity (`hook:event-route:tool-after`) as `invocation.operationId`, while the hook shell's request scope, the lifecycle replay, the test manifest, and `renderRoute` all use the compiled route id (`event:tool/after`). The worker record now carries the compiled id, so a route reading `invocation.operationId` sees one value on every surface; pinned by the warm-runtime integration test and the worker source digest. * fix(test): scope the harness process lifetime to each simulated executable A module-level lifetime made unrelated invokeCli calls, renders, and MCP sessions look like one warm process, so a provider branching on hits or instanceId could pass in the harness and fail in the artifact. The lifetime now lives on the logical executable exactly as the artifact scopes it: fresh per CLI invocation and per route-unit render, shared across the requests of one open in-memory MCP server, and fresh again for the open-call-close helper. * fix(test): snapshot the process hit count before awaiting provider loaders Concurrent requests on one in-memory MCP server could each increment the shared lifetime before the first request reached executeProviders, so every request observed the final count. Capture each request's hit right after the increment, as the generated worker does, and hand the snapshot to the shared execution helper. * fix(build,test): snapshot the process hit synchronously before state bindings Generated stateful scopes incremented processLifetime.hits, awaited state bindings, then snapshotted the value, so concurrent requests could observe the same count; the harness's in-memory server claimed its hit only after the bindings resolved. Both now claim and snapshot in one synchronous step before any await: the emitted scopes bind `processHit` at the increment, and the harness claims through claimProcessHit before requestBindings. The harness also looks up CLI command paths among authored commands only, since projected MCP commands carry their tool's route id and render through the tool branch. * fix(test): keep harness context optional under provider typegen and document module-evaluation scope Auto-mounting made #409's harness rule unreachable: once the generated augmentation declared provider keys, HarnessOptionsArguments and RenderRouteContextInit turned `options`/`context.providers` mandatory, so a typed project could never let the harness mount its real providers. The harness now keeps both optional (an explicit map must still carry every declared key; a direct runAgentRequest still requires providers) and framework-mode.md, entry-conventions.md, and the README describe the auto-mount contract instead of "the harness never executes provider modules". They also record what the harness does not simulate: provider modules are evaluated once per test worker, so module-level provider state is shared across simulated executables and is only proven cold by the proof levels that spawn the artifact. * test(typegen): pin that harness calls stay legal without context under provider typegen The #409 acceptance pinned `renderRoute(id)` as a compile error once the augmentation declares provider keys. With the harness mounting the project's providers itself that call is the artifact-faithful one, so it now typechecks clean alongside a call that passes only `input`, while a partial explicit fixture still fails on the missing key and a direct runAgentRequest still requires `providers`. * fix(test,changeset): scale the watcher e2e outer timeouts and rewrite the changeset as a release summary The three examples-real tests that wait on watcher rebuilds bounded their rebuild waits at 60s × timeScale but kept fixed 120s/150s outer timeouts, so in CI (timeScale 4) Rstest could end the test before its own readiness wait did. Their outer timeouts scale the same way now. The changeset is rewritten per AGENTS.md as an imperative user-facing summary naming the harness exports and the harness error, ending with the PR reference.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closed-issue verification lane G1 re-audited #313 and found it was closed while its own closing comment said it should stay open: plain
.tsrouted CLI commands never mounted conventional request context providers. Fixing that also exposed a second gap in the surfaces #319 did claim.generatedCliBinEntrySourcenow runs the same ordered, fail-closed provider loop as the Flight/rendered workers before every plainexecute, seedingprocessLifetime;package-build.tspassesmodel.providers.GeneratedCliExecuteContextgainsargs(additive) so the provider invocation is{ kind: 'cli', props: { args, command } }, matching the rendered path.invocationto its react-server worker, so providers on those surfaces receivedinvocation: undefined(they threw oninvocation.kind). The bridge forwards it now.interface.logobut the binary-gatedhost-install-proof.test.tsCodex expectation was not updated (red for anyone withcodexinstalled). Aligned.docs/entry-conventions.mdprovider section/table now lists plain routed CLI and the surface-specificinvocation.kind. Changeset:agent-bundlepatch.Evidence
packages/agent-bundle/tests/cli-routes-build.test.ts— the routed-CLI executable proof now builds asrc/providers/library-tooling.tsand asserts the value on a plaintoolingcommand ({ hits: 1, libraryTooling: { kind: 'cli', tool } }), the renderedreportcommand (tooling: 'cli:ffprobe 6.1'), and the renderedsummarizescript (tooling: 'script:ffprobe 6.1'). Before the bridge fix the rendered cases failed withContext provider "libraryTooling" ... Cannot read properties of undefined (reading 'kind').packages/agent-bundle/tests/entry-shell.test.ts— new pin for plain-CLI provider mounting (ordering, invocation contract, fail-closed wrapping, no-provider fallback) and for the bridge postinginvocation.pnpm typecheck✓,pnpm lint0/0 ✓,pnpm test:unit2667 passed / 5 skipped / 0 failed ✓,pnpm test:route-unit35 ✓,pnpm test:projection63 ✓, targeted integrationcli-routes-build+host-install-proof10/10 ✓ (Codex 0.147.0 and Claude present locally). Fullpnpm test:integration:runin flight; result posted below.Test plan
pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/entry-shell.test.tsAGENT_BUNDLE_WORKBENCH_PREBUILT=1 AGENT_BUNDLE_PACKAGE_PREBUILT=1 pnpm exec rstest --config rstest.integration.config.ts packages/agent-bundle/tests/cli-routes-build.test.ts packages/agent-bundle/tests/host-install-proof.test.tspnpm typecheck && pnpm lint && pnpm test:unit && pnpm test:route-unit && pnpm test:projectionpnpm build && pnpm test:integration:runRefs #313, #246, #364, #319.