feat(routes): let a route declare its render budget with config.render.maxElapsedMs (#454) - #526
Conversation
🦋 Changeset detectedLatest commit: 6ff411b 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: 40675e0434
ℹ️ 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".
| // directly and has none, so declaring one there is a mistake to surface. | ||
| const render = validateRouteRenderConfig(route, 'CLI route'); | ||
| diagnostics.push(...render.diagnostics); | ||
| if (render.render !== undefined && !isRenderedCliRoute(route)) { |
There was a problem hiding this comment.
Reject empty render configs on plain commands
When a plain .ts command declares the type-valid config.render: {}, validateRouteRenderConfig() returns neither a budget nor a diagnostic, so this condition is false and the unsupported key compiles silently. This contradicts the new documentation and changeset promise that plain commands reject any config.render declaration with AB4835; check whether route.config['render'] was declared rather than whether validation produced a budget.
AGENTS.md reference: AGENTS.md:L78-L81
Useful? React with 👍 / 👎.
76e2f2c to
72dd531
Compare
72dd531 to
3dae9ec
Compare
3dae9ec to
8e646ed
Compare
8e646ed to
bcde3ff
Compare
bcde3ff to
5f57f0f
Compare
…and, including an empty one
0d92015 to
8813bdf
Compare
Fixes #454.
Why
Every rendered route runs inside one render session bounded by
DEFAULT_AGENT_RENDER_LIMITS.maxElapsedMs(60 s). The CLI runtime and the MCP projector construct the dispatcher with no overrides and no config field could raise it, so a long-poll route (cargo-hauler'shauler_await) had to clamp its own waits to 55 s and ask the agent to call again.Design
config.render: { maxElapsedMs }onToolConfig,ResourceConfig,PromptConfig, andCliRouteConfig(RouteRenderConfig). Read statically with the rest ofconfig, soinspect --routesshows it and the generated servers never evaluate the module'sconfig.AB4835(routes/render-budget.ts):rendermust be an object whose only key ismaxElapsedMs, a positive safe integer of milliseconds ≤MAX_ROUTE_RENDER_ELAPSED_MS(24 h, exported fromagent-bundle). A plain.tsCLI command declaring one is alsoAB4835— it has no render session. Reported once per route: MCP routes with their server, CLI routes in the command compiler; a projected MCP command inherits its tool's value without re-reporting.AgentRenderDispatch.limitslayers per-dispatch limits over the dispatcher's own, so the one long-lived dispatcher a generated MCP server or CLI executable owns serves routes with different budgets.renderGeneratedRoutepasses the compiledconfig.render; the generated CLI shell passescommand.render(CompiledCliCommand.render, absent key when undeclared so pre-existing graph digests are unchanged); the route-unit andmcp-in-memoryharnesses layer the compiled budget over thelimitsa test passes as the dispatcher base (openInMemoryMcpServer({ limits })is new).progress.report()and streamedAgent.Progressfallback is forwarded asnotifications/progressfor the whole call.adapters/capabilities/*.jsonrecords an MCP tool-call deadline. From the host docs (cited in the guide): Claude Code's default per-call wall clock is ~28 h (MCP_TOOL_TIMEOUT; per-servertimeout), with a stdio idle timer of 30 min that progress notifications reset; Codex bounds a call attool_timeout_sec(60 s default). The 24 h ceiling sits under Claude's default; Codex must be raised by the operator regardless. Default (60 s) unchanged.Event routes keep their existing
timeoutMs; rendered scripts have no config surface and are out of scope.Tests
rsc-runtime/tests/dispatcher.test.ts— per-dispatch limits layer over the dispatcher base (real-time EOF past the base, completes under the dispatch budget; other keys untouched).tests/cli-routes.test.ts— valid budgets on compiled commands + inheritance by projected MCP commands; everyAB4835shape, message order, and that a rejected route compiles no command.tests/route-unit/render-route.test.ts—wait(fixture now declaresrender: { maxElapsedMs: 120_000 }) renders past a 100 ms base for a manifest route, the same module rendered directly does not, progress keeps flowing.tests/projection/mcp-in-memory.test.ts— real SDK client over the generated server with a 100 ms base:waitcompletes and threenotifications/progressarrive;catalog(no budget) is still bound by the base.tests/test-harness-manifest.test.ts— projected command carries the budget.Docs
docs/diagnostics.md(AB4835),docs/entry-conventions.md,website/docs/{en,zh}/guide/authoring/mcp.mdx(new "Render budget" section),website/docs/{en,zh}/guide/authoring/package-entries.mdx.pnpm docs:site:buildgreen.Verification
pnpm typecheck(theframework-plugin-registration.test.tserrors are pre-existing onmain, fixed by #523),pnpm lint,pnpm test:unit(onlyframework-plugin-registration.test.tsfails, same pre-existing cause; 24 timeouts under concurrent docs build all pass when re-run),pnpm test:route-unit,pnpm test:projection,pnpm docs:site:build.Review status
40675e0(PR opened) and left one P2 thread onroutes/cli-commands.ts: a plain command declaring the type-validconfig.render: {}compiled silently. Fixed in72dd531— anyrenderdeclaration on a plain.tscommand isAB4835, covered by the newplain-empty.tscase incli-routes.test.ts. No PR comments are posted from this side; the thread is answered here.72dd531(rebased overmainafter feat(cli): route first-party CLI terminal I/O through Effect Terminal/Stdio; spell routed-CLI input errors in CLI terms (#465) #505/test(build): pass the project root to the composed configs in the plugin-registry test #523) is not yet re-reviewed by the connector.