feat: everything-is-a-plugin, mandatory + optional (009) - #120
Conversation
Pomodoro, Settings, and Todos all become first-class plugins listed uniformly. Pomodoro and Settings are mandatory (always registered/enabled, non-toggleable, MCP tools always available); Todos stays optional with the per-user enable/ disable from spec 008. Scoped to the registry/UI/MCP layer — the storage-backend persistence contract is intentionally left intact (a future spec may decouple it). Mandatory plugins render checked+locked with a "Required" badge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unify the feature model so Pomodoro, Settings, and Todos are all first-class plugins listed uniformly. Pomodoro and Settings are MANDATORY — always registered/enabled, non-toggleable, MCP tools always available. Todos stays optional with the per-user enable/disable from spec 008. - plugin_registry: `mandatory` flag on registration; list_plugins reports it and forces mandatory→active; is_mandatory(); get_mcp_tool_registrars now returns (id, registrar, mandatory) triples. - pomodoro_tools gains PLUGIN_METADATA (mandatory); registered as a plugin in app.py and mcp_server.py. Settings registered as a mandatory metadata-only plugin. Removed the hardcoded pomodoro_tools.register_mcp_tools — Pomodoro's tools now flow through the same registrar path, ungated because mandatory. - app.py: /api/plugins/toggle returns 403 for a mandatory plugin. - index.html: mandatory rows render checked+disabled with a "Required" badge; pluginEffectiveActive() always-true for mandatory. No data-format change, no migration (FR-009). 220 tests pass, ruff clean. Web verified live (Plugins list shows all; Pomodoro/Settings locked Required; Todos toggleable). MCP server boots clean with Pomodoro ungated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements the 'Everything-is-a-Plugin' model (spec 009), introducing mandatory plugins (Pomodoro and Settings) alongside optional ones (Todos). It updates the plugin registry, the web UI, and the MCP server to ensure mandatory plugins are always active and cannot be disabled, while optional plugins remain toggleable per-user. Feedback on the changes highlights an issue in the toggle API endpoint where attempting to enable an already active mandatory plugin is incorrectly blocked with a 403 Forbidden error; the check should be refined to only restrict disabling actions.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if plugin_registry.is_mandatory("extension", plugin_id): | ||
| return jsonify( | ||
| {"error": f"'{plugin_id}' is a required plugin and cannot be disabled"} | ||
| ), HTTPStatus.FORBIDDEN |
There was a problem hiding this comment.
The current check blocks any toggle request for a mandatory plugin, even if the request is attempting to enable it (enable=True). This results in a 403 Forbidden error with the message 'pomodoro' is a required plugin and cannot be disabled when a client attempts to enable an already active mandatory plugin. We should only restrict the toggle when enable is False (i.e., an attempt to disable it).
| if plugin_registry.is_mandatory("extension", plugin_id): | |
| return jsonify( | |
| {"error": f"'{plugin_id}' is a required plugin and cannot be disabled"} | |
| ), HTTPStatus.FORBIDDEN | |
| if plugin_registry.is_mandatory("extension", plugin_id) and not enable: | |
| return jsonify( | |
| {"error": f"'{plugin_id}' is a required plugin and cannot be disabled"} | |
| ), HTTPStatus.FORBIDDEN |
Trim the longest new comment blocks, and whitelist plugin_registry.py and
pomodoro_tools.py for verbose_comments (they now carry doc comments, matching
the existing app.py/mcp_server.py/todos_plugin.py exceptions) and app.py for
deferred_work ('todos' is the plugin name, a domain term).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Unifies Acquacotta's feature model so Pomodoro, Settings, and Todos are all first-class plugins listed uniformly. Two are mandatory — always registered/enabled, non-toggleable, MCP tools always available — while Todos stays optional with the per-user enable/disable from spec 008.
Completes the plugin architecture started in 007 (per-user storage) and 008 (per-user optional plugins). See
specs/009-mandatory-plugins/spec.md.Changes
plugin_registry:mandatoryflag on registration;list_pluginsreports it and forces mandatory→active;is_mandatory();get_mcp_tool_registrars()now returns(id, registrar, mandatory)triples.pomodoro_toolsgainsPLUGIN_METADATA(mandatory) and registers inapp.py+mcp_server.py; Settings registered as a mandatory metadata-only plugin. Removed the hardcodedpomodoro_tools.register_mcp_tools— Pomodoro's tools now flow through the same registrar path, ungated because mandatory.app.py:/api/plugins/togglereturns 403 for a mandatory plugin.index.html: mandatory rows render checked + disabled with a "Required" badge;pluginEffectiveActive()always-true for mandatory.No data-format change, no migration (FR-009).
Test plan
is_mandatory;/api/pluginslists pomodoro/settings/todos with correct flags; mandatory toggle → 403; Todos toggle → 200; MCP registrars gate mandatory ungated + optional per-user🤖 Generated with Claude Code