Disabling, replacing and profiles

Turn system Plugins off per Project, replace a Plugin or one slot entry, and choose the hosts and profiles a Plugin runs on.

Which Plugins run in a window is decided by three things: the Project's choice (a system Plugin switched off), a trusted Plugin that replaces another, and the host mode's profile. This page says what each does and where it is stored. Design: dev_docs/everything-is-a-plugin-plan.md sections 5.1, 5.7 and 5.8.

Turning a system Plugin off#

The Plugins page has a System tab with every system Plugin (studio.*). Its switch turns the Plugin off for the open Project, both halves. A system Plugin cannot be uninstalled.

  • The choice is stored per Project in .witwin/plugins/state.json (the disabled list, plugins/state.py), the same ledger as third-party Plugins. Web-only system Plugins (studio.hierarchy, studio.properties, ...) are stored there too.
  • Plugins that require the one turned off go off with it. The page names them in a confirmation first; each row then says why it is off, for example requires studio.runs, which is disabled in this Project. Turning it back on restores all of them.
  • Plugins with an optional dependency on it stay on. Their entries in its slots wait and come back when it does (buildHost.tsx, plan 5.1).
  • Two system Plugins cannot be turned off: studio.scene and studio.project. The kernel itself serves their RPC domains (scene sync, the Project file layer, ui_state.*). The list is kernel in server/witwin_server/plugins_builtin/profiles.toml; the page shows them locked, and plugin.system.set_enabled refuses them.
  • When it takes effect: the web half and the Python half's RPC methods change at once. Python Handlers, routes and startup Tools change the next time the Project opens; the page says so (restartRequired).

RPC: plugin.system.list (the state the page shows) and plugin.system.set_enabled { plugin_id, enabled } (server/witwin_server/features/plugins/rpc.py). The Python side is BuiltinPluginHost.set_enabled / system_state (plugins/builtin.py); the web side is studio/src/app/pluginHost/systemComposition.ts, which calls pluginRuntime.setEnabled(id, enabled, reason).

Replacing a Plugin or one slot entry#

replaces in witwin.toml (and the same field in definePlugin) names what this Plugin takes the place of.

replaces = ["studio.console"]                         # the whole Plugin
replaces = ["properties.inspector:component:transform"]  # one keyed slot entry: "<slot>:<key>"
  • Whole Plugin. While the replacer is loaded and active, the replaced Plugin is off in both halves. The Plugins page shows "Replaced by <id>". Plugins that require the replaced one count the replacer in its place. When the replacer unloads, the replaced Plugin comes back, unless the Project turned it off. The same happens in the window when the replacer's web activate throws: the replacer is marked failed and the replaced Plugin is activated again (pluginRuntime.ts restoreReplaced).
  • One entry. "<slot>:<key>" lets the replacer's registration for that key in that keyed slot win over the owner's, without turning the owner off (replaces: '*' on the registration, @witwin/workbench/slots). A wildcard never displaces an entry the kernel registered.
  • Only a trusted third-party Plugin replaces anything: a local-path Plugin, or an installed one the user trusted. The server does not apply replaces of a system Plugin (plugins/composition.py trusted_replacements skips them), and no system Plugin declares one. An untrusted Plugin's replaces is ignored; its registrations collide instead.
  • The security core cannot be replaced: the kernel Plugins above, Tool permission tiers and the confirmation flow (tools/invoker.py), and the project.fs path checks. They are kernel code, not Plugins, so there is nothing to name; replaces of a kernel Plugin or a kernel module is refused (plugins/composition.py replace_error).
  • A replacer keeps its own namespace. It cannot take over the replaced Plugin's RPC domain (third-party methods live under <plugin id>.), so it serves its own methods; it may declare the same slots, so other Plugins' entries attach to it.

The regression Plugin is server/tests/fixtures/plugins/witwin_example_console/: it replaces studio.console in both halves and declares console.channels. Tests: server/tests/test_system_plugin_disable.py and studio/src/app/pluginHost/systemComposition.test.ts.

Profiles and hosts#

A profile says which system Plugins a host mode loads. The file is server/witwin_server/plugins_builtin/profiles.toml:

ProfileUsed bySystem Plugins
desktopthe desktop app's ProjectServerall (studio.*)
weba hosted ProjectServer (WITWIN_HOSTED=1)all (studio.*)
headlesswitwin CLI and witwin.studio scripts that open a Project (api/runtime.py open_project_scene)the Python halves a script needs: scene, project, assets, materials, em, parameters, timeline, platform, runs, results, studies, agent, tasks

WITWIN_PROFILE=desktop|web|headless overrides the choice. Each profile must include every required dependency of what it lists (test_profiles_file_names_only_system_plugins_and_is_closed_under_requires). A system Plugin outside the profile has status excluded.

hosts in witwin.toml (and definePlugin) says where a Plugin may load: any of desktop, web, headless. Without it a Plugin loads everywhere.

hosts = ["desktop"]   # needs this computer: not loaded in Studio Web

A Plugin whose hosts lack the current host does not load. The ProjectServer reports not available on web (hosts: desktop) (or on desktop, on headless), and a web half reports not available on the web (hosts: desktop) (or on the desktop) on the Plugins page (pluginRuntime.ts).