# Virtual Webhook Toy

A reference extension for AgenticLover API v7. The AI sends commands to a local HTTP server. The chat panel shows the request, response, current state, and the last eight events. No hardware is required.

The server records levels and timed patterns. It does not generate vibration waveforms or connect to a physical toy.

## Run it

1. Install Node.js 18 or later. No npm packages are required.
2. Put these four files in `devextensions/virtual-webhook-toy/`:
   - `manifest.json`
   - `virtual-webhook-toy.js`
   - `virtual-toy-server.mjs`
   - `README.md`
3. From the folder containing `devextensions`, run:

   ```sh
   node devextensions/virtual-webhook-toy/virtual-toy-server.mjs
   ```

   The server prints `listening on http://localhost:4400`. Keep this terminal open.
4. In AgenticLover, open Settings → Advanced. Turn on Developer Mode and Show developer features.
5. Open Dev Extensions. Pick the parent `devextensions` folder, scan, and install `virtual-webhook-toy`. Unload any existing dev extension first.
6. Open a chat and show the extension panel. Expand Virtual Webhook Toy and click Ping. The status changes to Idle.
7. Ask a model that supports tool calls: "Use toy_set_level to set the virtual toy to level 4."

The chat shows "Adjusted the toy". The panel shows level 4, the request JSON, and the server response. The server prints `steady at level 4`.

Dev extensions are enabled for every persona while installed. Unload this example when you finish. After editing the module, use Reload in Dev Extensions.

## Check the integration

- Ask for level 10. With the default Maximum level of 7, the server receives 7 and the tool result reports the clamp.
- Ask for a pulse at level 5 for 5 seconds. The server stops it when the timer ends. While open, the panel polls every two seconds and returns to Idle.
- Start another pattern and click the panel Stop button. Repeat with the top-bar Emergency Stop. Both send a stop command to the server.
- Ask the AI to stop to exercise `toy_stop`.
- Stop the server and ask for another command. The chat and panel show the failure. Restart the server and click Ping to reconnect.

Requests time out after five seconds. Failed emergency stops reject so the host can report the failure. The panel shows the last known state until the next successful request.

## Settings and local access

The extension sends requests from the app, not the AgenticLover backend. Run the server on the same computer as the app when using `localhost`.

Set `TOY_PORT` to change the port, then update Toy URL in the extension settings. Set `TOY_TOKEN` to require a bearer token, then enter it in Bearer token. For example, in a POSIX shell:

```sh
TOY_PORT=4401 TOY_TOKEN=example-token node devextensions/virtual-webhook-toy/virtual-toy-server.mjs
```

The server allows cross-origin requests for local development. Browser local network permissions can still block requests. If Ping stays disconnected, check the server terminal, Toy URL, token, and browser console.

## Tools

| Tool | Arguments | Result |
| --- | --- | --- |
| `toy_set_level` | `level`, 0 to 10 | Sets steady output. Zero stops the toy. |
| `toy_play_pattern` | `pattern`, `level`, `seconds` | Starts pulse, wave, or ramp for 5 to 120 seconds. |
| `toy_stop` | none | Stops output and clears the pattern timer. |

The module clamps levels to Maximum level, which defaults to 7. Tool results include the server message, a state description, and a clamp explanation when needed. Successful level and pattern tools also show a toast.

The persona receives a Tool Guidance contribution and a Current Toys entry. Settings come from `resolvedSettings` in hooks and the panel because `executeTool` does not receive `extensionState`. The hook and panel subscribe to module state through `useSyncExternalStore`.

## HTTP contract

```text
GET  /state
POST /command   { "action": "set_level", "level": 6 }
                { "action": "play_pattern", "pattern": "pulse", "level": 5, "seconds": 30 }
                { "action": "stop" }
```

Successful commands return HTTP 200 with this shape:

```json
{
	"ok": true,
	"toy": {
		"power": true,
		"level": 6,
		"pattern": null,
		"until": null,
		"updatedAt": "2026-09-25T00:00:00.000Z"
	},
	"message": "steady at level 6"
}
```

`GET /state` returns `{ ok: true, toy, history }`. History contains up to 20 entries with `at` and `message`. Timed patterns set `until` to an ISO timestamp. When a pattern ends, the server clears `pattern` and `until`, sets `power` to false, and sets `level` to zero.

The server returns `{ ok: false, error }` with HTTP 400 for invalid commands, 401 for a missing or incorrect configured token, and 404 for an unknown route. It answers OPTIONS requests for browser CORS checks.

The server accepts pattern durations of 1 to 600 seconds. The extension restricts the AI tools to 5 to 120 seconds. Maximum level is an extension setting, not a server restriction. Direct HTTP clients can set levels up to 10.

## Source and checks

The developer docs at `/docs/developer/example#virtual-webhook-toy` display and offer downloads of these same files. The docs build imports the source directly.

In a repository checkout, run the focused checks from this folder:

```sh
node --test virtual-webhook-toy.test.mjs
```
