Skip to content

Always show the Agents section in WP Explorer - #540

Merged
AllTerrainDeveloper merged 4 commits into
trunkfrom
feat/always-show-agents-section
Aug 8, 2026
Merged

Always show the Agents section in WP Explorer#540
AllTerrainDeveloper merged 4 commits into
trunkfrom
feat/always-show-agents-section

Conversation

@AllTerrainDeveloper

@AllTerrainDeveloper AllTerrainDeveloper commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Why

image

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 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.php now loads unconditionally, alongside guard.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.
  • canEnablemanage_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:

  • warning notice at the top, with a Turn on Agents button that opens Preferences → Features (non-admins get "ask an administrator" instead)
  • + Create agent rendered but disabled
  • empty state reads "Agents are turned off" rather than "No agents yet"
  • no listAgents(), no AI-status probe, no roles fetch — nothing that would 404
  • the AI-connector warning is suppressed while off, so the one actionable notice isn't competing with a second one

Two helpers moved so the entity descriptor can be built while rest.php and identity.php are unloaded:

Helper From To
openstation_agents_user_can_{read,manage,invoke}() rest.php bootstrap.php
openstation_agent_avatar_url() identity.php bootstrap.php

Both 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 requires enabled, 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 / canInvoke per role, helpers declared in the always-loaded bootstrap.
  • tests/vitest/agents-renderer.test.ts — new framework turned off block: no fetch, is-disabled class, create button disabled-not-hidden, notice wiring to openOsSettings({ 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 and openstation_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

Open WordPress Playground Preview

AllTerrainDeveloper and others added 4 commits August 8, 2026 20:19
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>
@AllTerrainDeveloper
AllTerrainDeveloper enabled auto-merge (squash) August 8, 2026 18:34
@AllTerrainDeveloper
AllTerrainDeveloper merged commit cf885c5 into trunk Aug 8, 2026
5 checks passed
@AllTerrainDeveloper
AllTerrainDeveloper deleted the feat/always-show-agents-section branch August 8, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant