Add test coverage for externally-driven execution (setLooped(FALSE))
## Summary
Add kernel-test coverage for `setLooped(FALSE)` — the mode where the agent parks its tool call and returns after one decision, leaving the caller to drive the next step. The behavior exists and is relied upon, but is untested.
## Why
`setLooped(FALSE)` lets a caller run an agent one decision at a time instead of looping to completion inside one request. Canvas's client-side orchestration loop depends entirely on this. Because it is undocumented and uncovered, an unrelated change could silently break every externally-driven consumer. Locking it with tests protects the contract.
- Roadmap: https://git.drupalcode.org/project/canvas/-/work_items/3591777
- Consumer (POC): https://git.drupalcode.org/project/canvas/-/merge_requests/1214
## Current behavior (uncovered)
- `loopedEnabled` defaults to `TRUE` (`src/PluginBase/AiAgentEntityWrapper.php:154`); `setLooped()` toggles it (line 377).
- With tools present and `loopedEnabled === FALSE`, `determineSolvability()` does not recurse (the `if ($this->loopedEnabled)` guards at lines 692 and 701) — the tool calls stay parked in `$this->contextTools[]` and control returns to the caller.
- Loop state round-trips through `toArray()` / `fromArray()` (`looped`, `looped_enabled` at lines 1544–1545 / 1625–1626) so a caller can serialize and resume.
- `AiAgentEntityWrapperDetermineSolvabilityTest` exercises only the default looping paths and event dispatch — nothing sets `setLooped(FALSE)`.
## Tests to add
- After a tool-calling response with `setLooped(FALSE)`: the tool is parked in `contextTools`, `determineSolvability()` does not recurse, and it returns; `isFinished()` is `FALSE`.
- A text-only response (no tool call) with `setLooped(FALSE)`: `isFinished()` becomes `TRUE` (terminal step).
- State round-trip: `fromArray(toArray())` preserves `looped` and `looped_enabled`, and a resumed wrapper continues where it left off.
---
_Issue generated with AI assistance._
issue