Repository navigation
feat(*): run the image and video roles on MiniMax through its provider - #1013
Conversation
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: reject adaptive ratios before submitting text-only MiniMax H3 jobs
I found one merge-blocking request-validation defect, marked inline.
Coverage: I reviewed the repository rules and domain maps, the full github/main...HEAD diff, the media/config/provider/RPC/UI callers, the 15-commit history, backward compatibility for OpenRouter and plain media sections, test changes for weakening, and the Runtime/Web/TUI/agent architecture boundaries. I also checked the new MiniMax V1/V2 wire shapes against MiniMax's current official API reference.
Verification: uv run pytest over the 11 changed Python test modules passed (1265 passed, 3 platform skips) after syncing the declared dev extra; the first attempt could not collect because that extra was not installed. The focused Roles test passed (36 tests), web type-check and generated-RPC check passed, web lint had 0 errors and 4 warnings in untouched files, and the source-language, large-file, diff, and commit-message checks passed.
|
No Windows compatibility issue found in the inspected scope. Reviewed head Windows compatibility notes:
Verification and limitations:
Exact verification commands# suite: 1496 passed, 0 failed, 0 errors, 3 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\139a745da2a44748ab0d7f2cd7c36ab7\pr1013\source\head
uv run pytest tests/test_agents_ppt_launcher.py 'tests/test_cli_doctor_commands.py::test_doctor_flags_a_capability_registered_without_a_key' 'tests/test_cli_doctor_commands.py::test_doctor_names_the_key_of_the_provider_a_section_runs_on' 'tests/test_cli_doctor_commands.py::test_an_unconfigured_capability_is_not_a_failure' tests/test_config_live.py tests/test_config_product_render.py tests/test_config_update_tools.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_raven_config_tool.py 'tests/test_rpc_console.py::test_the_settings_whitelist_is_exactly_this_set' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_volcengine' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_provider_takes_only_a_provider_the_video_tool_runs_on' tests/test_rpc_model.py tests/test_tool_capabilities.py tests/test_rpc_schema_match.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\139a745da2a44748ab0d7f2cd7c36ab7\checks\1013-suite\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\139a745da2a44748ab0d7f2cd7c36ab7\checks\1013-suite\junit.xml' |
2c1e525 to
88447f5
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The new revision fixes the outstanding MiniMax V2 mode conflict before submission and preserves the valid default, concrete-ratio, and first-frame paths. I resolved my earlier thread.
I covered the repository rules in AGENTS.md/CLAUDE.md and the applicable runtime/UI context constraints; the new diff and surrounding request caller; history via range-diff (all 15 previously reviewed commits are content-equivalent after the rebase, with only this fix added); backward compatibility through the control cases; test integrity (coverage was added, not weakened); and the provider/API architecture boundary. No new domain vocabulary or architectural crossing was introduced.
Verification: uv run pytest tests/test_media_gen_minimax.py tests/test_media_gen_tool.py -q (188 passed), plus git diff --check github/main...HEAD (passed). The visible GitHub checks are passing, with several unit/Windows jobs still running at review time.
|
No Windows compatibility issue found in the inspected scope. Reviewed head Windows compatibility notes:
Verification and limitations:
Exact verification commands# suite: 1504 passed, 0 failed, 0 errors, 3 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\35ab19773c0343e792e4aa67d59bc3ba\pr1013\source\head
uv run pytest tests/test_agents_ppt_launcher.py 'tests/test_cli_doctor_commands.py::test_doctor_flags_a_capability_registered_without_a_key' 'tests/test_cli_doctor_commands.py::test_doctor_names_the_key_of_the_provider_a_section_runs_on' 'tests/test_cli_doctor_commands.py::test_an_unconfigured_capability_is_not_a_failure' tests/test_config_live.py tests/test_config_product_render.py tests/test_config_update_tools.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_raven_config_tool.py 'tests/test_rpc_console.py::test_the_settings_whitelist_is_exactly_this_set' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_volcengine' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_provider_takes_only_a_provider_the_video_tool_runs_on' tests/test_rpc_model.py tests/test_tool_capabilities.py tests/test_rpc_schema_match.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\35ab19773c0343e792e4aa67d59bc3ba\checks\1013-suite\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\35ab19773c0343e792e4aa67d59bc3ba\checks\1013-suite\junit.xml' |
|
Not a blocker (review of Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.
|
Replayed onto main by a maintainer. One test now calls borrow_media_credentials, the helper #866 put in place of borrow_openrouter_key, with the author's assertions unchanged.
Replayed onto main by a maintainer. Its guard in borrows_openrouter_key, which #866 removed, moved to borrow_media_credentials: a MiniMax video model still borrows no OpenRouter key.
MiniMax becomes a provider the image tool runs on, chosen the way any other is: tools.media.image.provider names minimax or minimax_cn_api, the section borrows that provider's address, key and headers, and the tool answers it with MiniMax's own /v1/image_generation. The registry marks both sections image_api, so the roles card offers them and the save takes them. This replaces the reference route's own detection. There is no address test and no key chain of its own, and MINIMAX_API_KEY is not read, so the tool, the capability report and doctor resolve one key. An input image is the character to keep (subject_reference), one per request as MiniMax documents. A status MiniMax answers inside HTTP 200 is the error class, with its sentence as the detail. A picture its content check withheld is a refusal, not an empty answer. The call is recorded as usage, a picture link goes through the guarded download, and the file takes the format its bytes declare.
…er it The video section gets the image section's provider field, and the registry a video_api flag: OpenRouter, and MiniMax's two API-key sections. The settings save and raven_config take a provider marked video_api, model.options carries the flag, and the roles card offers the video role those providers. A video pick writes the provider it runs on, empty for OpenRouter, so a pick back to OpenRouter moves the role off MiniMax. A capability row on a named provider names that provider's key, and neither OpenRouter's variable nor its credit note; doctor prints no empty "or env:" line. The settings design records carry a dated amendment.
A video section on minimax or minimax_cn_api now takes MiniMax's video API instead of the model-name route: MiniMax-H3 and MiniMax-H3-Max on /v2/video_generation, the other models on /v1/video_generation with its query and file handoff. The address, key and headers are the provider's, borrowed, so the tool and every reader of the section agree on the key. A call is text-to-video unless it gives first_frame_image, which may be a local file (read under the workspace rule the image tool keeps), a URL or a data URL. A params key the model family does not take is refused before anything is sent, as is a ratio beside a first frame, which MiniMax would ignore. The client-side value checks are gone: they were per-model tables, one already stricter than the API. The finished video is downloaded through the guarded fetch, without the key. A video section naming a provider with no video path, and a first frame off MiniMax, are refused in so many words.
docs/minimax-media.md replaces the two pages the carried commits added, which described the address-and-environment setup this branch removed.
The V2 content item spelled the image part by hand, which the image_block guard refuses outside raven/utils/images.py.
MiniMax-H3-Max documents extra.prompt_expansion_mode, and the params check refused it as unknown.
…n MiniMax The image and video tools author their schema, so the registry serves it per turn, and the provider the section runs on picks between two: off MiniMax each is byte-identical to main's, and on MiniMax it says what MiniMax takes (a character reference; a first frame and MiniMax's params). A model on OpenRouter is no longer offered a first frame it would be refused.
…ts class The per-provider schema made description and parameters properties, so ImageGenerateTool.parameters read off the class was the property object, and the subagent routing test that takes the reference argument's spelling off the host tool failed. A small descriptor answers MiniMax's schema on a tool that runs on MiniMax, and the default one elsewhere and on the class, which has no section to ask.
… name one A deck pinned by PPT_IMAGE_MODEL, PPT_IMAGE_API_BASE or PPT_IMAGE_API_KEY read its image section as a snapshot in its own render, with the provider's name dropped, since the deck would have resolved the name against the providers its chat runs on. The name took MiniMax's request shape with it: a MiniMax host's pinned deck called the OpenAI Images path on MiniMax's address, which MiniMax answers with 404. A pinned section on a named provider now keeps the name and is written to a file of its own beside the render: product_render's new write_rendered_part names it by the render's pid and writes it owner-only as the render is, so the sweep that removes a dead server's render removes it too, and the render's naming stays in that one module. The file carries that provider's endpoint and the deck resolves the section there, so the providers its chat runs on stay as they were. The selectionConfig field now says it is a file the section is read from against that file's providers, the host's or a pinned deck's own. A raven from before render parts renders the section inline, as it did. PPT_IMAGE_PROVIDER names a provider other than the host image role's. Neither the host's model nor its endpoint crosses to it, so PPT_IMAGE_MODEL is required with it; a name the image tool does not run on draws nothing, and the launch log says what to set. A PPT_IMAGE_API_BASE without it is called as an OpenAI-compatible endpoint, as before.
The test holds the rule that MiniMax's request shape follows the provider a section names, but its mock answered the OpenAI Images path on MiniMax's address with a picture. MiniMax's API answers that path with its router's plain 404, so the mock now does too, and the test holds the schema to the same rule.
The text-only branch set ratio to 16:9 before spreading the caller's params over it, so an explicit adaptive replaced that default and reached an API that takes adaptive only beside a first frame. The call is now refused before an HTTP client exists, naming the two ways to make it valid. The first-frame branch and a concrete ratio are unchanged. Covered for both providers and both V2 models, with the default, an explicit 9:16 and a first frame's adaptive as controls. Schema text and docs/minimax-media.md say the same.
… parts A switch to another image provider already takes the host's provider name off the section. On a raven from before render parts the pinned section is written inline, and that branch then deleted the name a second time and raised KeyError, so the whole deck launch failed. The name is now dropped only if it is still there. Covered for a switch from OpenRouter to OpenAI with and without the deck's own image key: the section is rendered inline on OpenAI's address with the host's OpenAI key or the deck's, and carries no provider name and no selectionConfig.
A pinned image section is written to a file of its own, holding the deck's image key, before the launcher places the ACP home, builds the mode catalogue and writes the render. A launch refused at any of those steps never reached the sweep, so the owner-only file stayed behind until a later launch succeeded, one more per refused pid. render_config now removes that file on any exit that is not a finished render, SystemExit included, and re-raises. The file is named by this pid, so removing it cannot touch another run's. Covered by a launch refused at the ACP home through a real placement refusal, and by a render whose final write fails; the owner-only mode, the live read and the dead-pid sweep keep their tests.
…auncher does The gate deciding whether a routed lane can draw ignored the lane's <PREFIX>_IMAGE_PROVIDER, so it answered for the host's image role while the launcher configured another provider. Measured both ways: a deck pinned to MiniMax on the host's MiniMax key, the configuration .env.example documents, was refused, and the route from Raven-Design to Raven-PPT closed; a deck pinned to a provider it cannot draw on, or with no model for a switch, or with no key for the target, was admitted on the host's image key for another provider, or on the deck's OpenRouter chat key. product_image_pin reads the folder's provider and model the way the launcher does, and product_image_key no longer lends the chat key to a provider other than OpenRouter. With a pin, the gate refuses a provider the image tool cannot run on and a switch without a model, accepts the lane's own image key, refuses a base of its own with no key, and otherwise asks the host's live providers for the target's key. It never falls back to the host image role's key for another provider. Unpinned lanes keep the previous answer. The gate stays read-only. The rule is stated twice because a launcher can run on an older raven, so one table drives both sides: the gate's test, and a launcher test that renders each row, sends the picture request through a mock transport and asserts the gate opens exactly where a request goes out. Provider aliases, process environment over the folder .env, and a host key revoked live are covered too.
…paths The registry's video_api decides which providers the roles card offers for the video role, and the tool's dispatch decides which ones run. Nothing tied the two together: the RPC test pins the registry side to a literal list. The new test asserts the offered set equals the providers the video tool has a request path for, so drift on either side fails it.
… docstrings "product" is the retired category word for a discovered agent; the two docstrings the routing gate fix added used it in prose.
88447f5 to
59a6eb5
Compare
|
@0xKT thanks, all four reproduced here before fixing, and all four are fixed in this push (head
Mutants: removing the Could you take another look? |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the five follow-up commits and separated them from the rebase: range-diff shows the 16 previously reviewed commits are patch-identical. The provider-switch compatibility fix, failed-render cleanup, pinned-provider routing gate, video-provider contract test, and vocabulary correction all hold under their callers and controls; I found no concrete regression after trying to refute them.
Coverage this round: repository rules and canonical vocabulary; the actual diff plus surrounding launcher, gate, and media-tool callers; history via range-diff; old-raven and unpinned-path backward compatibility; test integrity (new regressions and shared launcher/gate cases, with no weakened assertions); and the provider, secret, routing, and rendered-part architecture boundaries. My prior thread remains resolved.
Verification: uv run pytest tests/test_agents_ppt_launcher.py tests/test_subagent_routed_lane_readiness.py tests/test_subagent_vendored_agents.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_rpc_model.py -q (703 passed), and git diff --check github/main...HEAD passed. Visible GitHub checks were passing, with four unit shards still running at review time.
## Summary Bump the package version from 0.2.5 to 0.2.6 for the next release. Only pyproject.toml and uv.lock change. Since v0.2.5, main merged 70 PRs: 15 features, 48 fixes, three test, two docs, one perf and one chore. The larger features are portable Agent Plugins 1.0 packages with their MCP servers (#1110), the compacting history strategy with source refs and checkpoints (#1031), sub-agent runs as their own trajectory spans and tabs (#1091, #1094, #1096, #1100), download mirror fallback (#1053), Keenable as a keyless web_search and web_fetch vendor (#1088), Opper as an OpenAI-compatible gateway (#869), and the MiniMax image and video roles (#1013). None carries a BREAKING CHANGE footer. The maintainer chose a patch bump, the way v0.2.1 through v0.2.5 were for their features. A v0.2.6-rc1 tag goes first to dry-run the release pipeline, as v0.2.5 did. The stable tag and release notes follow once this lands. uv.lock is regenerated with uv 0.12.12, which leaves only the raven version line changed (uv 0.9.x rewrites unrelated dependency markers and uv 0.13.x raises the lock revision). ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other ## Verification - `uvx --from uv==0.12.12 uv lock` on a clean worktree cut from origin/main: Updated raven v0.2.5 -> v0.2.6 - `git diff --numstat origin/main..HEAD`: one line each in pyproject.toml and uv.lock - `uvx --from uv==0.13.3 uv lock --check`: exit 0, so the `uv export --locked` step in release.yml accepts the lock - `PYTHONPATH=. python3 scripts/check_commit_messages.py origin/main..HEAD`: exit 0 - `npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs`: exit 0 - No test run: the change is version metadata only. - [ ] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No code change. Rollback is reverting this commit before any v0.2.6 tag is pushed. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
Summary
Runs the image and video roles on MiniMax. This carries octo-patch's #432 (MiniMax image references) and #433 (MiniMax image-to-video) and reworks both onto the provider route the review settled on #432: MiniMax is a provider a media section names, not an address or a model name the tool recognises.
minimaxandminimax_cn_apiasimage_apiand, new,video_api. A section naming either borrows that provider's address, key and headers throughborrow_media_credentials, and the tool dispatches onmedia_provider()to MiniMax's own APIs.MINIMAX_API_KEYis not read for these tools, so the tool, the capability report and doctor resolve one key, which settles the blocking note on feat(agent): support MiniMax image references #432 about the readers.POST /v1/image_generation. An input image is the character to keep (subject_reference), one per request as MiniMax documents. Abase_respstatus is the error class with MiniMax's sentence as the detail, a picture MiniMax's content check withheld is a refusal, the call is recorded as usage, a picture link goes through the guarded download, and the file takes the format its bytes declare.provider(VideoToolConfig), the settings save andraven_configaccept a provider markedvideo_api,model.optionscarries the flag (both generated clients regenerated), and the roles card offers the video role those providers. A video pick writes the provider it runs on, empty for OpenRouter./v2/video_generationand the other models/v1/video_generationwith its query and file handoff. A call is text-to-video unless it givesfirst_frame_image(a local file under the image tool's workspace rule, a URL or a data URL). A params key the model family does not take, a ratio beside a first frame, andadaptiveon a text-only call are refused before anything is sent, since MiniMax would ignore the first two silently and takesadaptiveonly beside a frame; a concrete ratio is passed through. The finished video is downloaded through the guarded fetch, without the key.to_schema, so the registry serves them per turn, and the search and index readers ofdescriptionandparametersget the same per-provider answer.providers.<name>.apiKey, with no OpenRouter variable or credit note, and doctor prints no empty "or env:" line.docs/minimax-media.mdreplaces the two pages the PRs added, the settings design records carry a dated amendment, and the changelog has the entry.PPT_IMAGE_MODEL,PPT_IMAGE_API_BASEorPPT_IMAGE_API_KEYused to drop the provider's name, since the deck would have resolved it against the providers its chat runs on, and with the name it lost MiniMax's request shape: the OpenAI Images path on MiniMax's address, which MiniMax answers with 404. A pinned section on a named provider now keeps the name in a file of its own beside the render (product_render.write_rendered_part), with that provider's endpoint: owner-only, named by the render's pid so the same sweep removes it, and resolved there, so the deck's chat providers are untouched. NewPPT_IMAGE_PROVIDERputs a deck's pictures on a provider other than the host image role's; it needsPPT_IMAGE_MODEL, carries none of the host's endpoint across, and a name the image tool does not run on draws nothing, with the launch log saying what to set. APPT_IMAGE_API_BASEwithout it is an OpenAI-compatible endpoint, as before.<PREFIX>_IMAGE_PROVIDERset it answers for that provider, by the launcher's rule: no for a provider the image tool cannot run on and for a switch without a model, yes for the lane's own image key, no for a base of its own without one, and otherwise the host's live key for that provider. It never counts the host image role's key for another provider, and the key that pays for the lane's words is lent only towards OpenRouter. An unpinned lane is answered as before. The gate stays read-only. The rule is stated twice because a launcher can run on an older raven, so one table drives both sides: the gate's test, and a launcher test that renders each row and asserts the gate opens exactly where a picture request goes out.PPT_IMAGE_PROVIDERswitch renders inline instead of raising.The two carried commits keep octo-patch's authorship. Replaying them onto main needed one adaptation each, named in its commit body, because #866 replaced
borrow_openrouter_keywithborrow_media_credentialsand removedborrows_openrouter_key. What the reviews filed, and where it went:adaptiveis refused before a request is sent, sinceoptionsspread over the default; covered for both providers and both V2 models.borrows_openrouter_key: gone with that function.Known gaps, checked and not changed here:
ppt_generate_imagedescribesreferencesas up to six pictures to match or vary, and on MiniMax the image tool sends the first alone, as the character to keep. Its schema does not vary by provider the wayimage_generate's now does; that belongs with the deck engine, not here.providerthe video tool has no request path for is refused in a sentence on every call, while doctor still reports the key it borrows. The settings save andraven_configrefuse such a provider.providers.openrouterkey, where the launcher takes any host provider block that addresses OpenRouter.api.minimax.ioandapi.minimaxi.com:POST /v1/image_generationanswers its authentication error,POST /v1/images/generationsthe same 404 as a made-up path.Type
Verification
The full-suite counts below were measured on origin/main ba3a3f8 with this branch applied, on Linux with Python 3.12, and were not re-measured since. The branch was rebased onto 55c145c and then onto 05cf9c3, without conflicts and with every earlier commit patch-identical; the review follow-up was verified at 05cf9c3:
pytest -n 0 tests/test_agents_ppt_launcher.py tests/test_subagent_routed_lane_readiness.py tests/test_subagent_vendored_agents.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_rpc_model.py: 703 passed, 28 of them new, in a venv built byuv sync --frozen --python 3.12 --all-extras;tests/test_config_product_render.py tests/test_subagent_routing_backend.py: 213 passed, 3 skipped.KeyError, the refused and failed launches left the image file, and five of the gate table's rows disagreed with the launcher. Five mutants of the fixes (the switch's default, the cleanup, the gate falling back to the host image key, the OpenRouter loan on the lane's own key, the chat key lent to another provider) each turned their tests red; the video-set test turned red when one side of the set was changed. The gate's table run with file writes, sockets and key output trapped stayed clean, beside a control mutant for each trap.ruff check,ruff format --check,ty check,lint-imports(10 contracts kept),scripts/check_commit_messages.py,commitlint,scripts/check_large_files.pyandscripts/check_source_language.pyover the 21 commits: pass, the last beside a CJK control commit it refuses, and commitlint beside a header it refuses.Earlier rounds:
uv run --frozen --python 3.12 --all-extras pytest -q -p no:randomly: 33584 passed, 178 skipped, 2 xfailed, 8 failed. The same 8 fail on clean ba3a3f8 on this machine: five proxy tests intests/test_config_update_providers.py,test_install_script.py::test_resolve_node_dir_answers_each_case_it_exists_for,test_knowledge_parser_excel.py::test_a_legacy_workbook_is_converted_and_readandtest_subagent_node_runtime.py::test_what_cannot_be_read_names_nothing.ruff checkandruff format --checkoverraven evolver agents plugins-dist tests scripts docs-site,lint-imports(10 contracts kept),deptry ravenandty check raven evolver agents plugins-dist scripts: all pass.scripts/check_large_files.pyandscripts/check_source_language.pyoverorigin/main...this branch: exit 0, each beside a control commit it refuses (a 2.1 MiB file; a CJK comment inmedia_gen.py).scripts/check_commit_messages.pyandcommitlint --config commitlint.config.cjsover the fifteen commits then on the branch: pass.ui-web:
npm run gen:check(generated.ts matches the contract, 245 methods),npm run lint(0 errors; 4 warnings, all in files this PR does not touch),npm run type-check, andnpx vitest run --maxWorkers=8: 251 files, 4159 tests passed. On this 32-core machine vitest's default worker count failed a varying set of tests on clean main a8455ba too, mostly 5 s timeouts, each passing when its file ran alone.ui-tui:
npm run lint,lint:rpc,lint:i18nandtype-checkpass.npx vitest run --maxWorkers=8: 2233 passed, 13 skipped, 9 failed, the same 9 (inpaintDump,intensityTransition,textInputTypingBurstanduserSlabRule) that fail on clean ba3a3f8 here.Off MiniMax, and read off the class, both tools' schemas were compared with main's module for a section on no provider,
openrouter,openaiandvolcengine: byte-identical.Mutation check: each behaviour this PR adds was removed one at a time (27 in the tools and settings, 17 in the deck launcher and its render parts, 2 in
Roles.tsx), and every mutant turned at least one test red, beside an unmutated run that is green. The three on the text-only ratio refusal are the guard, the default it fills and the direction of the comparison.Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
A section naming no provider, or one on another provider, behaves as on main, and both tools show it main's schema. A config that wrote
providerinto the video section by hand was ignored on main and is honoured now: on a provider without a video path every call is refused. MiniMax keys travel only to the address of the provider section that holds them. A deck pinned on a named provider now reads its image section from an owner-only file beside its render, which holds the image key as the render does; an unpinned deck, and one pinned to a base without a provider, renders as before. A route into a lane that pins<PREFIX>_IMAGE_PROVIDERcan now open or close differently from before: it opens on the host's key for the pinned provider, and closes where the launcher would draw nothing. Revert the squash commit to remove the routes; a config naming MiniMax for a media role then fails the settings save again.Related Issues
Supersedes #432 and #433; both are carried here.