Always show the Agents section in WP Explorer - #540
Merged
Conversation
The Agents framework is opt-in behind the `agents` extended option, default off — and the WP Explorer integration lived inside the gated module load, so the section was invisible on every site that had never turned it on. The one person who could enable it had no way to discover it from the window it belongs to. Load `includes/agents/my-wordpress.php` unconditionally, alongside guard.php, and ship `enabled` + `canEnable` on the section config. With the flag off the section still renders: every control disabled, no request issued (the REST routes genuinely do not exist then), and a warning notice that takes `manage_options` users straight to Preferences → Features. The capability filters move from rest.php and the avatar URL from identity.php into bootstrap.php — the entity descriptor needs all four while those two files are unloaded. The read gate is unchanged: `openstation_agents_user_can_read` (`edit_posts`) still decides whether the section is listed at all. Also stops the Send-to-agent menu warming its cache while the framework is off, which would have fetched a guaranteed 404. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`OpenStation_User_File::serialize()` guarded its agent block on
`openstation_agent_is_agent()` — which lives in guard.php and loads
unconditionally, because being an agent is a property of the user row
and the row survives the feature being switched off. It then called
`openstation_agent_get_description()` and `openstation_agent_get_triggers()`,
which live in store.php and do not load then.
So turning the `agents` extended option off with an agent tile on
someone's desktop fataled the whole admin on the next boot payload:
Uncaught Error: Call to undefined function
openstation_agent_get_description()
Guard the definition getters by name. The tile still reads as an agent
— that much is true regardless of the flag — but ships an empty
description and null drag kinds, so it rejects every drop, which is
correct: nothing can run to receive one.
Adds a structural test asserting that every agents function called
from outside `includes/agents/` is either declared in an
unconditionally loaded file or wrapped in `function_exists()` at the
call site. The rest of the suite cannot see this class of bug — the
test bootstrap forces the framework on, so these calls resolve fine
and fatal only on a real install.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The full-width notice said the same thing the empty state below it already said. While the framework is off nothing is fetched, so the list is always empty and that pane always renders — the banner was guaranteed duplication, not a fallback for some other case. It also carried a dismiss button by default (`<os-notice>` is dismissible unless told otherwise) which could not dismiss anything: no `notice-id`, so it came back on the next repaint, and closing it would not have turned agents on either way. `<os-empty-state>` has a `cta` slot built for exactly this. One message, action attached to it, no full-window bar carrying a single sentence with two thirds of it empty. The `is-disabled` dim now scopes to the sidebar rather than the whole layout: the detail pane holds the one button that undoes the disabled state, and grewing that out with everything else turns an explanation into a dead end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`<os-empty-state>` centres its own contents, but the host is a flex item that shrinks to fit, so it sat at the top of a pane that is otherwise several hundred pixels of empty space. Auto margins on both axes absorb the slack — placement only, matching the pattern already used for the comments window's placeholder. Applies to the framework-off state and "No agents yet" alike. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Why
The Agents framework is opt-in behind the
agentsextended option (default off), and the WP Explorer integration lived inside the gated module load. So on every site that had never turned agents on, the section simply did not exist — and the one person who could enable it had no way to discover it from the window it belongs to.What changed
includes/agents/my-wordpress.phpnow loads unconditionally, alongsideguard.php. The section config gained two fields:enabled— mirrors the extended option. False means the REST routes are not registered, so the bundle must not fetch.canEnable—manage_options, since the Extended options block of the Features tab is admin-only.With the flag off the section still opens and renders the same layout, entirely inert:
disabledlistAgents(), no AI-status probe, no roles fetch — nothing that would 404Two helpers moved so the entity descriptor can be built while
rest.phpandidentity.phpare unloaded:openstation_agents_user_can_{read,manage,invoke}()rest.phpbootstrap.phpopenstation_agent_avatar_url()identity.phpbootstrap.phpBoth files keep a comment at the old site explaining why, and a PHPUnit test asserts the declaring filename so they don't drift back.
Also:
agentsConfigured()in the Send-to-agent menu now requiresenabled, not just the presence of the config block — otherwise the cache warm-up would fire a guaranteed 404 on every site with the flag off.What did not change
The read gate.
openstation_agents_user_can_read(edit_posts) still decides whether the section is listed at all — subscribers and contributors see nothing, exactly as before.Known limitation
Flipping the option from the notice does not live-refresh the open WP Explorer window; the section config is baked into the window payload, and the extended-options save is not one of the payloads the menu-refresh bridge diffs. The user needs a reload to see the section come alive. Worth a follow-up if it grates.
Tests
tests/phpunit/tests/agentsMyWordpress.php(new, 8 tests) — entity listed with the flag off, withheld from non-readers,enabled/canEnable/canManage/canInvokeper role, helpers declared in the always-loaded bootstrap.tests/vitest/agents-renderer.test.ts— newframework turned offblock: no fetch,is-disabledclass, create button disabled-not-hidden, notice wiring toopenOsSettings({ tabId: 'features' }), admin vs non-admin copy, empty-state heading.tests/vitest/agents-send-to.test.ts— does not warm while off.Gates:
npm run build,lint,typecheck,test:js(4013 passed),lint:php,test:php(2069 passed) all green.Docs
docs/hooks-reference.md— the "one exception" note is now two, with the reasoning; the capability filters andopenstation_agent_avatar_url()are marked as available while the feature is off.docs/examples/agents.md— the intro no longer says nothing survives the flag being off.🤖 Generated with Claude Code