Skip to content

Integrate with Elementor's Angie - #705

Draft
ilicfilip wants to merge 29 commits into
developfrom
filip/angie-integration
Draft

Integrate with Elementor's Angie#705
ilicfilip wants to merge 29 commits into
developfrom
filip/angie-integration

Conversation

@ilicfilip

@ilicfilip ilicfilip commented Nov 6, 2025

Copy link
Copy Markdown
Collaborator

This PR implements https://github.com/ProgressPlanner/progress-planner-pro/issues/182 (still WIP of course)

Technically implementation is described in README.md, so I will focus more on testing initial implementation and what needs to be done in the future.

When Progress Planner MCP server is registered Angie becomes aware of it and it is smart when you ask her about it, for example queries like "List me all (completed) Progress Planner tasks" work without the a problem.

Tasks like "Can you complete 'Set site tagline' Progress Planner task for me? I would like to set tagline to 'Thank you!'" work fine most of the time.

Good example is "Can you complete the "Set site timezone" Progress Planner task? I would like to set the timezone to Amsterdam" as well. It is smart enough to pass Europe/Amsterdam to our API callback so the timezone_string wp_option is correctly set.

Completing the "Set site locale" didnt work for me, what Angie did was that it set the site locale (by setting the wp_option directly) and then tried to mark PP task as completed through our API endpoint. That is the thing which we need to be careful when testing, as PP plugin codebase will mark task as completed if the condition is satisfied, but it doesnt mean that it was completed through the Angie integration itself. More on that below.

Also my tries to tell it to complete the "Set the site icon" task to the icon which I have uploaded failed, it didn't even add icon to the Media library

Angie runs in an iframe and the WP Dashboard on the right doesn't update without refreshing it. Mentioning it since it will look a bit weird if Angie says that she changed site locale but change is not visible right away. This can be especially be weird if we trigger confetti (yes, it should be possible to do some kind of celebration at least) but in order to do so we need to make sure that tasks are completed through our integration (not by setting wp_options directly).

That can probably be improved by providing better context to Angie here.

When testing be aware that you have (only) 900 tokens per day on a free plan.

One technical note, code is placed in this way so our autoloader works (at first I placed it in ./third-party/angie, but autoloader requires classes only from classes directory) so we will probably move it to a better place.

ilicfilip and others added 2 commits November 6, 2025 10:33
Co-authored-by: Sculptor <sculptor@imbue.com>
@github-actions

github-actions Bot commented Nov 6, 2025

Copy link
Copy Markdown
Contributor

Test on Playground
Test this pull request on the Playground
or download the zip

@ilicfilip
ilicfilip marked this pull request as draft November 6, 2025 12:29
@github-actions

github-actions Bot commented Nov 6, 2025

Copy link
Copy Markdown
Contributor

✅ Code Coverage Report

Metric Value
Total Coverage 33.97% 📉
Base Coverage 32.27%
Difference 📈 1.70%

⚠️ Coverage below recommended 40% threshold

🎉 Great job maintaining/improving code coverage!

📊 File-level Coverage Changes (4 files)

🆕 New Files

Class Coverage Lines
🟢 Progress_Planner\Third_Party\Angie\Angie_API 85.97% 239/278
🔴 Progress_Planner\Third_Party\Angie\Integration 7.50% 3/40

📈 Coverage Improved

Class Before After Change
Progress_Planner\Suggested_Tasks\Task 20.00% 23.33% +3.33%
Progress_Planner\Base 45.40% 46.95% +1.55%
ℹ️ About this report
  • All tests run in a single job with Xdebug coverage
  • Security tests excluded from coverage to prevent output issues
  • Coverage calculated from line coverage percentages

@ilicfilip

Copy link
Copy Markdown
Collaborator Author

Notes from the recent commits:

  • Tasks which Angie 'sees' are limited now, she can see only the tasks which she can complete: core-blogdescription, set-locale, select-timezone (WIP of course, the list will be expanded).
  • Angie tool definition is moved to PHP, so JS doesnt need to be rebuilt every time there is a change (new tool is added or simply description changed). This will be further improved.
  • JSON schema, which is used to define Angie tool, is improved (uses more strict type checking) and expanded.

We should be careful when defining schema, docs are not that clear and a small mistake can cause Progress Planner MCP server (or tools) not to be registered.

@github-actions

github-actions Bot commented Jan 30, 2026

Copy link
Copy Markdown
Contributor

🔍 WordPress Plugin Check Report

⚠️ Status: Passed with warnings

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
10 0 10

⚠️ Warnings (10)

📁 classes/suggested-tasks/providers/class-content-review.php (4 warnings)
📍 Line 🔖 Check 💬 Message
232 WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information.
377 WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information.
381 WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information.
388 WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information.
📁 classes/suggested-tasks/data-collector/class-unpublished-content.php (1 warning)
📍 Line 🔖 Check 💬 Message
103 WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information.
📁 classes/suggested-tasks/data-collector/class-yoast-orphaned-content.php (1 warning)
📍 Line 🔖 Check 💬 Message
111 PluginCheck.Security.DirectDB.UnescapedDBParameter Unescaped parameter $query used in $wpdb->get_row()\n$query assigned unsafely at line 98.
📁 classes/suggested-tasks/data-collector/class-terms-without-description.php (1 warning)
📍 Line 🔖 Check 💬 Message
108 PluginCheck.Security.DirectDB.UnescapedDBParameter Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 106.
📁 classes/suggested-tasks/data-collector/class-terms-without-posts.php (1 warning)
📍 Line 🔖 Check 💬 Message
120 PluginCheck.Security.DirectDB.UnescapedDBParameter Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 118.
📁 classes/activities/class-query.php (2 warnings)
📍 Line 🔖 Check 💬 Message
71 PluginCheck.Security.DirectDB.UnescapedDBParameter Unescaped parameter $table_name used in $wpdb->query()\n$table_name assigned unsafely at line 58.
163 PluginCheck.Security.DirectDB.UnescapedDBParameter Unescaped parameter $where_args used in $wpdb->get_results()\n$where_args assigned unsafely at line 153.

🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

Three test files conflicted (add/add): both branches had written tests
for select-locale, select-timezone and core-blogdescription. Resolved by
taking develop's versions.

That is not the obvious choice -- this branch's versions were larger and
better designed (they complete a task by setting the real option, where
develop's fake it by inserting a Suggested_Task activity, which is what
produces the "Column 'type' cannot be null" noise in the test output).
But they target complete_task() on the task providers, which develop
removed in the onboarding rework (39a2e0d). All 17 errored.

The same removal broke the feature itself, not just its tests:
Angie_API::complete_task() called $task_provider->complete_task(), a
fatal on every Angie task completion. Ported to the replacement contract.

The providers now apply settings through
handle_interactive_task_submit(), which reads $_POST and terminates with
wp_send_json_*(). A REST callback has to return a response, so that
method cannot be called from one. The update is performed in
Angie_API::update_task_setting() instead, which reuses the provider's
option whitelist (and its filter) rather than bypassing it -- this
endpoint can only write options the interactive tasks could already
write, which the old complete_task() path did not enforce.

get_angie_tasks_map() consequently maps task IDs to option names
(WPLANG, timezone_string) rather than the old per-task argument names.
Timezone handling mirrors Select_Timezone: a UTC+/- value is stored as a
numeric gmt_offset, a named zone as a timezone_string, never both.

Adds 6 tests for the new path: the whitelist rejecting an off-list
option, a whitelisted option being written, non-scalar and empty values
rejected before anything is written, and the two timezone formats plus an
unrecognised one. Verified by mutation -- removing the whitelist, the
scalar guard, the empty guard or the timezone branch each fail tests.

Note the named-timezone test does not assert gmt_offset is emptied:
WordPress core recomputes it from timezone_string, so it reads back as
that zone's offset rather than the '' written here.
A local vendor symlink used to run the test suite in a worktree was swept
into the merge commit by git add -A, which broke the Build plugin step
("Broken symlink at /vendor").
Angie 1.1.12 collects MCP-capable plugins through the angie_mcp_plugins
filter and passes the result to its app as window.angieConfig.plugins.
Progress Planner was not in that list, so the MCP server script this
integration enqueues registered into nothing -- confirmed against a live
install, where the filter returned only ["elementor_pro"].

Add the filter, matching how Angie's own Elementor module registers.

This is unverified beyond the PHP layer. Angie's app is fetched at
runtime from editor-static-bucket.elementor.com, so whether it does
anything with a third-party key cannot be determined from this codebase;
every other entry in that list is published by Elementor. The app also
only enqueues on Angie's own admin screens and behind a consent gate and
an account, none of which are reachable from WP-CLI. Needs a manual pass
with a real Angie account to confirm.
get_tasks_by_status() returned only the three recommendations Angie can
complete itself and silently dropped the rest, so asking Angie to list
tasks on a site with 19 open recommendations reported two -- and on a
site where none of those three happened to be open, it reported none at
all. The endpoint answers "what are my tasks", so it now returns all of
them and marks each with can_complete.

This does not widen what Angie can change: complete_task() enforces the
same map, and can_complete simply tells Angie which ones it can act on.

complete_task() previously fell through to $task->celebrate() for any
task ID that was not in the map, marking the recommendation done without
touching the site it described -- the user would be told their site had
been changed when nothing had. It now returns task_not_completable and
leaves the recommendation open.

Two existing tests asserted the old filtering (one asserted a count of 0
with two tasks present, which is the reported behaviour) and have been
rewritten. Adds a test that a task Angie cannot handle is refused and
stays open.

Verified against a live Angie 1.1.12 install: the endpoint returned 0
tasks before this change on a site with 16 open recommendations, and 19
with 2 completable after.
@ilicfilip

Copy link
Copy Markdown
Collaborator Author

Tested against a live Angie 1.1.12 install

Notes from bringing this branch up to date, since a couple of things here are easy to miss.

It works now, but it needed a registration change

Angie 1.1.12 discovers MCP-capable plugins through the angie_mcp_plugins filter and passes the result to its app as window.angieConfig.plugins. This branch never added itself to that list, so the MCP server script it enqueues registered into nothing — confirmed on a live install, where the filter returned only ["elementor_pro"].

Added in 21ac0b8, matching how Angie's own Elementor module registers. After that, Angie discovers the tools and calls them: the composer shows a Progress Planner badge and the task list renders with action buttons.

The "you have no active tasks" bug

get_tasks_by_status() returned only the three recommendations Angie can complete itself (core-blogdescription, select-locale, select-timezone) and silently dropped everything else.

On a live site that meant:

published tasks in DB:  16
endpoint returned:       0

None of those 16 happened to be one of the three, so Angie reported "You currently have no active tasks in Progress Planner" on a site with 16 open recommendations. Even once two of the three existed, it listed 2 of 19 with no indication the rest were hidden.

Fixed in 8789e42: every recommendation is listed, each carrying a can_complete flag. This does not widen what Angie can change — complete_task() enforces the same map — it just stops the list from lying about what exists.

A second bug found while fixing that one

complete_task() fell through to $task->celebrate() for any task ID not in the map. So asking Angie to complete something it has no handler for (e.g. hello-world) would mark the recommendation done without touching the site it describes — the user is told their site changed when nothing did.

It now returns task_not_completable and leaves the recommendation open.

The provider API had moved out from under this branch

Worth flagging for anyone picking this up: develop's onboarding rework (39a2e0d) removed complete_task() from the task providers. Angie_API::complete_task() still called $task_provider->complete_task(), which is a fatal on every Angie task completion.

The replacement (handle_interactive_task_submit()) reads $_POST and terminates with wp_send_json_*(), so it cannot be called from a REST callback that has to return a response. The update now happens in Angie_API::update_task_setting(), which reuses the provider's option whitelist rather than bypassing it — the old complete_task() path had no such restriction, so this is slightly tighter than before.

Also: three test files (select-locale, select-timezone, core-blogdescription) existed in both branches. This branch's versions were larger and arguably better designed, but all targeted the removed API and errored, so develop's versions were kept.

Still open

  • Path B — the Abilities API. Angie 1.1.12 also exposes tools via wp_register_ability() (its wp-abilities module), which would be consumer-agnostic and could replace the ~500-line TS source and its ~12k built bundle entirely. Not viable to develop locally as things stand: Angie's .org build ships without vendor/, so WP\MCP\Core\McpAdapter never loads and the module stays inactive.
  • Completion flow is unverified end-to-end through Angie. The REST layer is tested (whitelist enforced, timezone mapping correct, real options written), but nobody has driven an actual task completion through the chat UI since the refactor. The original description notes "Set site locale" behaving oddly — worth re-checking.
  • The two root-level docs (AI_TASKS_*.md style working notes) and the committed dist/ bundle are still there.

@ilicfilip

Copy link
Copy Markdown
Collaborator Author

Notes: porting to the Abilities API (Path B)

Recording the design discussion so it isn't lost. Nothing here is implemented — this is what a port would involve if we continue the Angie integration.

Why bother, when the current approach works

The integration works today (see previous comment): Angie discovers the MCP server via angie_mcp_plugins and calls its tools.

The argument for moving is that completion should go through plugin code, not around it. For a side-effect-free task like the tagline, update_option( 'blogdescription', … ) and "completing the task" are literally the same write — so if Angie ever set it directly, the outcome would look identical while proving nothing about the integration. That's a false green, and it matters for the complex tasks where the two aren't equivalent.

Framing the simple tasks as a canary for the integration is the right instinct. It only works if the completion demonstrably routes through our code.

What already exists on this branch

Worth stating, because analysis done against develop will conclude otherwise:

  • A REST completion route existsPOST /progress-planner/v1/angie/recommendations. It's the REST sibling of the admin-AJAX handle_interactive_task_submit(), running the same option allowlist and capability check. develop has no such route, so a survey of develop will report this as missing.
  • Tool discovery works — via the angie_mcp_plugins filter (21ac0b8).

So the "we need a REST equivalent of the AJAX handler" step is already done. What's missing is only the ability registration layer.

What a port would add

Following Angie's own pattern (Mcp_Adapter_Ability_Registration::register_rest_tool() wraps an existing REST route as an ability):

  • progress-planner/list-tasks → wraps the existing GET /angie/recommendations
  • progress-planner/complete-task → wraps the existing POST /angie/recommendations

Both routes already exist and are tested, so this is a registration layer over them rather than new endpoint work.

The payoff is that abilities are consumer-agnostic: they'd serve Angie and any other MCP client, and would let us delete the ~500-line TS source and its ~12k committed dist/ bundle. Angie's app is fetched from a remote bucket, so the current SDK-bundle approach is coupled to a client contract we can't see or pin.

Where to register it

Progress Planner registering its own, guarded by function_exists( 'wp_register_ability' ) — no hard dependency for older WP, and the capability travels with the feature rather than depending on Angie's module layout.

Blocker for local work

Angie's .org build ships without vendor/, so WP\MCP\Core\McpAdapter and WP\MCP\Transport\HttpTransport never load and Wp_Abilities_Support::is_mcp_adapter_supported() returns false — the whole wp-abilities module stays inactive.

Checked on this install:

WP 7.1
wp_register_ability()
WP\MCP\Core\McpAdapter
WP\MCP\Transport\HttpTransport

So this can't be developed or tested locally as-is. Needs either an Angie build with its Composer deps, or wordpress/mcp-adapter pulled in separately.

Separate bug worth fixing regardless

Tasks_Interactive::handle_interactive_task_submit() on develop treats an unchanged value as failure:

$updated = \update_option( $setting, $value );
if ( ! $updated ) {
    \wp_send_json_error( [ 'message' => … 'Failed to update setting.' ] );
}

update_option() returns false when the new value equals the old one, so re-submitting a setting that already matches reports a failure that isn't one. That affects the human popover path today, independently of Angie.

The REST route on this branch doesn't inherit it (it doesn't check the return value), but the two should agree on what "success" means — worth fixing on develop rather than only here.

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.

2 participants