PHPackages                             brunocfalcao/laravel-openclaw-bridge - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. brunocfalcao/laravel-openclaw-bridge

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

brunocfalcao/laravel-openclaw-bridge
====================================

OpenClaw bridge for Laravel — WebSocket client, real-time streaming, CDP screenshots, memory management

1.0.0(4mo ago)5212[1 issues](https://github.com/brunocfalcao/laravel-openclaw-bridge/issues)[1 PRs](https://github.com/brunocfalcao/laravel-openclaw-bridge/pulls)MITPHPPHP ^8.2

Since Feb 15Pushed 4mo agoCompare

[ Source](https://github.com/brunocfalcao/laravel-openclaw-bridge)[ Packagist](https://packagist.org/packages/brunocfalcao/laravel-openclaw-bridge)[ Docs](https://github.com/brunocfalcao/laravel-openclaw-bridge)[ RSS](/packages/brunocfalcao-laravel-openclaw-bridge/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (2)Versions (2)Used By (0)

 [![Laravel 12](https://camo.githubusercontent.com/b9f2a91f69a94b3e0a7e511e67ce864f7007c41c1fbf88decb093c176386ac4d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31322e782d4646324432303f7374796c653d666f722d7468652d6261646765266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/b9f2a91f69a94b3e0a7e511e67ce864f7007c41c1fbf88decb093c176386ac4d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31322e782d4646324432303f7374796c653d666f722d7468652d6261646765266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465) [![PHP 8.2+](https://camo.githubusercontent.com/a23fef6d61ed756ea195ca133e7143504dd8c9c97ac94f525dfafe9f5d836cfb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322b2d3737374242343f7374796c653d666f722d7468652d6261646765266c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/a23fef6d61ed756ea195ca133e7143504dd8c9c97ac94f525dfafe9f5d836cfb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322b2d3737374242343f7374796c653d666f722d7468652d6261646765266c6f676f3d706870266c6f676f436f6c6f723d7768697465) [![MIT License](https://camo.githubusercontent.com/153acf9dff19deb8abfc598c53bac50a4ceae0f5c83a552711060d3d78d2c057/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e3f7374796c653d666f722d7468652d6261646765)](https://camo.githubusercontent.com/153acf9dff19deb8abfc598c53bac50a4ceae0f5c83a552711060d3d78d2c057/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e3f7374796c653d666f722d7468652d6261646765)

Laravel OpenClaw Bridge
=======================

[](#laravel-openclaw-bridge)

**Connect your Laravel app to AI agents with persistent memory, real-time streaming, and browser automation — all through a clean, expressive API.**

Laravel OpenClaw Bridge is a first-party Laravel package that provides a seamless interface to the [OpenClaw](https://openclaw.ai) AI gateway. Send messages to AI agents that remember previous conversations, stream responses in real-time, and automate browser interactions — all with the elegance you expect from a Laravel package.

---

Highlights
----------

[](#highlights)

- **Persistent Memory** — AI agents remember context across multiple calls. Build multi-step workflows where each step builds on the last.
- **Real-Time Streaming** — Stream AI responses token-by-token with event callbacks. Perfect for live UIs and progress indicators.
- **Browser Automation** — Full browser control via Chrome DevTools Protocol. Navigate pages, type into forms, click elements, execute JavaScript, take screenshots — all pure PHP, no Puppeteer or Node.js.
- **Clean Facade API** — `sendMessage()`, `streamMessage()`, and a full `Browser` contract. Everything else is configuration.
- **Proper Architecture** — Interfaces for testability, DTOs for type safety, custom exceptions for precise error handling, and a `StreamEvent` enum instead of magic strings.
- **Zero Lock-In** — Standard WebSocket protocol, environment-based config, and Laravel conventions throughout.

---

Installation
------------

[](#installation)

```
composer require brunocfalcao/laravel-openclaw-bridge
```

Publish the configuration file:

```
php artisan vendor:publish --tag=oc-bridge-config
```

Add your credentials to `.env`:

```
OC_GATEWAY_URL=ws://127.0.0.1:18789
OC_GATEWAY_TOKEN=your-gateway-token
```

That's it. The service provider and facade are auto-discovered.

---

Quick Start
-----------

[](#quick-start)

```
use Brunocfalcao\OCBridge\Facades\OcBridge;

// Send a message and get a response
$result = OcBridge::sendMessage('What are the key trends in the SaaS market?');

echo $result->text;
```

---

Sending Messages
----------------

[](#sending-messages)

The `sendMessage` method sends a prompt to an OpenClaw agent and returns the complete response.

```
$result = OcBridge::sendMessage(
    message: 'Analyze the competitive landscape for food delivery apps',
);

$result->text;       // The agent's full response
$result->sessionKey; // The session key used (for logging/debugging)
```

The response is a `GatewayResponse` DTO — a readonly object with typed properties, full IDE autocomplete, and no guessing.

You can also inject the `Gateway` interface directly instead of using the facade:

```
use Brunocfalcao\OCBridge\Contracts\Gateway;

class MarketAnalysisController extends Controller
{
    public function analyze(Gateway $gateway): JsonResponse
    {
        $result = $gateway->sendMessage('Analyze the SaaS market');

        return response()->json(['analysis' => $result->text]);
    }
}
```

### Routing to Different Agents

[](#routing-to-different-agents)

OpenClaw can host multiple specialized agents. Route your message to any of them:

```
// Uses the default agent (configured in OC_DEFAULT_AGENT)
$result = OcBridge::sendMessage('General analysis request');

// Route to a specific agent
$result = OcBridge::sendMessage(
    message: 'Analyze customer sentiment from these reviews',
    agentId: 'sentiment-analyzer',
);
```

---

Persistent Memory
-----------------

[](#persistent-memory)

This is where it gets powerful. Pass a `memoryId` and the agent **remembers everything** from previous calls with that same ID. No re-sending context. No token waste. The agent simply knows.

```
use Illuminate\Support\Str;

$memoryId = Str::uuid()->toString();

// Step 1 — The agent learns about your market
$step1 = OcBridge::sendMessage(
    message: 'The market is online pet food in Europe. What is the market size?',
    memoryId: $memoryId,
);

// Step 2 — The agent already knows the market. Just ask the next question.
$step2 = OcBridge::sendMessage(
    message: 'Who are the top 5 competitors?',
    memoryId: $memoryId,
);

// Step 3 — The agent has full context from Steps 1 and 2
$step3 = OcBridge::sendMessage(
    message: 'Based on the market size and competitors, what pricing strategy do you recommend?',
    memoryId: $memoryId,
);
```

### How It Works Under the Hood

[](#how-it-works-under-the-hood)

Each `memoryId` maps to a unique session on the OpenClaw gateway:

```
agent:{agentId}:{prefix}-{memoryId}

```

All messages sharing the same `memoryId` connect to the **same session**. The agent maintains full conversational context — no re-prompting, no context windows to manage, no retrieval hacks.

### Building Multi-Step Workflows

[](#building-multi-step-workflows)

Memory makes it trivial to build workflows where each step depends on the last:

```
$memoryId = Str::uuid()->toString();

$chapters = [
    'Analyze the market size and growth trajectory',
    'Map the competitive landscape',
    'Profile the ideal customer segments',
    'Identify market entry opportunities',
    'Develop a go-to-market strategy',
    'Synthesize everything into an executive summary',
];

$results = [];

foreach ($chapters as $prompt) {
    $results[] = OcBridge::sendMessage($prompt, $memoryId);
}

// The final chapter references findings from ALL previous chapters
// because the agent has full memory of the entire conversation.
```

---

Real-Time Streaming
-------------------

[](#real-time-streaming)

For live UIs, progress indicators, or long-running analysis, stream the response token-by-token instead of waiting for the full result.

The `$onEvent` callback receives a `StreamEvent` enum — no magic strings, full IDE autocomplete:

```
use Brunocfalcao\OCBridge\Enums\StreamEvent;

OcBridge::streamMessage(
    message: 'Write a comprehensive market analysis for electric vehicles',
    memoryId: $memoryId,
    onEvent: function (StreamEvent $type, array $data) {
        match ($type) {
            StreamEvent::Delta    => echo $data['delta'],           // Each new token
            StreamEvent::Complete => saveToDatabase($data['text']), // Full response
            StreamEvent::Error    => Log::error($data['message']),  // Something went wrong
        };
    },
);
```

### With Keepalive Callbacks

[](#with-keepalive-callbacks)

For very long responses, pass an `onIdle` callback to keep connections alive:

```
OcBridge::streamMessage(
    message: $prompt,
    memoryId: $memoryId,
    onEvent: function (StreamEvent $type, array $data) {
        if ($type === StreamEvent::Delta) {
            broadcast(new TokenReceived($data['delta']));
        }
    },
    onIdle: function () {
        // Called periodically while waiting for the next token.
        // Use it for heartbeats, progress bars, or connection keepalives.
        logger()->debug('Still processing...');
    },
);
```

### Event Types

[](#event-types)

EventPayloadWhen`StreamEvent::Delta``['delta' => string, 'text' => string]`Each new token arrives`StreamEvent::Complete``['text' => string, 'session_key' => string]`Response is finished`StreamEvent::Error``['message' => string]`Something went wrong---

Browser Automation
------------------

[](#browser-automation)

Full browser control via Chrome DevTools Protocol — entirely in PHP, no Node.js or Puppeteer dependencies. Navigate pages, interact with elements, execute JavaScript, and take screenshots.

```
use Brunocfalcao\OCBridge\Contracts\Browser;

$browser = app(Browser::class);

$browser->open('https://example.com');
$browser->screenshot('/path/to/screenshot.png');
$browser->close();
```

### Full API

[](#full-api)

```
$browser = app(Browser::class);

// Check if Chrome is running
if ($browser->testConnection()) {

    // Open a URL (returns the browser tab ID)
    $tabId = $browser->open('https://example.com');

    // Navigate to a different page in the same tab
    $browser->navigate('https://example.com/pricing');

    // Full-page screenshot saved to disk
    $browser->screenshot('/tmp/full-page.png');

    // Viewport-only screenshot (no scrolling)
    $browser->screenshot('/tmp/viewport.png', fullPage: false);

    // Get raw base64 PNG data (no file saved)
    $base64 = $browser->screenshot();

    // Clean up
    $browser->close();
}
```

### Page Interaction

[](#page-interaction)

Type into form fields, click buttons, and wait for elements — enabling automated workflows on any web page.

```
$browser->open('https://app.example.com/login');

// Wait for the login form to render
$browser->waitForSelector('#email');

// Fill in credentials and submit
$browser->type('#email', 'user@example.com');
$browser->type('#password', 'secret');
$browser->click('#login-button');

// Wait for the dashboard to load
$found = $browser->waitForSelector('.dashboard', timeoutSeconds: 10);

if ($found) {
    $browser->screenshot('/tmp/dashboard.png');
}

$browser->close();
```

### JavaScript Evaluation

[](#javascript-evaluation)

Execute arbitrary JavaScript in the page context — useful for reading page state, triggering client-side actions, or extracting data.

```
$browser->open('https://example.com');

// Read a value from the page
$title = $browser->evaluateJavaScript('document.title');

// Get the full HTML content of the page
$html = $browser->getContent();

// Trigger a client-side action
$browser->evaluateJavaScript('window.scrollTo(0, document.body.scrollHeight)');

// Async expressions are supported (awaitPromise is enabled)
$data = $browser->evaluateJavaScript('fetch("/api/status").then(r => r.json())');

$browser->close();
```

### Page Ready Detection

[](#page-ready-detection)

The `waitForPageReady()` method waits for `document.readyState === 'complete'` and for Alpine.js to finish initializing (if present). It is called automatically after `open()` and `navigate()`, but you can call it manually after client-side navigation.

```
$browser->click('#spa-link');
$browser->waitForPageReady(timeoutSeconds: 15);
$browser->screenshot('/tmp/after-navigation.png');
```

### Method Reference

[](#method-reference)

MethodReturnsDescription`open(string $url)``string`Open a URL in a browser tab (reuses tabs on the same domain). Returns the tab ID.`navigate(string $url)``void`Navigate the current tab to a different URL.`screenshot(?string $path, bool $fullPage)``string`Capture a screenshot. Returns file path or base64 data.`type(string $selector, string $text)``void`Type text into a form element (clears existing content first).`click(string $selector)``void`Click an element by CSS selector.`waitForSelector(string $selector, int $timeoutSeconds)``bool`Wait for an element to appear in the DOM. Returns false on timeout.`getContent()``string`Get the full HTML content of the current page.`evaluateJavaScript(string $expression)``mixed`Evaluate a JS expression in the page context (supports async).`waitForPageReady(int $timeoutSeconds)``void`Wait for page load and Alpine.js initialization.`testConnection()``bool`Check if headless Chrome is running and reachable.`close()``void`Close the current browser tab and release resources.### Smart Tab Reuse

[](#smart-tab-reuse)

The browser service intelligently manages tabs. If you open multiple URLs on the same domain, it reuses the existing tab instead of creating new ones — reducing memory and overhead.

```
$browser->open('https://competitor.com');          // Opens new tab
$browser->open('https://competitor.com/pricing');   // Reuses same tab, navigates
$browser->open('https://other-site.com');           // Opens new tab (different domain)
```

### Prerequisites

[](#prerequisites)

Browser automation requires headless Chrome running with remote debugging enabled:

```
google-chrome --headless --remote-debugging-port=9222 --no-sandbox
```

Then set the endpoint in your `.env`:

```
OC_BROWSER_URL=http://127.0.0.1:9222
```

---

Configuration
-------------

[](#configuration)

All configuration lives in `config/oc-bridge.php` and is driven by environment variables:

```
# Gateway connection
OC_GATEWAY_URL=ws://127.0.0.1:18789    # WebSocket endpoint
OC_GATEWAY_TOKEN=your-token             # Authentication token
OC_GATEWAY_TIMEOUT=600                  # Response timeout in seconds (default: 10 min)

# Session management
OC_SESSION_PREFIX=my-app                # Namespace to isolate your app's sessions
OC_DEFAULT_AGENT=main                   # Default agent to route messages to

# Browser automation (optional)
OC_BROWSER_URL=http://127.0.0.1:9222   # Chrome DevTools Protocol endpoint
```

VariableDefaultDescription`OC_GATEWAY_URL``ws://127.0.0.1:18789`WebSocket endpoint for the OpenClaw gateway`OC_GATEWAY_TOKEN`—Your authentication token`OC_GATEWAY_TIMEOUT``600`Max seconds to wait for a response`OC_SESSION_PREFIX``laravel`Prefix for session keys (isolates your app)`OC_DEFAULT_AGENT``main`Agent ID used when none is specified`OC_BROWSER_URL``http://127.0.0.1:9222`Chrome DevTools Protocol endpoint---

Installation Wizard
-------------------

[](#installation-wizard)

Run the interactive installer to set up everything automatically:

```
php artisan oc-bridge:install
```

The installer will:

1. **Pre-flight checks** — PHP version, Laravel version, database connection
2. **Publish config** — copies `config/oc-bridge.php` to your project
3. **Validate environment** — checks for `OC_GATEWAY_TOKEN` and auto-detects it from `~/.openclaw/openclaw.json` if missing
4. **Install Chrome** — offers to install Chromium and create a systemd service for headless mode
5. **Connectivity check** — verifies the OpenClaw gateway is reachable
6. **Smoke test** — sends a test message to the agent and confirms a response

For non-interactive environments (CI/CD), use `--no-interaction` — the installer will skip prompts and report warnings instead.

---

CLI Testing
-----------

[](#cli-testing)

The package ships with an artisan command to quickly test your gateway connection from the terminal:

```
# Send a message to the default agent
php artisan oc-bridge:message "Hello, are you there?"

# Route to a specific agent
php artisan oc-bridge:message "Analyze this dataset" --agent=research
```

The command streams the response in real-time to stdout — useful for verifying connectivity, debugging agent behavior, or testing new agents.

---

Protocol
--------

[](#protocol)

The bridge implements OpenClaw's WebSocket protocol v3. Every request follows this flow:

```
┌─────────────┐         ┌──────────────────┐
│  Your App   │         │  OpenClaw Gateway │
└──────┬──────┘         └────────┬─────────┘
       │                         │
       │   1. Connect (WS)       │
       │────────────────────────>│
       │                         │
       │   2. Nonce challenge    │
       ││
       │                         │
       │   4. ACK                │
       ││
       │                         │
       │   6. Stream deltas      │
       │
