diff options
| author | soryu <soryu@soryu.co> | 2026-02-22 14:39:14 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-22 14:39:14 +0000 |
| commit | 6a34a6f3c423a7c57616762eb4cea2b7da52eaf3 (patch) | |
| tree | 7c596eac896918466e7ef3f149b02333fef09212 | |
| parent | 0523765af84492640928d571f481e17b26008b13 (diff) | |
| download | soryu-6a34a6f3c423a7c57616762eb4cea2b7da52eaf3.tar.gz soryu-6a34a6f3c423a7c57616762eb4cea2b7da52eaf3.zip | |
feat: Add daemon page with download binary and Cloudflare Agent setup (#77)
* feat: soryu-co/soryu - makima: Create DaemonList and DaemonDetail page components
* feat: soryu-co/soryu - makima: Add daemon page routes, CSS styles, and navigation
* feat: soryu-co/soryu - makima: Create daemon page with download and monitoring
* WIP: heartbeat checkpoint
* WIP: heartbeat checkpoint
* feat: soryu-co/soryu - makima: Integrate Cloudflare Agent setup into daemon page
| -rw-r--r-- | frontend/src/components/DaemonDetail.tsx | 229 | ||||
| -rw-r--r-- | frontend/src/components/DaemonList.tsx | 125 | ||||
| -rw-r--r-- | frontend/src/components/VNInterface.tsx | 13 | ||||
| -rw-r--r-- | frontend/src/main.tsx | 10 | ||||
| -rw-r--r-- | frontend/src/styles/pc98.css | 59 | ||||
| -rw-r--r-- | makima/cloudflare-agent/.gitignore | 4 | ||||
| -rw-r--r-- | makima/cloudflare-agent/README.md | 237 | ||||
| -rw-r--r-- | makima/cloudflare-agent/package-lock.json | 2802 | ||||
| -rw-r--r-- | makima/cloudflare-agent/package.json | 22 | ||||
| -rwxr-xr-x | makima/cloudflare-agent/setup.sh | 331 | ||||
| -rw-r--r-- | makima/cloudflare-agent/src/agent.ts | 773 | ||||
| -rw-r--r-- | makima/cloudflare-agent/src/index.ts | 47 | ||||
| -rw-r--r-- | makima/cloudflare-agent/src/types.ts | 148 | ||||
| -rw-r--r-- | makima/cloudflare-agent/tsconfig.json | 18 | ||||
| -rw-r--r-- | makima/cloudflare-agent/wrangler.toml | 20 | ||||
| -rw-r--r-- | makima/frontend/src/components/NavStrip.tsx | 1 | ||||
| -rw-r--r-- | makima/frontend/src/hooks/useMultiTaskSubscription.ts | 84 | ||||
| -rw-r--r-- | makima/frontend/src/main.tsx | 9 | ||||
| -rw-r--r-- | makima/frontend/src/routes/daemon.tsx | 746 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 |
20 files changed, 5595 insertions, 85 deletions
diff --git a/frontend/src/components/DaemonDetail.tsx b/frontend/src/components/DaemonDetail.tsx new file mode 100644 index 0000000..2de0997 --- /dev/null +++ b/frontend/src/components/DaemonDetail.tsx @@ -0,0 +1,229 @@ +import React, { useEffect, useState } from 'react' +import { useParams, Link } from 'react-router-dom' + +interface Daemon { + id: string + ownerId: string + connectionId: string + hostname: string | null + machineId: string | null + maxConcurrentTasks: number + currentTaskCount: number + status: string + lastHeartbeatAt: string + connectedAt: string + disconnectedAt: string | null +} + +interface DaemonDirectory { + path: string + label: string + directoryType: string + hostname: string | null + exists?: boolean +} + +type Tab = 'overview' | 'directories' + +function statusIndicator(status: string): { color: string; label: string } { + switch (status.toLowerCase()) { + case 'connected': + return { color: 'green', label: 'Connected' } + case 'disconnected': + return { color: 'red', label: 'Disconnected' } + case 'unhealthy': + return { color: 'yellow', label: 'Unhealthy' } + default: + return { color: 'gray', label: status } + } +} + +export function DaemonDetail() { + const { id } = useParams<{ id: string }>() + const [daemon, setDaemon] = useState<Daemon | null>(null) + const [directories, setDirectories] = useState<DaemonDirectory[]>([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState<string | null>(null) + const [activeTab, setActiveTab] = useState<Tab>('overview') + + useEffect(() => { + async function fetchDaemon() { + if (!id) return + + try { + setLoading(true) + const [daemonRes, dirRes] = await Promise.all([ + fetch(`/api/v1/mesh/daemons/${id}`), + fetch('/api/v1/mesh/daemons/directories'), + ]) + + if (!daemonRes.ok) { + throw new Error(`Failed to fetch daemon: ${daemonRes.statusText}`) + } + + const daemonData = await daemonRes.json() + setDaemon(daemonData) + + if (dirRes.ok) { + const dirData = await dirRes.json() + setDirectories(dirData.directories || []) + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error') + } finally { + setLoading(false) + } + } + + fetchDaemon() + + const interval = setInterval(async () => { + if (!id) return + try { + const response = await fetch(`/api/v1/mesh/daemons/${id}`) + if (response.ok) { + const daemonData = await response.json() + setDaemon(daemonData) + } + } catch { + // Silently ignore refresh errors + } + }, 10000) + + return () => clearInterval(interval) + }, [id]) + + if (loading) { + return ( + <div className="daemon-detail-container"> + <div className="loading">Loading daemon...</div> + </div> + ) + } + + if (error) { + return ( + <div className="daemon-detail-container"> + <div className="error">Error: {error}</div> + <Link to="/daemons" className="back-link"> + Back to Daemons + </Link> + </div> + ) + } + + if (!daemon) { + return ( + <div className="daemon-detail-container"> + <div className="not-found">Daemon not found</div> + <Link to="/daemons" className="back-link"> + Back to Daemons + </Link> + </div> + ) + } + + const status = statusIndicator(daemon.status) + const filteredDirectories = directories.filter( + (dir) => dir.hostname === daemon.hostname + ) + + return ( + <div className="daemon-detail-container"> + <div className="daemon-detail-header"> + <Link to="/daemons" className="back-link"> + Back to Daemons + </Link> + <h1 className="daemon-title">{daemon.hostname || 'Unknown Host'}</h1> + <div className="daemon-meta"> + <span> + <span + className="status-dot" + style={{ backgroundColor: status.color }} + /> + {status.label} + </span> + <span> + Tasks: {daemon.currentTaskCount} / {daemon.maxConcurrentTasks} + </span> + </div> + </div> + + <div className="daemon-tabs"> + <button + className={`tab-button ${activeTab === 'overview' ? 'active' : ''}`} + onClick={() => setActiveTab('overview')} + > + Overview + </button> + <button + className={`tab-button ${activeTab === 'directories' ? 'active' : ''}`} + onClick={() => setActiveTab('directories')} + > + Directories ({filteredDirectories.length}) + </button> + </div> + + <div className="daemon-tab-content"> + {activeTab === 'overview' && ( + <div className="tab-panel"> + <h2>Daemon Overview</h2> + <dl className="overview-list"> + <dt>ID</dt> + <dd>{daemon.id}</dd> + <dt>Hostname</dt> + <dd>{daemon.hostname || 'N/A'}</dd> + <dt>Machine ID</dt> + <dd>{daemon.machineId || 'N/A'}</dd> + <dt>Status</dt> + <dd> + <span + className="status-dot" + style={{ backgroundColor: status.color }} + /> + {status.label} + </dd> + <dt>Connection ID</dt> + <dd>{daemon.connectionId}</dd> + <dt>Max Concurrent Tasks</dt> + <dd>{daemon.maxConcurrentTasks}</dd> + <dt>Current Task Count</dt> + <dd>{daemon.currentTaskCount}</dd> + <dt>Connected At</dt> + <dd>{new Date(daemon.connectedAt).toLocaleString()}</dd> + <dt>Last Heartbeat At</dt> + <dd>{new Date(daemon.lastHeartbeatAt).toLocaleString()}</dd> + <dt>Disconnected At</dt> + <dd> + {daemon.disconnectedAt + ? new Date(daemon.disconnectedAt).toLocaleString() + : 'N/A'} + </dd> + </dl> + </div> + )} + + {activeTab === 'directories' && ( + <div className="tab-panel"> + <h2>Directories</h2> + {filteredDirectories.length === 0 ? ( + <p>No directories found for this daemon</p> + ) : ( + <ul className="directory-list"> + {filteredDirectories.map((dir, index) => ( + <li key={`${dir.path}-${index}`} className="directory-item"> + <h3>{dir.label}</h3> + <div className="daemon-meta"> + <span>Path: {dir.path}</span> + <span>Type: {dir.directoryType}</span> + </div> + </li> + ))} + </ul> + )} + </div> + )} + </div> + </div> + ) +} diff --git a/frontend/src/components/DaemonList.tsx b/frontend/src/components/DaemonList.tsx new file mode 100644 index 0000000..215c790 --- /dev/null +++ b/frontend/src/components/DaemonList.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' + +interface DaemonSummary { + id: string + ownerId: string + connectionId: string + hostname: string | null + machineId: string | null + maxConcurrentTasks: number + currentTaskCount: number + status: string + lastHeartbeatAt: string + connectedAt: string + disconnectedAt: string | null +} + +function statusDotColor(status: string): string { + switch (status.toLowerCase()) { + case 'connected': + return 'green' + case 'disconnected': + return 'red' + case 'unhealthy': + return 'yellow' + default: + return 'gray' + } +} + +export function DaemonList() { + const [daemons, setDaemons] = useState<DaemonSummary[]>([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState<string | null>(null) + + useEffect(() => { + async function fetchDaemons() { + try { + setLoading(true) + const response = await fetch('/api/v1/mesh/daemons') + if (!response.ok) { + throw new Error(`Failed to fetch daemons: ${response.statusText}`) + } + const data = await response.json() + setDaemons(data.daemons || []) + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error') + } finally { + setLoading(false) + } + } + + fetchDaemons() + + const interval = setInterval(async () => { + try { + const response = await fetch('/api/v1/mesh/daemons') + if (response.ok) { + const data = await response.json() + setDaemons(data.daemons || []) + } + } catch { + // Silently ignore refresh errors + } + }, 10000) + + return () => clearInterval(interval) + }, []) + + if (loading) { + return ( + <div className="daemon-list-container"> + <div className="loading">Loading daemons...</div> + </div> + ) + } + + if (error) { + return ( + <div className="daemon-list-container"> + <div className="error">Error: {error}</div> + </div> + ) + } + + return ( + <div className="daemon-list-container"> + <Link to="/" className="back-link"> + Back to Home + </Link> + <h1>Daemons</h1> + {daemons.length === 0 ? ( + <p>No daemons found</p> + ) : ( + <ul className="daemon-list"> + {daemons.map((daemon) => ( + <li key={daemon.id} className="daemon-item"> + <Link to={`/daemons/${daemon.id}`}> + <h2>{daemon.hostname || 'Unknown Host'}</h2> + <div className="daemon-status"> + <span + className="status-dot" + style={{ backgroundColor: statusDotColor(daemon.status) }} + /> + <span>{daemon.status}</span> + </div> + <div className="daemon-meta"> + <span> + Tasks: {daemon.currentTaskCount} / {daemon.maxConcurrentTasks} + </span> + <span> + Connected: {new Date(daemon.connectedAt).toLocaleString()} + </span> + {daemon.machineId && ( + <span>Machine: {daemon.machineId}</span> + )} + </div> + </Link> + </li> + ))} + </ul> + )} + </div> + ) +} diff --git a/frontend/src/components/VNInterface.tsx b/frontend/src/components/VNInterface.tsx index be71d27..318a9b9 100644 --- a/frontend/src/components/VNInterface.tsx +++ b/frontend/src/components/VNInterface.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react' +import { Link } from 'react-router-dom' import { useStore } from '@nanostores/react' import { isStandbyStore, @@ -100,6 +101,18 @@ export function VNInterface({ onLogout }: VNInterfaceProps) { {isStandby ? 'STDBY' : 'LIVE'} </span> </div> + <div className="status-item"> + <Link to="/daemons" style={{ color: '#66ccff', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '4px' }}> + <span className="info-label">Mesh:</span> + <span className="info-value">Daemons</span> + </Link> + </div> + <div className="status-item"> + <Link to="/contracts" style={{ color: '#66ccff', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '4px' }}> + <span className="info-label">View:</span> + <span className="info-value">Contracts</span> + </Link> + </div> </div> </div> </div> diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index a6eae5b..04b8cde 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,8 @@ import App from './App' import { ContractList } from './components/ContractList' import { ContractDetail } from './components/ContractDetail' import { FileDetail } from './components/FileDetail' +import { DaemonList } from './components/DaemonList' +import { DaemonDetail } from './components/DaemonDetail' import './styles/pc98.css' import './styles/mobile.css' @@ -33,6 +35,14 @@ const router = createBrowserRouter([ path: '/contracts/:contractId/files/:fileId', element: <FileDetail />, }, + { + path: '/daemons', + element: <DaemonList />, + }, + { + path: '/daemons/:id', + element: <DaemonDetail />, + }, ]) ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/frontend/src/styles/pc98.css b/frontend/src/styles/pc98.css index 4dcf15e..7ec0d1c 100644 --- a/frontend/src/styles/pc98.css +++ b/frontend/src/styles/pc98.css @@ -4621,3 +4621,62 @@ button:focus-visible { display: none; } } + +/* ===== Daemon Pages ===== */ + +.daemon-list-container { max-width: 900px; margin: 0 auto; padding: 40px 20px; font-family: 'MS Gothic', monospace; min-height: 100vh; background: var(--bg-gradient); } +.daemon-list-container h1 { font-family: 'MS Gothic', monospace; font-size: 24px; color: #66ccff; text-transform: uppercase; letter-spacing: 2px; margin: 0 0 30px 0; border-bottom: 2px solid rgba(102, 204, 255, 0.3); padding-bottom: 10px; } +.daemon-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 12px; } +.daemon-item { background: rgba(0, 0, 0, 0.6); border: 1px solid rgba(102, 204, 255, 0.3); padding: 0; transition: all 0.2s ease; } +.daemon-item:hover { border-color: #66ccff; background: rgba(0, 0, 51, 0.8); box-shadow: 0 0 15px rgba(102, 204, 255, 0.15); } +.daemon-item a { display: block; padding: 16px 20px; color: inherit; text-decoration: none; } +.daemon-item h2 { font-family: 'MS Gothic', monospace; font-size: 16px; color: #ffffff; margin: 0 0 8px 0; display: flex; align-items: center; gap: 10px; } +.daemon-meta { display: flex; gap: 20px; flex-wrap: wrap; font-size: 12px; color: rgba(255, 255, 255, 0.6); } +.daemon-meta span { display: flex; align-items: center; gap: 4px; } + +/* Status indicators */ +.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; } +.status-dot.connected { background: #00ff88; box-shadow: 0 0 6px rgba(0, 255, 136, 0.6); } +.status-dot.disconnected { background: #ff4466; box-shadow: 0 0 6px rgba(255, 68, 102, 0.6); } +.status-dot.unhealthy { background: #ffcc66; box-shadow: 0 0 6px rgba(255, 204, 102, 0.6); } +.daemon-status { display: flex; align-items: center; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; } +.daemon-status.connected { color: #00ff88; } +.daemon-status.disconnected { color: #ff4466; } +.daemon-status.unhealthy { color: #ffcc66; } + +/* Daemon Detail */ +.daemon-detail-container { max-width: 900px; margin: 0 auto; padding: 40px 20px; font-family: 'MS Gothic', monospace; min-height: 100vh; background: var(--bg-gradient); } +.daemon-detail-header { margin-bottom: 30px; } +.daemon-title { font-family: 'MS Gothic', monospace; font-size: 22px; color: #ffffff; margin: 15px 0 8px; } + +/* Shared back links */ +.daemon-list-container .back-link, .daemon-detail-container .back-link { color: #66ccff; text-decoration: none; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; opacity: 0.8; transition: opacity 0.2s ease; } +.daemon-list-container .back-link:hover, .daemon-detail-container .back-link:hover { opacity: 1; text-decoration: underline; } + +/* Daemon Tabs */ +.daemon-tabs { display: flex; gap: 0; border-bottom: 2px solid rgba(102, 204, 255, 0.3); margin-bottom: 20px; } +.daemon-tabs .tab-button { background: transparent; border: none; border-bottom: 2px solid transparent; color: rgba(255, 255, 255, 0.5); font-family: 'MS Gothic', monospace; font-size: 13px; padding: 10px 20px; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; transition: all 0.2s ease; margin-bottom: -2px; } +.daemon-tabs .tab-button:hover { color: rgba(255, 255, 255, 0.8); background: rgba(102, 204, 255, 0.05); } +.daemon-tabs .tab-button.active { color: #66ccff; border-bottom-color: #66ccff; } + +/* Tab content */ +.daemon-tab-content .tab-panel { animation: fadeIn 0.2s ease; } +.daemon-tab-content .tab-panel h2 { font-family: 'MS Gothic', monospace; font-size: 16px; color: #66ccff; margin: 0 0 15px 0; text-transform: uppercase; letter-spacing: 1px; } +.daemon-tab-content .overview-list { margin: 0; display: grid; grid-template-columns: 180px 1fr; gap: 0; } +.daemon-tab-content .overview-list dt { padding: 10px 15px; color: rgba(255, 255, 255, 0.5); font-size: 12px; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid rgba(102, 204, 255, 0.1); } +.daemon-tab-content .overview-list dd { padding: 10px 15px; margin: 0; color: #ffffff; font-size: 13px; border-bottom: 1px solid rgba(102, 204, 255, 0.1); } + +/* Directory list */ +.directory-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; } +.directory-item { background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(102, 204, 255, 0.2); padding: 12px 16px; } +.directory-item h3 { font-size: 14px; color: #ffffff; margin: 0 0 4px 0; } +.directory-item .directory-path { font-size: 12px; color: rgba(255, 255, 255, 0.5); word-break: break-all; } +.directory-item .directory-type { display: inline-block; font-size: 10px; color: #66ccff; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; padding: 2px 6px; border: 1px solid rgba(102, 204, 255, 0.3); } + +/* Capacity bar */ +.task-capacity { display: flex; align-items: center; gap: 8px; font-size: 12px; } +.capacity-bar { width: 60px; height: 6px; background: rgba(255, 255, 255, 0.1); overflow: hidden; } +.capacity-fill { height: 100%; background: #66ccff; transition: width 0.3s ease; } +.capacity-fill.high { background: #ffcc66; } +.capacity-fill.full { background: #ff4466; } +.refresh-indicator { font-size: 11px; color: rgba(255, 255, 255, 0.3); margin-left: auto; } diff --git a/makima/cloudflare-agent/.gitignore b/makima/cloudflare-agent/.gitignore new file mode 100644 index 0000000..d833692 --- /dev/null +++ b/makima/cloudflare-agent/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.wrangler/ +.dev.vars +dist/ diff --git a/makima/cloudflare-agent/README.md b/makima/cloudflare-agent/README.md new file mode 100644 index 0000000..b33842a --- /dev/null +++ b/makima/cloudflare-agent/README.md @@ -0,0 +1,237 @@ +# Makima Cloudflare Agent + +Edge relay agent for [Makima](https://github.com/soryu-co/makima) — distributed task orchestration for AI coding agents. + +This Cloudflare Worker acts as a **WebSocket relay** between the Makima server and native daemon instances. It runs on Cloudflare's edge network using Durable Objects for persistent state and connections. + +## Why an Edge Relay? + +The full Makima daemon requires native capabilities (process spawning, git operations, filesystem access) that aren't available on Cloudflare Workers. Instead, this agent serves as: + +1. **WebSocket Relay** — Bridges the Makima server with remote daemon instances via persistent WebSocket connections +2. **Task Queue Manager** — Receives tasks from the server and dispatches them to the least-loaded downstream daemon +3. **Status Aggregator** — Tracks daemon health and task history across edge locations +4. **API Proxy** — Provides HTTP endpoints for monitoring and management from the edge + +## Architecture + +``` + Cloudflare Edge + ┌─────────────────────────┐ + │ │ + Makima Server ◄──►│ MakimaAgent │◄──► Native Daemon 1 + (wss://...) │ (Durable Object) │◄──► Native Daemon 2 + │ │◄──► Native Daemon N + │ ┌───────────────────┐ │ + │ │ SQLite State │ │ + │ │ - Task history │ │ + │ │ - Connection logs │ │ + │ └───────────────────┘ │ + │ │ + └─────────────────────────┘ +``` + +**Message flow:** + +1. The agent maintains a persistent WebSocket to the Makima server (upstream) +2. Native daemons connect to the agent via WebSocket at `/ws/daemon` (downstream) +3. When the server sends a `SpawnTask` command, the agent selects the least-loaded downstream daemon and forwards the task +4. Task output, progress, and completion messages from daemons are relayed back to the server +5. The agent sends periodic heartbeats and tracks all task dispatches in SQLite + +## Prerequisites + +- **Cloudflare Account** with Workers and Durable Objects enabled +- **Node.js 18+** and npm +- **Makima Server** running and accessible (default: `wss://api.makima.jp`) +- **API Key** from your Makima server dashboard + +## Quick Setup + +```bash +# Clone and navigate to the cloudflare-agent directory +cd makima/cloudflare-agent + +# Run the interactive setup script +chmod +x setup.sh +./setup.sh +``` + +The setup script will: + +1. Check prerequisites (Node.js 18+, npm, npx) +2. Prompt for your Makima server URL and API key +3. Install npm dependencies +4. Create `.dev.vars` with your secrets +5. Check Cloudflare authentication (offer to log in) +6. Optionally set production secrets +7. Offer to deploy immediately or start a dev server + +## Manual Setup + +If you prefer manual configuration: + +```bash +# 1. Install dependencies +npm install + +# 2. Create local secrets file +cat > .dev.vars <<EOF +MAKIMA_SERVER_URL=wss://api.makima.jp +MAKIMA_API_KEY=your-api-key-here +MAKIMA_AGENT_NAME=my-edge-agent +EOF + +# 3. Log in to Cloudflare +npx wrangler login + +# 4. Set production secrets +echo "wss://api.makima.jp" | npx wrangler secret put MAKIMA_SERVER_URL +echo "your-api-key-here" | npx wrangler secret put MAKIMA_API_KEY +echo "my-edge-agent" | npx wrangler secret put MAKIMA_AGENT_NAME + +# 5. Deploy +npx wrangler deploy +``` + +## Development + +```bash +# Start local dev server with hot reload +npm run dev + +# Stream production logs +npm run tail + +# Deploy to Cloudflare +npm run deploy +``` + +## API Endpoints + +After deployment, the agent exposes these HTTP endpoints: + +| Method | Path | Description | +|--------|---------------|------------------------------------------| +| GET | `/` | Agent status (same as `/status`) | +| GET | `/status` | Connection status and daemon overview | +| GET | `/health` | Simple health check | +| GET | `/tasks` | Task dispatch history (paginated) | +| GET | `/logs` | Connection event logs | +| POST | `/reconnect` | Force reconnection to upstream server | +| WS | `/ws/daemon` | WebSocket endpoint for downstream daemons| + +### Query Parameters + +- **`/tasks`**: `?limit=50&offset=0` — Paginate task history +- **`/logs`**: `?limit=50` — Limit log entries returned + +### Example Responses + +**GET /status** +```json +{ + "status": "ok", + "agentName": "makima-edge", + "upstreamConnected": true, + "daemonId": "550e8400-e29b-41d4-a716-446655440000", + "lastHeartbeat": "2024-12-15T10:30:00.000Z", + "connectedDaemons": 2, + "activeTasks": 3, + "totalTasksProcessed": 142 +} +``` + +**GET /health** +```json +{ + "healthy": true, + "upstreamConnected": true +} +``` + +## Environment Variables + +| Variable | Required | Description | Default | +|-----------------------|----------|------------------------------------------------|-----------------------| +| `MAKIMA_SERVER_URL` | Yes | WebSocket URL of the Makima server | — | +| `MAKIMA_API_KEY` | Yes | API key for server authentication | — | +| `MAKIMA_AGENT_NAME` | No | Human-readable name for this agent | `makima-edge-{id}` | + +Set these as Cloudflare Workers secrets for production: +```bash +echo "value" | npx wrangler secret put VARIABLE_NAME +``` + +For local development, put them in `.dev.vars` (gitignored automatically). + +## How It Works + +### Upstream Connection (to Makima Server) + +1. On startup, the agent opens a WebSocket to `MAKIMA_SERVER_URL/ws/daemon` +2. It authenticates using the `MAKIMA_API_KEY` with `maxConcurrentTasks: 0` (relay-only mode) +3. Sends heartbeats every 30 seconds +4. On disconnect, reconnects with exponential backoff (1s → 60s, max 20 attempts) + +### Downstream Connections (from Native Daemons) + +1. Native Makima daemons connect via WebSocket to `/ws/daemon` +2. Daemons authenticate with their hostname and max concurrent task count +3. The agent tracks each daemon's active tasks and health + +### Task Dispatch + +When the server sends a `SpawnTask` command: +1. The agent records the task in SQLite +2. Selects the downstream daemon with the fewest active tasks (that still has capacity) +3. Forwards the full `SpawnTask` command +4. If no daemons are available, the task remains pending + +### Persistence + +The agent uses Cloudflare Durable Objects with SQLite for: +- **Task history** — All received, dispatched, completed, and failed tasks +- **Connection logs** — Connect/disconnect/error events (last 200 entries) +- **Agent state** — Connection status, daemon ID, active tasks + +The Durable Object auto-hibernates when idle and resumes on the next request, preserving all state. + +## Connecting Native Daemons + +Native Makima daemons can connect to this edge relay instead of directly to the server. Configure the daemon to point at your deployed Worker URL: + +```toml +# makima-daemon.toml +[server] +url = "wss://makima-agent.your-account.workers.dev/ws/daemon" +api_key = "your-api-key" +``` + +Or via environment variable: +```bash +MAKIMA_DAEMON_SERVER__URL=wss://makima-agent.your-account.workers.dev/ws/daemon \ +MAKIMA_DAEMON_SERVER__API_KEY=your-api-key \ +makima daemon +``` + +## Troubleshooting + +### Agent shows "disconnected" status +- Verify `MAKIMA_SERVER_URL` is correct and the server is reachable +- Check that `MAKIMA_API_KEY` is valid +- Review connection logs at `GET /logs` + +### Tasks stuck in "pending" +- No downstream daemons are connected +- Ensure native daemons are pointing to this agent's `/ws/daemon` endpoint +- Check `GET /status` for `connectedDaemons` count + +### Deployment fails +- Ensure you're logged in: `npx wrangler whoami` +- Ensure your account has Durable Objects enabled +- Check `wrangler.toml` configuration + +## License + +Part of the [Makima](https://github.com/soryu-co/makima) project. diff --git a/makima/cloudflare-agent/package-lock.json b/makima/cloudflare-agent/package-lock.json new file mode 100644 index 0000000..256a708 --- /dev/null +++ b/makima/cloudflare-agent/package-lock.json @@ -0,0 +1,2802 @@ +{ + "name": "makima-agent", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "makima-agent", + "version": "0.1.0", + "dependencies": { + "agents": "^0.0.67" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "^5.7.0", + "wrangler": "^3.99.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", + "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/@ai-sdk/react": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", + "dependencies": { + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", + "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "dev": true, + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", + "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", + "dev": true, + "peerDependencies": { + "unenv": "2.0.0-rc.14", + "workerd": "^1.20250124.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", + "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", + "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", + "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", + "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", + "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260228.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260228.0.tgz", + "integrity": "sha512-9LfRg93ncQq6Oc4MFpqGSs+PmPhqWvg8TspXwbiYNR201IhXB4WqHR/aTSudPI0ujsf/NLc8E9fF3C+aA2g8KQ==" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", + "dev": true, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agents": { + "version": "0.0.67", + "resolved": "https://registry.npmjs.org/agents/-/agents-0.0.67.tgz", + "integrity": "sha512-Bjn8w9230xcgeP/UKvWbZVvQvPuCpA1S0f8X6859xSvkviAcKWgHidnyRBUMUCniljNTzbU1ZgR8YWveJ4xbqg==", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.10.2", + "ai": "^4.3.9", + "cron-schedule": "^5.0.4", + "nanoid": "^5.1.5", + "partyserver": "^0.0.67", + "partysocket": "1.1.3", + "zod": "^3.24.3" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/ai": { + "version": "4.3.19", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", + "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, + "dependencies": { + "printable-characters": "^1.0.42" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cron-schedule": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-5.0.4.tgz", + "integrity": "sha512-nH0a49E/kSVk6BeFgKZy4uUsy6D2A16p120h5bYD9ILBhQu7o2sJFH+WI4R731TSBQ0dB1Ik7inB/dRAB4C8QQ==", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-polyfill": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz", + "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.1.tgz", + "integrity": "sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw==", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "optional": true + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" + }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/miniflare": { + "version": "3.20250718.3", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz", + "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "stoppable": "1.1.0", + "undici": "^5.28.5", + "workerd": "1.20250718.0", + "ws": "8.18.0", + "youch": "3.3.4", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/miniflare/node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/partyserver": { + "version": "0.0.67", + "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.0.67.tgz", + "integrity": "sha512-GQ0fjJ7n5r5LrsFHFUkGR3Bd50YdBZaDNjvRTi8PowZgI5fvCFliT/XdDpRVuwfDDk0jb9es2cXcaAh1z5GsLA==", + "dependencies": { + "nanoid": "^5.1.5" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20240729.0" + } + }, + "node_modules/partysocket": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.3.tgz", + "integrity": "sha512-87Jd/nqPoWnVfzHE6Z12WLWTJ+TAgxs0b7i2S163HfQSrVDUK5tW/FC64T5N8L5ss+gqF+EV0BwjZMWggMY3UA==", + "dependencies": { + "event-target-polyfill": "^0.0.4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/swr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.0.tgz", + "integrity": "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "optional": true + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.14", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", + "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "dev": true, + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.1", + "ohash": "^2.0.10", + "pathe": "^2.0.3", + "ufo": "^1.5.4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerd": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz", + "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250718.0", + "@cloudflare/workerd-darwin-arm64": "1.20250718.0", + "@cloudflare/workerd-linux-64": "1.20250718.0", + "@cloudflare/workerd-linux-arm64": "1.20250718.0", + "@cloudflare/workerd-windows-64": "1.20250718.0" + } + }, + "node_modules/wrangler": { + "version": "3.114.17", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.17.tgz", + "integrity": "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA==", + "dev": true, + "dependencies": { + "@cloudflare/kv-asset-handler": "0.3.4", + "@cloudflare/unenv-preset": "2.0.2", + "@esbuild-plugins/node-globals-polyfill": "0.2.3", + "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.17.19", + "miniflare": "3.20250718.3", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.14", + "workerd": "1.20250718.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=16.17.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250408.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrangler/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", + "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "dev": true, + "dependencies": { + "cookie": "^0.7.1", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/makima/cloudflare-agent/package.json b/makima/cloudflare-agent/package.json new file mode 100644 index 0000000..5b6a01e --- /dev/null +++ b/makima/cloudflare-agent/package.json @@ -0,0 +1,22 @@ +{ + "name": "makima-agent", + "version": "0.1.0", + "description": "Makima edge relay agent for Cloudflare Workers - bridges the Makima server with remote daemon instances", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "setup": "bash setup.sh", + "tail": "wrangler tail", + "types": "wrangler types" + }, + "dependencies": { + "agents": "^0.0.67" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "^5.7.0", + "wrangler": "^3.99.0" + } +} diff --git a/makima/cloudflare-agent/setup.sh b/makima/cloudflare-agent/setup.sh new file mode 100755 index 0000000..acf9cd9 --- /dev/null +++ b/makima/cloudflare-agent/setup.sh @@ -0,0 +1,331 @@ +#!/usr/bin/env bash +# ============================================================================ +# Makima Cloudflare Agent — Interactive Setup Script +# +# Sets up the Cloudflare Workers project for deploying a Makima edge relay +# agent. This agent acts as a WebSocket bridge between the Makima server +# and native daemon instances running on your infrastructure. +# +# Usage: +# chmod +x setup.sh && ./setup.sh +# ============================================================================ + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +info() { echo -e "${BLUE}ℹ${NC} $*"; } +success() { echo -e "${GREEN}✔${NC} $*"; } +warn() { echo -e "${YELLOW}⚠${NC} $*"; } +error() { echo -e "${RED}✖${NC} $*"; } + +prompt() { + local var_name="$1" + local prompt_text="$2" + local default_value="${3:-}" + local value + + if [ -n "$default_value" ]; then + echo -ne "${CYAN}?${NC} ${prompt_text} ${BOLD}[${default_value}]${NC}: " + else + echo -ne "${CYAN}?${NC} ${prompt_text}: " + fi + + read -r value + value="${value:-$default_value}" + eval "$var_name=\"$value\"" +} + +confirm() { + local prompt_text="$1" + local default="${2:-y}" + local yn + + if [ "$default" = "y" ]; then + echo -ne "${CYAN}?${NC} ${prompt_text} ${BOLD}[Y/n]${NC}: " + else + echo -ne "${CYAN}?${NC} ${prompt_text} ${BOLD}[y/N]${NC}: " + fi + + read -r yn + yn="${yn:-$default}" + + case "$yn" in + [Yy]*) return 0 ;; + *) return 1 ;; + esac +} + +separator() { + echo "" + echo -e "${BLUE}──────────────────────────────────────────────────────${NC}" + echo "" +} + +# --------------------------------------------------------------------------- +# Header +# --------------------------------------------------------------------------- + +clear 2>/dev/null || true +echo "" +echo -e "${BOLD}${CYAN}" +echo " ╔══════════════════════════════════════════════════╗" +echo " ║ Makima Cloudflare Agent Setup ║" +echo " ║ ║" +echo " ║ Edge relay for distributed task orchestration ║" +echo " ╚══════════════════════════════════════════════════╝" +echo -e "${NC}" +echo "" + +# --------------------------------------------------------------------------- +# 1. Check Prerequisites +# --------------------------------------------------------------------------- + +info "Checking prerequisites..." +echo "" + +missing=0 + +# Node.js +if command -v node &>/dev/null; then + node_version=$(node --version) + # Check if version is 18+ + major_version=$(echo "$node_version" | sed 's/v//' | cut -d. -f1) + if [ "$major_version" -ge 18 ]; then + success "Node.js ${node_version}" + else + error "Node.js ${node_version} (requires v18+)" + missing=1 + fi +else + error "Node.js not found (requires v18+)" + missing=1 +fi + +# npm +if command -v npm &>/dev/null; then + success "npm $(npm --version)" +else + error "npm not found" + missing=1 +fi + +# npx +if command -v npx &>/dev/null; then + success "npx available" +else + error "npx not found" + missing=1 +fi + +if [ "$missing" -eq 1 ]; then + echo "" + error "Missing prerequisites. Please install the missing tools and try again." + echo "" + info "Install Node.js 18+: https://nodejs.org/" + exit 1 +fi + +separator + +# --------------------------------------------------------------------------- +# 2. Gather Configuration +# --------------------------------------------------------------------------- + +info "Let's configure your Makima edge agent." +echo "" + +prompt MAKIMA_SERVER_URL "Makima server WebSocket URL" "wss://api.makima.jp" +echo "" + +prompt MAKIMA_API_KEY "Makima API key (from your server dashboard)" "" +if [ -z "$MAKIMA_API_KEY" ]; then + error "API key is required." + exit 1 +fi +echo "" + +prompt MAKIMA_AGENT_NAME "Agent display name (optional)" "makima-edge" +echo "" + +separator + +# --------------------------------------------------------------------------- +# 3. Install Dependencies +# --------------------------------------------------------------------------- + +info "Installing dependencies..." +echo "" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +npm install 2>&1 | while IFS= read -r line; do + echo " $line" +done + +echo "" +success "Dependencies installed." + +separator + +# --------------------------------------------------------------------------- +# 4. Create .dev.vars (local secrets for development) +# --------------------------------------------------------------------------- + +info "Creating .dev.vars for local development secrets..." + +cat > .dev.vars <<EOF +# Makima Cloudflare Agent — Local Development Secrets +# These are used by 'wrangler dev' and should NOT be committed to git. + +MAKIMA_SERVER_URL=${MAKIMA_SERVER_URL} +MAKIMA_API_KEY=${MAKIMA_API_KEY} +MAKIMA_AGENT_NAME=${MAKIMA_AGENT_NAME} +EOF + +success "Created .dev.vars" +echo "" + +# Ensure .dev.vars is gitignored +if [ -f .gitignore ]; then + if ! grep -q ".dev.vars" .gitignore 2>/dev/null; then + echo ".dev.vars" >> .gitignore + success "Added .dev.vars to .gitignore" + fi +else + echo ".dev.vars" > .gitignore + echo "node_modules/" >> .gitignore + echo ".wrangler/" >> .gitignore + success "Created .gitignore" +fi + +separator + +# --------------------------------------------------------------------------- +# 5. Wrangler Login Check +# --------------------------------------------------------------------------- + +info "Checking Cloudflare authentication..." +echo "" + +if npx wrangler whoami 2>/dev/null | grep -q "You are logged in"; then + success "Already logged in to Cloudflare." +else + warn "Not logged in to Cloudflare." + if confirm "Would you like to log in now?"; then + npx wrangler login + success "Logged in to Cloudflare." + else + warn "Skipping login. You'll need to run 'npx wrangler login' before deploying." + fi +fi + +separator + +# --------------------------------------------------------------------------- +# 6. Set Production Secrets +# --------------------------------------------------------------------------- + +if confirm "Would you like to set production secrets now? (Required before first deploy)"; then + echo "" + info "Setting secrets via Wrangler..." + echo "" + + echo "$MAKIMA_SERVER_URL" | npx wrangler secret put MAKIMA_SERVER_URL 2>&1 | while IFS= read -r line; do + echo " $line" + done + success "Set MAKIMA_SERVER_URL" + + echo "$MAKIMA_API_KEY" | npx wrangler secret put MAKIMA_API_KEY 2>&1 | while IFS= read -r line; do + echo " $line" + done + success "Set MAKIMA_API_KEY" + + if [ -n "$MAKIMA_AGENT_NAME" ]; then + echo "$MAKIMA_AGENT_NAME" | npx wrangler secret put MAKIMA_AGENT_NAME 2>&1 | while IFS= read -r line; do + echo " $line" + done + success "Set MAKIMA_AGENT_NAME" + fi +fi + +separator + +# --------------------------------------------------------------------------- +# 7. Deploy or Dev +# --------------------------------------------------------------------------- + +echo -e "${BOLD}Setup complete!${NC} What would you like to do next?" +echo "" +echo " 1) Deploy to Cloudflare Workers (production)" +echo " 2) Start local development server" +echo " 3) Exit (deploy later)" +echo "" + +prompt NEXT_ACTION "Choose an option" "3" + +case "$NEXT_ACTION" in + 1) + echo "" + info "Deploying to Cloudflare Workers..." + echo "" + npx wrangler deploy 2>&1 | while IFS= read -r line; do + echo " $line" + done + echo "" + success "Deployed! Your Makima edge agent is live." + ;; + 2) + echo "" + info "Starting local development server..." + echo "" + info "Press Ctrl+C to stop." + echo "" + npx wrangler dev + ;; + 3|*) + ;; +esac + +# --------------------------------------------------------------------------- +# 8. Next Steps +# --------------------------------------------------------------------------- + +separator + +echo -e "${BOLD}${GREEN}🎉 Makima Cloudflare Agent is ready!${NC}" +echo "" +echo -e " ${BOLD}Useful commands:${NC}" +echo "" +echo -e " ${CYAN}npm run dev${NC} Start local development server" +echo -e " ${CYAN}npm run deploy${NC} Deploy to Cloudflare Workers" +echo -e " ${CYAN}npm run tail${NC} Stream production logs" +echo "" +echo -e " ${BOLD}API Endpoints (after deploy):${NC}" +echo "" +echo -e " ${CYAN}GET /status${NC} Agent connection status" +echo -e " ${CYAN}GET /health${NC} Health check" +echo -e " ${CYAN}GET /tasks${NC} Task dispatch history" +echo -e " ${CYAN}GET /logs${NC} Connection event logs" +echo -e " ${CYAN}POST /reconnect${NC} Force upstream reconnection" +echo -e " ${CYAN}WS /ws/daemon${NC} Downstream daemon WebSocket" +echo "" +echo -e " ${BOLD}Architecture:${NC}" +echo "" +echo -e " Makima Server ←WebSocket→ ${CYAN}Edge Agent${NC} ←WebSocket→ Native Daemons" +echo "" +echo -e " ${BOLD}Documentation:${NC}" +echo -e " See ${CYAN}README.md${NC} for full setup and architecture details." +echo "" diff --git a/makima/cloudflare-agent/src/agent.ts b/makima/cloudflare-agent/src/agent.ts new file mode 100644 index 0000000..a5a7823 --- /dev/null +++ b/makima/cloudflare-agent/src/agent.ts @@ -0,0 +1,773 @@ +/** + * MakimaAgent — Cloudflare Durable Object Agent + * + * Acts as an edge relay between the Makima server and downstream daemon + * instances. It does NOT execute tasks itself (that requires native process + * spawning, git, filesystem access). Instead it: + * + * 1. Maintains a persistent WebSocket to the Makima server (upstream). + * 2. Accepts WebSocket connections from native daemons (downstream). + * 3. Relays messages bidirectionally between upstream and downstream. + * 4. Tracks task dispatch history and daemon health in SQLite. + * 5. Exposes an HTTP API for status and management. + */ + +import { Agent } from "agents"; +import type { Connection, ConnectionContext, WSMessage } from "agents"; +import type { + Env, + AgentState, + DaemonMessage, + DaemonCommand, + TaskRecord, + DownstreamDaemon, + StatusResponse, + TaskHistoryResponse, +} from "./types"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const HEARTBEAT_INTERVAL_MS = 30_000; // 30s +const RECONNECT_BASE_MS = 1_000; // 1s initial backoff +const RECONNECT_MAX_MS = 60_000; // 60s max backoff +const MAX_RECONNECT_ATTEMPTS = 20; +const TASK_HISTORY_MAX = 500; + +// --------------------------------------------------------------------------- +// MakimaAgent Durable Object +// --------------------------------------------------------------------------- + +export class MakimaAgent extends Agent<Env, AgentState> { + // -- Upstream (Makima server) connection -- + private upstreamWs: WebSocket | null = null; + private heartbeatInterval: ReturnType<typeof setInterval> | null = null; + private reconnectTimeout: ReturnType<typeof setTimeout> | null = null; + + // -- Downstream (native daemon) connections -- + private downstreamDaemons: Map<string, DownstreamDaemon> = new Map(); + + // -- Bookkeeping -- + private totalTasksProcessed = 0; + + // -- Default state for new agent instances -- + initialState: AgentState = { + connected: false, + lastConnectedAt: null, + lastHeartbeatAt: null, + reconnectAttempts: 0, + daemonId: null, + activeTasks: [], + }; + + // ========================================================================= + // Lifecycle + // ========================================================================= + + /** + * Called once when the Durable Object is first instantiated. + * Sets up the SQLite schema and establishes the upstream connection. + */ + async onStart(): Promise<void> { + this.initDatabase(); + this.loadStats(); + + // Attempt upstream connection + await this.connectUpstream(); + } + + // ========================================================================= + // SQLite Schema + // ========================================================================= + + private initDatabase(): void { + // Use the raw storage sql for DDL since tagged template doesn't support multi-statement + this.ctx.storage.sql.exec(` + CREATE TABLE IF NOT EXISTS task_history ( + task_id TEXT PRIMARY KEY, + task_name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + dispatched_to TEXT, + received_at TEXT NOT NULL, + dispatched_at TEXT, + completed_at TEXT, + error TEXT + ) + `); + this.ctx.storage.sql.exec(` + CREATE TABLE IF NOT EXISTS connection_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + event TEXT NOT NULL, + timestamp TEXT NOT NULL, + details TEXT + ) + `); + this.ctx.storage.sql.exec(` + CREATE INDEX IF NOT EXISTS idx_task_history_status + ON task_history(status) + `); + this.ctx.storage.sql.exec(` + CREATE INDEX IF NOT EXISTS idx_task_history_received + ON task_history(received_at DESC) + `); + } + + private loadStats(): void { + const rows = this.sql<{ cnt: number }>`SELECT COUNT(*) as cnt FROM task_history`; + this.totalTasksProcessed = rows[0]?.cnt ?? 0; + } + + // ========================================================================= + // Upstream (Makima Server) Connection + // ========================================================================= + + private async connectUpstream(): Promise<void> { + const serverUrl = this.env.MAKIMA_SERVER_URL; + const apiKey = this.env.MAKIMA_API_KEY; + + if (!serverUrl || !apiKey) { + this.logConnection( + "error", + "Missing MAKIMA_SERVER_URL or MAKIMA_API_KEY" + ); + return; + } + + // Normalize URL: ensure it ends with /ws/daemon + let wsUrl = serverUrl; + if (!wsUrl.endsWith("/ws/daemon")) { + wsUrl = wsUrl.replace(/\/$/, "") + "/ws/daemon"; + } + + try { + // Cloudflare Workers use the standard WebSocket API via fetch upgrade + const resp = await fetch(wsUrl, { + headers: { + Upgrade: "websocket", + }, + }); + + const ws = resp.webSocket; + if (!ws) { + throw new Error(`WebSocket upgrade failed (status ${resp.status})`); + } + + ws.accept(); + this.upstreamWs = ws; + + // Authenticate immediately + const authMsg: DaemonMessage = { + type: "authenticate", + apiKey, + machineId: `cf-agent-${this.name}`, + hostname: `cloudflare-edge-${this.name}`, + maxConcurrentTasks: 0, // Relay only — does not execute tasks directly + }; + ws.send(JSON.stringify(authMsg)); + + // Wire up event handlers + ws.addEventListener("message", (event) => { + this.handleUpstreamMessage(event); + }); + + ws.addEventListener("close", (event) => { + this.handleUpstreamClose(event.code, event.reason); + }); + + ws.addEventListener("error", () => { + this.logConnection("error", "Upstream WebSocket error"); + this.handleUpstreamClose(1006, "WebSocket error"); + }); + + // Start heartbeat + this.startHeartbeat(); + + this.logConnection("connected", `Connected to ${wsUrl}`); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + this.logConnection("error", `Failed to connect: ${msg}`); + this.scheduleReconnect(); + } + } + + private handleUpstreamMessage(event: MessageEvent): void { + let command: DaemonCommand; + try { + command = JSON.parse( + typeof event.data === "string" ? event.data : "" + ) as DaemonCommand; + } catch { + this.logConnection("error", "Failed to parse upstream message"); + return; + } + + switch (command.type) { + case "authenticated": + this.onUpstreamAuthenticated(command.daemonId); + break; + + case "spawnTask": + this.onSpawnTask(command); + break; + + case "pauseTask": + case "resumeTask": + case "interruptTask": + case "sendMessage": + // Forward task control commands to the appropriate downstream daemon + this.forwardToDownstream(command); + break; + + default: + // Forward any unrecognized commands to all downstream daemons + this.broadcastToDownstream(JSON.stringify(command)); + break; + } + } + + private onUpstreamAuthenticated(daemonId: string): void { + this.setState({ + ...this.state, + connected: true, + lastConnectedAt: new Date().toISOString(), + reconnectAttempts: 0, + daemonId, + }); + + this.logConnection( + "authenticated", + `Authenticated as daemon ${daemonId}` + ); + } + + private onSpawnTask(command: Extract<DaemonCommand, { type: "spawnTask" }>): void { + // Record in task history + const now = new Date().toISOString(); + const taskId = command.taskId; + const taskName = command.taskName; + this.sql`INSERT OR REPLACE INTO task_history (task_id, task_name, status, received_at) + VALUES (${taskId}, ${taskName}, 'pending', ${now})`; + this.totalTasksProcessed++; + + // Find the best downstream daemon to dispatch to + const daemon = this.selectDownstreamDaemon(); + if (daemon) { + this.dispatchTaskToDownstream(daemon, command); + } else { + // No downstream daemons connected — store as pending + this.logConnection( + "warning", + `No downstream daemons available for task ${command.taskId}` + ); + // Notify upstream that we can't handle this task right now + this.sendUpstream({ + type: "taskStatusChange", + taskId: command.taskId, + oldStatus: "pending", + newStatus: "pending", + }); + } + } + + private dispatchTaskToDownstream( + daemon: DownstreamDaemon, + command: Extract<DaemonCommand, { type: "spawnTask" }> + ): void { + try { + daemon.ws.send(JSON.stringify(command)); + daemon.activeTasks.add(command.taskId); + + const now = new Date().toISOString(); + const did = daemon.id; + const tid = command.taskId; + this.sql`UPDATE task_history + SET status = 'dispatched', dispatched_to = ${did}, dispatched_at = ${now} + WHERE task_id = ${tid}`; + + this.setState({ + ...this.state, + activeTasks: [...this.state.activeTasks, command.taskId], + }); + + this.logConnection( + "dispatch", + `Task ${command.taskId} dispatched to daemon ${daemon.hostname}` + ); + } catch (err) { + this.logConnection( + "error", + `Failed to dispatch task ${command.taskId}: ${err}` + ); + } + } + + private selectDownstreamDaemon(): DownstreamDaemon | null { + let best: DownstreamDaemon | null = null; + let leastTasks = Infinity; + + for (const daemon of this.downstreamDaemons.values()) { + if (daemon.activeTasks.size < daemon.maxConcurrentTasks) { + if (daemon.activeTasks.size < leastTasks) { + leastTasks = daemon.activeTasks.size; + best = daemon; + } + } + } + return best; + } + + private forwardToDownstream(command: DaemonCommand): void { + // Find which downstream daemon has this task + const taskId = "taskId" in command ? (command as { taskId: string }).taskId : null; + if (!taskId) return; + + for (const daemon of this.downstreamDaemons.values()) { + if (daemon.activeTasks.has(taskId)) { + daemon.ws.send(JSON.stringify(command)); + return; + } + } + + this.logConnection( + "warning", + `No downstream daemon found for task ${taskId}` + ); + } + + private broadcastToDownstream(message: string): void { + for (const daemon of this.downstreamDaemons.values()) { + try { + daemon.ws.send(message); + } catch { + // Ignore send failures on individual daemons + } + } + } + + // ========================================================================= + // Downstream (Native Daemon) WebSocket Handling + // ========================================================================= + + /** + * Called by the Agents SDK when a client connects via WebSocket. + * In our architecture, "clients" are native Makima daemons that connect + * to this edge relay to receive task dispatches. + */ + onConnect(connection: Connection, ctx: ConnectionContext): void { + const daemonId = crypto.randomUUID(); + const daemon: DownstreamDaemon = { + id: daemonId, + ws: connection as unknown as WebSocket, + hostname: "unknown", + maxConcurrentTasks: 10, + activeTasks: new Set(), + lastHeartbeat: new Date().toISOString(), + }; + this.downstreamDaemons.set(daemonId, daemon); + + this.logConnection( + "downstream_connect", + `Downstream daemon connected: ${daemonId}` + ); + } + + /** + * Called when a downstream daemon sends a message. + */ + onMessage(connection: Connection, message: WSMessage): void { + const data = + typeof message === "string" + ? message + : message instanceof ArrayBuffer + ? new TextDecoder().decode(message) + : ""; + let parsed: DaemonMessage; + try { + parsed = JSON.parse(data) as DaemonMessage; + } catch { + return; + } + + // Find which downstream daemon sent this + const daemon = this.findDaemonByConnection(connection); + + // Handle authenticate from downstream daemons + if (parsed.type === "authenticate" && daemon) { + daemon.hostname = (parsed as Extract<DaemonMessage, { type: "authenticate" }>).hostname; + daemon.maxConcurrentTasks = (parsed as Extract<DaemonMessage, { type: "authenticate" }>).maxConcurrentTasks; + // Send back a synthetic authentication confirmation + connection.send( + JSON.stringify({ + type: "authenticated", + daemonId: daemon.id, + }) + ); + return; + } + + // Handle heartbeats from downstream + if (parsed.type === "heartbeat" && daemon) { + daemon.lastHeartbeat = new Date().toISOString(); + return; + } + + // Handle task completion from downstream + if (parsed.type === "taskComplete" && daemon) { + const tc = parsed as Extract<DaemonMessage, { type: "taskComplete" }>; + daemon.activeTasks.delete(tc.taskId); + + const status = tc.success ? "completed" : "failed"; + const now = new Date().toISOString(); + const errMsg = tc.error ?? null; + const tid = tc.taskId; + this.sql`UPDATE task_history + SET status = ${status}, completed_at = ${now}, error = ${errMsg} + WHERE task_id = ${tid}`; + + this.setState({ + ...this.state, + activeTasks: this.state.activeTasks.filter((id) => id !== tc.taskId), + }); + } + + // Forward all daemon messages to the upstream server + this.sendUpstream(parsed); + } + + /** + * Called when a downstream daemon disconnects. + */ + onClose(connection: Connection, code: number, reason: string, wasClean: boolean): void { + const daemon = this.findDaemonByConnection(connection); + if (daemon) { + this.logConnection( + "downstream_disconnect", + `Downstream daemon ${daemon.hostname} disconnected: code=${code} reason=${reason} clean=${wasClean}` + ); + this.downstreamDaemons.delete(daemon.id); + } + } + + onError(connectionOrError: Connection | unknown, error?: unknown): void { + // Handle the overloaded signature: (connection, error) or (error) + if (error !== undefined) { + const connection = connectionOrError as Connection; + const daemon = this.findDaemonByConnection(connection); + if (daemon) { + this.logConnection( + "error", + `Downstream daemon ${daemon.hostname} error: ${error}` + ); + } + } else { + this.logConnection("error", `Agent error: ${connectionOrError}`); + } + } + + private findDaemonByConnection(connection: Connection): DownstreamDaemon | undefined { + for (const daemon of this.downstreamDaemons.values()) { + if (daemon.ws === (connection as unknown as WebSocket)) { + return daemon; + } + } + return undefined; + } + + // ========================================================================= + // Heartbeat + // ========================================================================= + + private startHeartbeat(): void { + this.stopHeartbeat(); + this.heartbeatInterval = setInterval(() => { + this.sendHeartbeat(); + }, HEARTBEAT_INTERVAL_MS); + } + + private stopHeartbeat(): void { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); + this.heartbeatInterval = null; + } + } + + private sendHeartbeat(): void { + const activeTasks = this.state.activeTasks; + this.sendUpstream({ + type: "heartbeat", + activeTasks, + }); + this.setState({ + ...this.state, + lastHeartbeatAt: new Date().toISOString(), + }); + } + + // ========================================================================= + // Upstream Reconnection + // ========================================================================= + + private handleUpstreamClose(code: number, reason: string): void { + this.upstreamWs = null; + this.stopHeartbeat(); + + this.setState({ + ...this.state, + connected: false, + }); + + this.logConnection( + "disconnected", + `Upstream disconnected: code=${code} reason=${reason}` + ); + + this.scheduleReconnect(); + } + + private scheduleReconnect(): void { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + + const attempts = this.state.reconnectAttempts; + if (attempts >= MAX_RECONNECT_ATTEMPTS) { + this.logConnection( + "error", + `Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached — giving up` + ); + return; + } + + // Exponential backoff with jitter + const delay = Math.min( + RECONNECT_BASE_MS * Math.pow(2, attempts) + + Math.random() * RECONNECT_BASE_MS, + RECONNECT_MAX_MS + ); + + this.setState({ + ...this.state, + reconnectAttempts: attempts + 1, + }); + + this.logConnection( + "reconnecting", + `Scheduling reconnect in ${Math.round(delay)}ms (attempt ${attempts + 1})` + ); + + this.reconnectTimeout = setTimeout(() => { + this.connectUpstream(); + }, delay); + } + + // ========================================================================= + // Upstream Send Helper + // ========================================================================= + + private sendUpstream(message: DaemonMessage): void { + if (!this.upstreamWs) return; + try { + this.upstreamWs.send(JSON.stringify(message)); + } catch { + this.logConnection("error", "Failed to send upstream message"); + } + } + + // ========================================================================= + // Connection Logging + // ========================================================================= + + private logConnection(event: string, details: string): void { + try { + const now = new Date().toISOString(); + this.sql`INSERT INTO connection_log (event, timestamp, details) + VALUES (${event}, ${now}, ${details})`; + + // Prune old logs (keep last 200) + this.ctx.storage.sql.exec(` + DELETE FROM connection_log + WHERE id NOT IN ( + SELECT id FROM connection_log ORDER BY id DESC LIMIT 200 + ) + `); + } catch { + // Swallow — logging should never break the agent + } + } + + // ========================================================================= + // RPC Methods (callable from Agent SDK clients) + // ========================================================================= + + /** Returns the current status of the edge relay agent. */ + getStatus(): StatusResponse { + const agentName = this.env.MAKIMA_AGENT_NAME ?? `makima-edge-${this.name}`; + + return { + status: this.state.connected + ? this.downstreamDaemons.size > 0 + ? "ok" + : "degraded" + : "disconnected", + agentName, + upstreamConnected: this.state.connected, + daemonId: this.state.daemonId, + lastHeartbeat: this.state.lastHeartbeatAt, + connectedDaemons: this.downstreamDaemons.size, + activeTasks: this.state.activeTasks.length, + totalTasksProcessed: this.totalTasksProcessed, + }; + } + + /** Returns paginated task history from the SQLite store. */ + getTaskHistory(limit = 50, offset = 0): TaskHistoryResponse { + const clampedLimit = Math.min(Math.max(1, limit), TASK_HISTORY_MAX); + + const rows = this.sql<TaskRecord>` + SELECT task_id as taskId, task_name as taskName, status, + dispatched_to as dispatchedTo, received_at as receivedAt, + dispatched_at as dispatchedAt, completed_at as completedAt, error + FROM task_history + ORDER BY received_at DESC + LIMIT ${clampedLimit} OFFSET ${offset} + `; + + const totalRows = this.sql<{ cnt: number }>`SELECT COUNT(*) as cnt FROM task_history`; + + return { + tasks: rows, + total: totalRows[0]?.cnt ?? 0, + limit: clampedLimit, + offset, + }; + } + + /** Force a reconnection to the upstream Makima server. */ + reconnect(): { success: boolean; message: string } { + // Close existing connection if any + if (this.upstreamWs) { + try { + this.upstreamWs.close(1000, "Manual reconnect requested"); + } catch { + // Ignore + } + this.upstreamWs = null; + } + + this.stopHeartbeat(); + + // Reset reconnect counter + this.setState({ + ...this.state, + reconnectAttempts: 0, + connected: false, + }); + + // Start fresh connection + this.connectUpstream(); + + return { + success: true, + message: "Reconnection initiated", + }; + } + + // ========================================================================= + // HTTP Request Handler + // ========================================================================= + + /** + * Handles HTTP requests routed to this Durable Object. + * Provides a REST API for management and status. + */ + async onRequest(request: Request): Promise<Response> { + const url = new URL(request.url); + const path = url.pathname; + + // CORS headers for browser-based clients + const corsHeaders: Record<string, string> = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", + }; + + if (request.method === "OPTIONS") { + return new Response(null, { status: 204, headers: corsHeaders }); + } + + const jsonHeaders: Record<string, string> = { + "Content-Type": "application/json", + ...corsHeaders, + }; + + try { + // GET /status — Agent status + if (path === "/status" || path === "/") { + return new Response(JSON.stringify(this.getStatus()), { + headers: jsonHeaders, + }); + } + + // GET /tasks — Task history + if (path === "/tasks" && request.method === "GET") { + const limit = parseInt(url.searchParams.get("limit") ?? "50", 10); + const offset = parseInt(url.searchParams.get("offset") ?? "0", 10); + return new Response( + JSON.stringify(this.getTaskHistory(limit, offset)), + { headers: jsonHeaders } + ); + } + + // POST /reconnect — Force reconnection + if (path === "/reconnect" && request.method === "POST") { + return new Response(JSON.stringify(this.reconnect()), { + headers: jsonHeaders, + }); + } + + // GET /logs — Connection logs + if (path === "/logs" && request.method === "GET") { + const logLimit = Math.min( + parseInt(url.searchParams.get("limit") ?? "50", 10), + 200 + ); + const rows = this.sql<{ + id: number; + event: string; + timestamp: string; + details: string | null; + }>`SELECT id, event, timestamp, details + FROM connection_log + ORDER BY id DESC + LIMIT ${logLimit}`; + return new Response(JSON.stringify({ logs: rows }), { + headers: jsonHeaders, + }); + } + + // GET /health — Simple health check + if (path === "/health") { + return new Response( + JSON.stringify({ + healthy: true, + upstreamConnected: this.state.connected, + }), + { headers: jsonHeaders } + ); + } + + return new Response(JSON.stringify({ error: "Not found" }), { + status: 404, + headers: jsonHeaders, + }); + } catch (err) { + return new Response( + JSON.stringify({ + error: err instanceof Error ? err.message : "Internal error", + }), + { status: 500, headers: jsonHeaders } + ); + } + } +} diff --git a/makima/cloudflare-agent/src/index.ts b/makima/cloudflare-agent/src/index.ts new file mode 100644 index 0000000..0b64af4 --- /dev/null +++ b/makima/cloudflare-agent/src/index.ts @@ -0,0 +1,47 @@ +/** + * Makima Cloudflare Agent — Worker Entry Point + * + * Routes incoming requests to the MakimaAgent Durable Object which manages + * the upstream WebSocket connection to the Makima server and relays tasks + * to downstream native daemon instances. + * + * Routes: + * GET / → Agent status (JSON) + * GET /status → Agent status (JSON) + * GET /health → Health check + * GET /tasks → Task history (paginated) + * GET /logs → Connection logs + * POST /reconnect → Force upstream reconnection + * * /ws → WebSocket upgrade for downstream daemons + * * /agent/* → Forwarded to Agent's onRequest handler + */ + +import { MakimaAgent } from "./agent"; +import type { Env } from "./types"; + +export { MakimaAgent }; + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { + const url = new URL(request.url); + + // Use a singleton Durable Object — all traffic routes to one agent + // instance identified by a fixed name. This ensures a single persistent + // upstream WebSocket to the Makima server. + const agentId = env.MAKIMA_AGENT.idFromName("makima-relay-primary"); + const agent = env.MAKIMA_AGENT.get(agentId); + + // WebSocket upgrade path — downstream daemons connect here + if (url.pathname === "/ws" || url.pathname === "/ws/daemon") { + const upgradeHeader = request.headers.get("Upgrade"); + if (upgradeHeader !== "websocket") { + return new Response("Expected WebSocket upgrade", { status: 426 }); + } + // Forward the WebSocket upgrade to the Durable Object + return agent.fetch(request); + } + + // All other routes are forwarded to the Durable Object's onRequest handler + return agent.fetch(request); + }, +}; diff --git a/makima/cloudflare-agent/src/types.ts b/makima/cloudflare-agent/src/types.ts new file mode 100644 index 0000000..d2ab20a --- /dev/null +++ b/makima/cloudflare-agent/src/types.ts @@ -0,0 +1,148 @@ +/** + * Type definitions for the Makima Cloudflare Agent. + * + * These types mirror the daemon-server WebSocket protocol defined in + * makima/src/daemon/ws/protocol.rs. Only the subset relevant to the + * edge relay is included here. + */ + +// --------------------------------------------------------------------------- +// Worker Environment +// --------------------------------------------------------------------------- + +export interface Env { + /** Durable Object binding for MakimaAgent instances. */ + MAKIMA_AGENT: DurableObjectNamespace; + /** Makima server WebSocket URL (e.g. wss://api.makima.jp). */ + MAKIMA_SERVER_URL: string; + /** API key for authenticating with the Makima server. */ + MAKIMA_API_KEY: string; + /** Optional: human-readable name for this edge agent. */ + MAKIMA_AGENT_NAME?: string; +} + +// --------------------------------------------------------------------------- +// Agent State (persisted in Durable Object SQLite) +// --------------------------------------------------------------------------- + +export interface AgentState { + /** Whether the upstream WebSocket is currently connected. */ + connected: boolean; + /** ISO-8601 timestamp of last successful connection. */ + lastConnectedAt: string | null; + /** ISO-8601 timestamp of last heartbeat sent. */ + lastHeartbeatAt: string | null; + /** Number of consecutive reconnect failures. */ + reconnectAttempts: number; + /** Daemon ID assigned by the server after authentication. */ + daemonId: string | null; + /** Active task IDs being tracked through this relay. */ + activeTasks: string[]; +} + +export const DEFAULT_AGENT_STATE: AgentState = { + connected: false, + lastConnectedAt: null, + lastHeartbeatAt: null, + reconnectAttempts: 0, + daemonId: null, + activeTasks: [], +}; + +// --------------------------------------------------------------------------- +// Daemon → Server Messages (subset used by the relay) +// --------------------------------------------------------------------------- + +export type DaemonMessage = + | { type: "authenticate"; apiKey: string; machineId: string; hostname: string; maxConcurrentTasks: number } + | { type: "heartbeat"; activeTasks: string[] } + | { type: "taskOutput"; taskId: string; output: string; isPartial: boolean } + | { type: "taskStatusChange"; taskId: string; oldStatus: string; newStatus: string } + | { type: "taskProgress"; taskId: string; summary: string } + | { type: "taskComplete"; taskId: string; success: boolean; error?: string }; + +// --------------------------------------------------------------------------- +// Server → Daemon Commands (subset relevant to the relay) +// --------------------------------------------------------------------------- + +export type DaemonCommand = + | { type: "authenticated"; daemonId: string } + | { type: "spawnTask"; taskId: string; taskName: string; plan: string; repoUrl?: string; baseBranch?: string; targetBranch?: string; contractId?: string; depth: number; isOrchestrator: boolean } + | { type: "pauseTask"; taskId: string } + | { type: "resumeTask"; taskId: string } + | { type: "interruptTask"; taskId: string; graceful: boolean } + | { type: "sendMessage"; taskId: string; message: string }; + +// --------------------------------------------------------------------------- +// Task History Record (stored in SQLite) +// --------------------------------------------------------------------------- + +export interface TaskRecord { + /** UUID of the task. */ + taskId: string; + /** Human-readable task name. */ + taskName: string; + /** Current status: pending | dispatched | completed | failed. */ + status: string; + /** Which downstream daemon received this task. */ + dispatchedTo: string | null; + /** ISO-8601 timestamp when the task was received. */ + receivedAt: string; + /** ISO-8601 timestamp when the task was dispatched downstream. */ + dispatchedAt: string | null; + /** ISO-8601 timestamp when the task completed. */ + completedAt: string | null; + /** Error message if failed. */ + error: string | null; +} + +// --------------------------------------------------------------------------- +// Connected Downstream Daemon +// --------------------------------------------------------------------------- + +export interface DownstreamDaemon { + /** Unique ID for this downstream connection. */ + id: string; + /** WebSocket connection to this daemon. */ + ws: WebSocket; + /** Human-readable hostname. */ + hostname: string; + /** Maximum concurrent tasks this daemon supports. */ + maxConcurrentTasks: number; + /** Currently active task IDs on this daemon. */ + activeTasks: Set<string>; + /** ISO-8601 timestamp of last heartbeat received. */ + lastHeartbeat: string; +} + +// --------------------------------------------------------------------------- +// HTTP API Response Types +// --------------------------------------------------------------------------- + +export interface StatusResponse { + status: "ok" | "degraded" | "disconnected"; + agentName: string; + upstreamConnected: boolean; + daemonId: string | null; + lastHeartbeat: string | null; + connectedDaemons: number; + activeTasks: number; + totalTasksProcessed: number; +} + +export interface TaskHistoryResponse { + tasks: TaskRecord[]; + total: number; + limit: number; + offset: number; +} + +// --------------------------------------------------------------------------- +// RPC method types for the Agent +// --------------------------------------------------------------------------- + +export interface AgentRPC { + getStatus: () => StatusResponse; + getTaskHistory: (limit?: number, offset?: number) => TaskHistoryResponse; + reconnect: () => { success: boolean; message: string }; +} diff --git a/makima/cloudflare-agent/tsconfig.json b/makima/cloudflare-agent/tsconfig.json new file mode 100644 index 0000000..ed1dd65 --- /dev/null +++ b/makima/cloudflare-agent/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["@cloudflare/workers-types"], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/makima/cloudflare-agent/wrangler.toml b/makima/cloudflare-agent/wrangler.toml new file mode 100644 index 0000000..11f5c66 --- /dev/null +++ b/makima/cloudflare-agent/wrangler.toml @@ -0,0 +1,20 @@ +name = "makima-agent" +main = "src/index.ts" +compatibility_date = "2024-12-01" +compatibility_flags = ["nodejs_compat"] + +[observability] +enabled = true + +[vars] +# Non-secret configuration — override via .dev.vars for local dev +# MAKIMA_SERVER_URL = "wss://api.makima.jp" + +[durable_objects] +bindings = [ + { name = "MAKIMA_AGENT", class_name = "MakimaAgent" } +] + +[[migrations]] +tag = "v1" +new_sqlite_classes = ["MakimaAgent"] diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index 9556458..1bd0891 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -17,6 +17,7 @@ const NAV_LINKS: NavLink[] = [ { label: "Mesh", href: "/mesh", requiresAuth: true }, { label: "Daemons", href: "/daemons", requiresAuth: true }, { label: "History", href: "/history", requiresAuth: true }, + { label: "Daemon", href: "/daemon", requiresAuth: true }, ]; export function NavStrip() { diff --git a/makima/frontend/src/hooks/useMultiTaskSubscription.ts b/makima/frontend/src/hooks/useMultiTaskSubscription.ts index 41489c7..b229e90 100644 --- a/makima/frontend/src/hooks/useMultiTaskSubscription.ts +++ b/makima/frontend/src/hooks/useMultiTaskSubscription.ts @@ -31,8 +31,6 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption const backfilledTasksRef = useRef<Set<string>>(new Set()); const taskMapRef = useRef(taskMap); const enabledRef = useRef(enabled); - /** Track which task IDs have already been backfilled to avoid re-fetching */ - const backfilledTasksRef = useRef<Set<string>>(new Set()); // Keep refs in sync useEffect(() => { @@ -43,88 +41,6 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption enabledRef.current = enabled; }, [enabled]); - /** Max number of historical events to backfill per task */ - const MAX_BACKFILL_PER_TASK = 200; - - /** - * Convert a TaskEvent (from the REST API) into a MultiTaskOutputEntry. - * Only converts events with event_type === 'output'. - */ - const convertTaskEventToEntry = useCallback( - (event: TaskEvent): MultiTaskOutputEntry | null => { - if (event.eventType !== "output") return null; - const data = event.eventData; - if (!data) return null; - - return { - taskId: event.taskId, - messageType: (data.messageType as string) || "system", - content: (data.content as string) || "", - toolName: data.toolName as string | undefined, - toolInput: data.toolInput as Record<string, unknown> | undefined, - isError: data.isError as boolean | undefined, - costUsd: data.costUsd as number | undefined, - durationMs: data.durationMs as number | undefined, - isPartial: false, - taskLabel: - taskMapRef.current.get(event.taskId) || event.taskId, - receivedAt: new Date(event.createdAt).getTime(), - isBackfill: true, - }; - }, - [] - ); - - /** - * Backfill historical log entries for a task from the REST API. - * Only fetches once per task ID (tracked in backfilledTasksRef). - */ - const backfillTask = useCallback( - async (taskId: string) => { - if (backfilledTasksRef.current.has(taskId)) return; - backfilledTasksRef.current.add(taskId); - - try { - const response = await listTaskEvents(taskId); - const events = response.events; - - // The API returns events in DESC order; reverse to get chronological ASC - const chronologicalEvents = [...events].reverse(); - - // Filter to output events and convert, limiting to MAX_BACKFILL_PER_TASK - const backfillEntries: MultiTaskOutputEntry[] = []; - for (const event of chronologicalEvents) { - const entry = convertTaskEventToEntry(event); - if (entry) { - backfillEntries.push(entry); - if (backfillEntries.length >= MAX_BACKFILL_PER_TASK) break; - } - } - - if (backfillEntries.length === 0) return; - - // Prepend historical entries before any existing live entries for this task, - // maintaining overall chronological order across all tasks - setEntries((prev) => { - // Merge backfill entries with existing entries, maintaining chronological order - const merged = [...backfillEntries, ...prev]; - // Sort by receivedAt to ensure proper chronological ordering - merged.sort((a, b) => a.receivedAt - b.receivedAt); - // Trim to maxEntries - if (merged.length > maxEntries) { - return merged.slice(merged.length - maxEntries); - } - return merged; - }); - } catch (e) { - console.error(`Failed to backfill task events for ${taskId}:`, e); - // Remove from backfilled set so it can be retried - backfilledTasksRef.current.delete(taskId); - } - }, - [convertTaskEventToEntry, maxEntries] - ); - // Derive task IDs from the map, stabilized to avoid unnecessary effect triggers const taskIdsKey = useMemo(() => Array.from(taskMap.keys()).sort().join(","), [taskMap]); const taskIds = useMemo(() => Array.from(taskMap.keys()), [taskIdsKey]); // eslint-disable-line react-hooks/exhaustive-deps diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx index 32c05ba..a75d6a0 100644 --- a/makima/frontend/src/main.tsx +++ b/makima/frontend/src/main.tsx @@ -21,6 +21,7 @@ import SettingsPage from "./routes/settings"; import ContractFilePage from "./routes/contract-file"; import SpeakPage from "./routes/speak"; import DirectivesPage from "./routes/directives"; +import DaemonPage from "./routes/daemon"; createRoot(document.getElementById("root")!).render( <StrictMode> @@ -162,6 +163,14 @@ createRoot(document.getElementById("root")!).render( } /> <Route + path="/daemon" + element={ + <ProtectedRoute> + <DaemonPage /> + </ProtectedRoute> + } + /> + <Route path="/speak" element={ <ProtectedRoute> diff --git a/makima/frontend/src/routes/daemon.tsx b/makima/frontend/src/routes/daemon.tsx new file mode 100644 index 0000000..66154ad --- /dev/null +++ b/makima/frontend/src/routes/daemon.tsx @@ -0,0 +1,746 @@ +import { useState, useEffect, useCallback } from "react"; +import { useNavigate } from "react-router"; +import { Masthead } from "../components/Masthead"; +import { useAuth } from "../contexts/AuthContext"; +import { + listDaemons, + restartDaemon, + type Daemon, +} from "../lib/api"; + +// ============================================================================= +// Types +// ============================================================================= + +interface GitHubAsset { + name: string; + browser_download_url: string; + size: number; +} + +interface GitHubRelease { + tag_name: string; + name: string; + published_at: string; + html_url: string; + assets: GitHubAsset[]; +} + +interface PlatformDownload { + label: string; + arch: string; + pattern: string; + asset: GitHubAsset | null; + recommended: boolean; +} + +// ============================================================================= +// Helpers +// ============================================================================= + +function detectPlatform(): string { + const ua = navigator.userAgent.toLowerCase(); + const platform = navigator.platform?.toLowerCase() || ""; + + if (platform.includes("mac") || ua.includes("macintosh")) { + // Check for Apple Silicon + // navigator.platform is "MacIntel" even on ARM for some browsers, + // but we can check userAgent for hints or default to arm64 for modern Macs + if ( + ua.includes("arm") || + ua.includes("aarch64") || + // Chrome 93+ on ARM Macs reports this + (typeof navigator !== "undefined" && + "userAgentData" in navigator && + // @ts-expect-error -- userAgentData may not be typed + navigator.userAgentData?.platform === "macOS") + ) { + return "macos-arm64"; + } + return "macos-arm64"; // Default to ARM64 for modern Macs + } + + if (platform.includes("linux") || ua.includes("linux")) { + return "linux-x86_64"; + } + + return "linux-x86_64"; // fallback +} + +function formatBytes(bytes: number): string { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; +} + +function formatDate(dateStr: string): string { + return new Date(dateStr).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); +} + +// ============================================================================= +// Sub-components +// ============================================================================= + +function SectionHeader({ children }: { children: React.ReactNode }) { + return ( + <h2 className="text-[11px] font-mono uppercase tracking-wide text-[#8899aa] mb-3 pb-2 border-b border-[rgba(117,170,252,0.15)]"> + {children} + </h2> + ); +} + +function ErrorAlert({ children }: { children: React.ReactNode }) { + return ( + <div className="border border-red-700/50 bg-red-900/20 text-red-400 px-3 py-2 mb-4 font-mono text-xs"> + {children} + </div> + ); +} + +function CodeBlock({ children }: { children: React.ReactNode }) { + return ( + <code className="block bg-black/50 px-3 py-2 text-[11px] font-mono text-green-400 mb-2 overflow-x-auto"> + {children} + </code> + ); +} + +function StepNumber({ n }: { n: number }) { + return ( + <span className="inline-flex items-center justify-center w-5 h-5 border border-[rgba(117,170,252,0.35)] text-[10px] font-mono text-[#75aafc] mr-2 shrink-0"> + {n} + </span> + ); +} + +// ============================================================================= +// Download Section +// ============================================================================= + +function DownloadSection() { + const [release, setRelease] = useState<GitHubRelease | null>(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState<string | null>(null); + const [userPlatform] = useState(detectPlatform); + + useEffect(() => { + const fetchRelease = async () => { + try { + setLoading(true); + setError(null); + const res = await fetch( + "https://api.github.com/repos/soryu-co/makima/releases/latest" + ); + if (!res.ok) { + throw new Error(`GitHub API returned ${res.status}`); + } + const data: GitHubRelease = await res.json(); + setRelease(data); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to fetch release info" + ); + } finally { + setLoading(false); + } + }; + fetchRelease(); + }, []); + + const platforms: PlatformDownload[] = [ + { + label: "Linux x86_64", + arch: "linux-x86_64", + pattern: "linux-x86_64.tar.gz", + asset: null, + recommended: userPlatform === "linux-x86_64", + }, + { + label: "macOS Intel (x86_64)", + arch: "macos-x86_64", + pattern: "macos-x86_64.tar.gz", + asset: null, + recommended: userPlatform === "macos-x86_64", + }, + { + label: "macOS Apple Silicon (ARM64)", + arch: "macos-arm64", + pattern: "macos-arm64.tar.gz", + asset: null, + recommended: userPlatform === "macos-arm64", + }, + ]; + + // Match assets to platforms + if (release) { + for (const p of platforms) { + p.asset = + release.assets.find((a) => a.name.includes(p.pattern)) || null; + } + } + + // Sort recommended first + const sortedPlatforms = [...platforms].sort( + (a, b) => (b.recommended ? 1 : 0) - (a.recommended ? 1 : 0) + ); + + return ( + <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4"> + <SectionHeader>Download Daemon</SectionHeader> + + {loading && ( + <p className="text-[#7788aa] font-mono text-xs"> + Fetching latest release... + </p> + )} + + {error && <ErrorAlert>Failed to load release: {error}</ErrorAlert>} + + {release && ( + <> + <div className="flex items-center justify-between mb-4"> + <div className="flex items-center gap-3"> + <span className="text-[#9bc3ff] font-mono text-sm"> + {release.tag_name} + </span> + <span className="text-[#556677] font-mono text-[10px]"> + {formatDate(release.published_at)} + </span> + </div> + <a + href="https://github.com/soryu-co/makima/releases" + target="_blank" + rel="noopener noreferrer" + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors" + > + All Releases → + </a> + </div> + + <div className="space-y-2"> + {sortedPlatforms.map((p) => ( + <div + key={p.arch} + className={`border p-3 flex items-center justify-between ${ + p.recommended + ? "border-[rgba(117,170,252,0.5)] bg-[#0a1628]" + : "border-[rgba(117,170,252,0.15)] bg-[#0a1525]" + }`} + > + <div className="flex items-center gap-3"> + <span className="font-mono text-xs text-[#9bc3ff]"> + {p.label} + </span> + {p.recommended && ( + <span className="text-[9px] font-mono uppercase px-1.5 py-0.5 border border-[rgba(117,170,252,0.4)] text-[#75aafc] bg-[rgba(117,170,252,0.08)]"> + Detected + </span> + )} + {p.asset && ( + <span className="text-[10px] font-mono text-[#556677]"> + {formatBytes(p.asset.size)} + </span> + )} + </div> + {p.asset ? ( + <a + href={p.asset.browser_download_url} + className={`text-[10px] font-mono uppercase px-3 py-1.5 border transition-colors ${ + p.recommended + ? "text-[#9bc3ff] border-[rgba(117,170,252,0.5)] bg-[rgba(117,170,252,0.1)] hover:bg-[rgba(117,170,252,0.2)]" + : "text-[#75aafc] border-[rgba(117,170,252,0.25)] hover:bg-[rgba(117,170,252,0.1)]" + }`} + download + > + Download + </a> + ) : ( + <span className="text-[10px] font-mono text-[#556677]"> + Not available + </span> + )} + </div> + ))} + </div> + </> + )} + </section> + ); +} + +// ============================================================================= +// Setup Instructions Section +// ============================================================================= + +function SetupSection() { + const [showConfig, setShowConfig] = useState(false); + + return ( + <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4"> + <SectionHeader>Setup Instructions</SectionHeader> + <div className="space-y-3"> + <div className="flex items-start"> + <StepNumber n={1} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Download the binary for your platform above + </p> + </div> + </div> + + <div className="flex items-start"> + <StepNumber n={2} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Extract the archive + </p> + <CodeBlock>tar xzf makima-*.tar.gz</CodeBlock> + </div> + </div> + + <div className="flex items-start"> + <StepNumber n={3} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Move to PATH + </p> + <CodeBlock>sudo mv makima /usr/local/bin/</CodeBlock> + </div> + </div> + + <div className="flex items-start"> + <StepNumber n={4} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Set your API key ( + <a + href="/settings" + className="text-[#75aafc] hover:text-[#9bc3ff] transition-colors" + > + generate one in Settings + </a> + ) + </p> + <CodeBlock>export MAKIMA_API_KEY="your-key"</CodeBlock> + </div> + </div> + + <div className="flex items-start"> + <StepNumber n={5} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Set server URL + </p> + <CodeBlock> + export MAKIMA_DAEMON_SERVER_URL="ws://your-server:8080" + </CodeBlock> + </div> + </div> + + <div className="flex items-start"> + <StepNumber n={6} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Run the daemon + </p> + <CodeBlock>makima daemon</CodeBlock> + </div> + </div> + </div> + + {/* Config file alternative */} + <div className="mt-4 pt-3 border-t border-[rgba(117,170,252,0.1)]"> + <button + onClick={() => setShowConfig(!showConfig)} + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors bg-transparent border-none cursor-pointer p-0" + > + {showConfig ? "- Hide" : "+"} Config file alternative + </button> + {showConfig && ( + <div className="mt-3"> + <p className="text-[#7788aa] font-mono text-[10px] mb-2"> + Create <code className="text-green-400">makima-daemon.toml</code>{" "} + in the working directory: + </p> + <code className="block bg-black/50 px-3 py-2 text-[11px] font-mono text-green-400 whitespace-pre overflow-x-auto"> + {`[daemon] +api_key = "your-key" +server_url = "ws://your-server:8080" +max_concurrent_tasks = 4`} + </code> + </div> + )} + </div> + </section> + ); +} + +// ============================================================================= +// Cloudflare Edge Deployment Section +// ============================================================================= + +function CloudflareAgentSection() { + const [showSetup, setShowSetup] = useState(false); + + const benefits = [ + { + label: "Global edge presence", + desc: "Lower latency from 300+ Cloudflare locations worldwide", + }, + { + label: "Auto-scaling & hibernation", + desc: "Cost-efficient — only runs when needed", + }, + { + label: "WebSocket relay", + desc: "Coordinate remote daemon instances through persistent connections", + }, + { + label: "Durable Objects", + desc: "Built on Cloudflare's stateful edge compute primitives", + }, + ]; + + return ( + <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4"> + <SectionHeader>Edge Deployment</SectionHeader> + + <p className="text-[#7788aa] font-mono text-[10px] mb-4 leading-relaxed"> + Deploy a lightweight Makima relay agent on Cloudflare's edge network for + global, low-latency daemon coordination. Ideal for distributed teams or + production deployments requiring high availability. + </p> + + {/* Benefits */} + <div className="space-y-2 mb-4"> + {benefits.map((b) => ( + <div + key={b.label} + className="flex items-start gap-2 text-[10px] font-mono" + > + <span className="text-[#75aafc] mt-px shrink-0">▸</span> + <div> + <span className="text-[#9bc3ff]">{b.label}</span> + <span className="text-[#556677] ml-1">— {b.desc}</span> + </div> + </div> + ))} + </div> + + {/* Quick Setup */} + <div className="border-t border-[rgba(117,170,252,0.1)] pt-3"> + <button + onClick={() => setShowSetup(!showSetup)} + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors bg-transparent border-none cursor-pointer p-0" + > + {showSetup ? "- Hide" : "+"} Quick setup + </button> + {showSetup && ( + <div className="mt-3 space-y-3"> + <div className="flex items-start"> + <StepNumber n={1} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Navigate to the Cloudflare agent directory + </p> + <CodeBlock>cd makima/cloudflare-agent</CodeBlock> + </div> + </div> + <div className="flex items-start"> + <StepNumber n={2} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Run the setup script + </p> + <CodeBlock>./setup.sh</CodeBlock> + </div> + </div> + <div className="flex items-start"> + <StepNumber n={3} /> + <div className="flex-1"> + <p className="text-[#7788aa] font-mono text-[10px] mb-1"> + Deploy to Cloudflare + </p> + <CodeBlock>npx wrangler deploy</CodeBlock> + </div> + </div> + </div> + )} + </div> + + {/* Link to repo */} + <div className="mt-4 pt-3 border-t border-[rgba(117,170,252,0.1)] flex items-center justify-between"> + <span className="text-[10px] font-mono text-[#556677]"> + Full documentation & source + </span> + <a + href="https://github.com/soryu-co/soryu/tree/master/makima/cloudflare-agent" + target="_blank" + rel="noopener noreferrer" + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors px-3 py-1.5 border border-[rgba(117,170,252,0.25)] hover:bg-[rgba(117,170,252,0.1)]" + > + View on GitHub → + </a> + </div> + </section> + ); +} + +// ============================================================================= +// Connected Daemons Section +// ============================================================================= + +function ConnectedDaemonsSection() { + const [daemons, setDaemons] = useState<Daemon[]>([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState<string | null>(null); + const [restartingDaemonId, setRestartingDaemonId] = useState<string | null>( + null + ); + const [restartConfirmDaemonId, setRestartConfirmDaemonId] = useState< + string | null + >(null); + + const loadDaemons = useCallback(async () => { + try { + setError(null); + const response = await listDaemons(); + setDaemons(response.daemons); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to load daemons" + ); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + loadDaemons(); + }, [loadDaemons]); + + // Auto-refresh every 30 seconds + useEffect(() => { + const interval = setInterval(() => { + loadDaemons(); + }, 30000); + return () => clearInterval(interval); + }, [loadDaemons]); + + const handleRestartDaemon = async (id: string) => { + try { + setRestartingDaemonId(id); + setError(null); + await restartDaemon(id); + setRestartConfirmDaemonId(null); + // Daemon will restart, so refresh the list after a short delay + setTimeout(() => { + loadDaemons(); + }, 2000); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to restart daemon" + ); + } finally { + setRestartingDaemonId(null); + } + }; + + return ( + <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4"> + <div className="flex items-center justify-between mb-3 pb-2 border-b border-[rgba(117,170,252,0.15)]"> + <div className="flex items-center gap-2"> + <h2 className="text-[11px] font-mono uppercase tracking-wide text-[#8899aa]"> + Connected Daemons + </h2> + {daemons.length > 0 && ( + <span className="text-[10px] font-mono text-[#556677]"> + ({daemons.filter((d) => d.status === "connected").length}{" "} + connected / {daemons.length} total) + </span> + )} + </div> + <button + onClick={loadDaemons} + disabled={loading} + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] disabled:opacity-50 bg-transparent border-none cursor-pointer p-0" + title="Refresh" + > + {loading ? "..." : "\u21BB"} + </button> + </div> + + {error && <ErrorAlert>{error}</ErrorAlert>} + + {loading && daemons.length === 0 ? ( + <p className="text-[#7788aa] font-mono text-xs">Loading...</p> + ) : daemons.length === 0 ? ( + <div className="text-center py-6"> + <p className="text-[#7788aa] font-mono text-xs mb-2"> + No daemons connected + </p> + <p className="text-[#556677] font-mono text-[10px]"> + Follow the setup instructions above to connect a daemon + </p> + </div> + ) : ( + <div className="space-y-2"> + {daemons.map((daemon) => ( + <div + key={daemon.id} + className="border border-[rgba(117,170,252,0.15)] bg-[#0a1525] p-3" + > + <div className="flex items-center justify-between mb-2"> + <span className="font-mono text-xs text-[#9bc3ff]"> + {daemon.hostname || "Unknown Host"} + </span> + <div className="flex items-center gap-2"> + <span + className={`text-[10px] font-mono uppercase px-2 py-0.5 border ${ + daemon.status === "connected" + ? "text-green-400 border-green-700/50 bg-green-900/20" + : daemon.status === "unhealthy" + ? "text-yellow-400 border-yellow-700/50 bg-yellow-900/20" + : "text-[#8899aa] border-[rgba(117,170,252,0.25)]" + }`} + > + {daemon.status} + </span> + </div> + </div> + <div className="font-mono text-[10px] text-[#7788aa] space-y-1"> + <div className="flex justify-between"> + <span>Tasks</span> + <span className="text-[#9bc3ff]"> + {daemon.currentTaskCount} / {daemon.maxConcurrentTasks} + </span> + </div> + <div className="flex justify-between"> + <span>Connected</span> + <span className="text-[#75aafc]"> + {new Date(daemon.connectedAt).toLocaleString()} + </span> + </div> + {daemon.machineId && ( + <div className="flex justify-between"> + <span>Machine</span> + <span + className="text-[#556677] truncate ml-2" + title={daemon.machineId} + > + {daemon.machineId.substring(0, 16)}... + </span> + </div> + )} + </div> + {/* Restart Section */} + {daemon.status === "connected" && ( + <div className="mt-3 pt-2 border-t border-[rgba(117,170,252,0.1)]"> + {restartConfirmDaemonId === daemon.id ? ( + <div className="flex items-center justify-between gap-2"> + <span className="text-[10px] font-mono text-yellow-400"> + Restart daemon? Running tasks will be interrupted. + </span> + <div className="flex gap-2"> + <button + onClick={() => setRestartConfirmDaemonId(null)} + className="text-[10px] font-mono text-[#7788aa] hover:text-[#9bc3ff] px-2 py-1 bg-transparent border-none cursor-pointer" + disabled={restartingDaemonId === daemon.id} + > + Cancel + </button> + <button + onClick={() => handleRestartDaemon(daemon.id)} + disabled={restartingDaemonId === daemon.id} + className="text-[10px] font-mono text-red-400 hover:text-red-300 px-2 py-1 border border-red-700/50 bg-red-900/20 disabled:opacity-50 cursor-pointer" + > + {restartingDaemonId === daemon.id + ? "Restarting..." + : "Confirm"} + </button> + </div> + </div> + ) : ( + <button + onClick={() => setRestartConfirmDaemonId(daemon.id)} + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] bg-transparent border-none cursor-pointer p-0" + > + ⟳ Restart Daemon + </button> + )} + </div> + )} + </div> + ))} + </div> + )} + </section> + ); +} + +// ============================================================================= +// Main Page +// ============================================================================= + +export default function DaemonPage() { + const { + isAuthenticated, + isAuthConfigured, + isLoading: authLoading, + } = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + if (!authLoading && isAuthConfigured && !isAuthenticated) { + navigate("/login"); + } + }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); + + if (authLoading) { + return ( + <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> + <Masthead showNav /> + <main className="flex-1 flex items-center justify-center"> + <p className="text-[#7788aa] font-mono text-sm">Loading...</p> + </main> + </div> + ); + } + + return ( + <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> + <Masthead showNav /> + <main className="flex-1 px-4 py-6 max-w-4xl mx-auto w-full"> + {/* Page header */} + <div className="mb-6"> + <h1 className="text-sm font-mono uppercase tracking-wide text-[#9bc3ff] mb-1"> + Daemon Management + </h1> + <p className="text-[#556677] font-mono text-[10px]"> + Download, configure, and monitor Makima daemons + </p> + </div> + + <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> + {/* Left Column: Downloads & Setup */} + <div className="space-y-6"> + <DownloadSection /> + <SetupSection /> + </div> + + {/* Right Column: Edge Deployment & Connected Daemons */} + <div className="space-y-6"> + <ConnectedDaemonsSection /> + <CloudflareAgentSection /> + </div> + </div> + </main> + </div> + ); +} diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 68f2eac..410931d 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/stepnode.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/orchestratorstepnode.tsx","./src/components/directives/stepnode.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/daemon.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"errors":true,"version":"5.9.3"}
\ No newline at end of file |
