feat(AI): #3592032 Canvas AI: Keep the agent history across chat turns in the backend

## Problem

Each dev-chat turn ran a fresh agent. The stored agent state was deleted when a turn finished, and the next turn was seeded from the client transcript, which carries text only. The agent lost every tool call and tool result of the conversation and repeated work, e.g. re-reading component schemas before every follow-up edit.

## Fix

- `AiWizardDev` generates one `conversation_id` per mount (the point where the chat history is cleared today) and sends it with every request, next to the per-turn `request_id`.

- `CanvasAiTempStore` gets a second key space, `conversation_state_{id}`.

- `CanvasDevAiBuilder`: a turn that ends cleanly writes the agent state under the conversation id. A new turn with no in-turn state restores that state with the loop counter reset to 0, then sets the new message as chat input. The counter drives three things in `AiAgentEntityWrapper` (chat input appended on loop 1 only, status thread started at loop 0 only, `max_loops`), so keeping it would make a resumed turn ignore the message, emit no narration and count loops across the conversation.

- A turn that fails or gives up (`JOB_NOT_SOLVABLE`) forgets both keys, so the next turn seeds from the transcript again rather than from a history that stops before the failed turn. A state still holding a parked tool call is never kept, so a resumed turn cannot run something an earlier turn never ran.

- Requests without a `conversation_id` behave exactly as before.

## Tests

- `CanvasDevPageBuilderAgentEndToEndTest::testSecondTurnResumesTheHistoryOfTheFirst`: red before the fix (turn 2 reached the model with roles `user, assistant, user, user`), green after. Turn 2 runs two real hops against two new echoai fixtures and asserts the seven-message history the model receives, including turn 1's `edit_components` call and its result.

- `CanvasAiDevControllerTest`: five new mocked-agent tests (clean end keeps the state, parked tool call is not kept, no `conversation_id` keeps nothing, first turn seeds from the transcript, later turn resumes with `looped` reset); the failure and not-solvable tests now also assert the conversation state is dropped.

- `aiDev.spec.ts`: all requests of one chat carry the same `conversation_id`.

- Checked by hand in the browser with the dev page builder agent on a Gemini model: before the change the second message re-fetched the component details it had fetched one message earlier; after it, the second message went straight to the placement/edit call.

## Open points (see the issue)

1. `get_component_context` runs on `available_on_loop: [1]`, so every turn appends another copy of the catalog to the kept history. The new fixtures pin this; deduplicating would be a small follow-up.

2. `chat_history_max_messages` no longer bounds anything once the backend keeps the history, so growth is unbounded for now.

Merge request reports

Loading