repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | import { Hono } from 'hono'; |
| #2 | import type { AppEnv } from '../types'; |
| #3 | import { findExistingMoltbotProcess } from '../gateway'; |
| #4 | |
| #5 | /** |
| #6 | * Debug routes for inspecting container state |
| #7 | * Note: These routes should be protected by Cloudflare Access middleware |
| #8 | * when mounted in the main app |
| #9 | */ |
| #10 | const debug = new Hono<AppEnv>(); |
| #11 | |
| #12 | // GET /debug/version - Returns version info from inside the container |
| #13 | debug.get('/version', async (c) => { |
| #14 | const sandbox = c.get('sandbox'); |
| #15 | try { |
| #16 | // Get moltbot version (CLI is still named clawdbot until upstream renames) |
| #17 | const versionProcess = await sandbox.startProcess('clawdbot --version'); |
| #18 | await new Promise(resolve => setTimeout(resolve, 500)); |
| #19 | const versionLogs = await versionProcess.getLogs(); |
| #20 | const moltbotVersion = (versionLogs.stdout || versionLogs.stderr || '').trim(); |
| #21 | |
| #22 | // Get node version |
| #23 | const nodeProcess = await sandbox.startProcess('node --version'); |
| #24 | await new Promise(resolve => setTimeout(resolve, 500)); |
| #25 | const nodeLogs = await nodeProcess.getLogs(); |
| #26 | const nodeVersion = (nodeLogs.stdout || '').trim(); |
| #27 | |
| #28 | return c.json({ |
| #29 | moltbot_version: moltbotVersion, |
| #30 | node_version: nodeVersion, |
| #31 | }); |
| #32 | } catch (error) { |
| #33 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; |
| #34 | return c.json({ status: 'error', message: `Failed to get version info: ${errorMessage}` }, 500); |
| #35 | } |
| #36 | }); |
| #37 | |
| #38 | // GET /debug/processes - List all processes with optional logs |
| #39 | debug.get('/processes', async (c) => { |
| #40 | const sandbox = c.get('sandbox'); |
| #41 | try { |
| #42 | const processes = await sandbox.listProcesses(); |
| #43 | const includeLogs = c.req.query('logs') === 'true'; |
| #44 | |
| #45 | const processData = await Promise.all(processes.map(async p => { |
| #46 | const data: Record<string, unknown> = { |
| #47 | id: p.id, |
| #48 | command: p.command, |
| #49 | status: p.status, |
| #50 | startTime: p.startTime?.toISOString(), |
| #51 | endTime: p.endTime?.toISOString(), |
| #52 | exitCode: p.exitCode, |
| #53 | }; |
| #54 | |
| #55 | if (includeLogs) { |
| #56 | try { |
| #57 | const logs = await p.getLogs(); |
| #58 | data.stdout = logs.stdout || ''; |
| #59 | data.stderr = logs.stderr || ''; |
| #60 | } catch { |
| #61 | data.logs_error = 'Failed to retrieve logs'; |
| #62 | } |
| #63 | } |
| #64 | |
| #65 | return data; |
| #66 | })); |
| #67 | |
| #68 | // Sort by status (running first, then starting, completed, failed) |
| #69 | // Within each status, sort by startTime descending (newest first) |
| #70 | const statusOrder: Record<string, number> = { |
| #71 | 'running': 0, |
| #72 | 'starting': 1, |
| #73 | 'completed': 2, |
| #74 | 'failed': 3, |
| #75 | }; |
| #76 | |
| #77 | processData.sort((a, b) => { |
| #78 | const statusA = statusOrder[a.status as string] ?? 99; |
| #79 | const statusB = statusOrder[b.status as string] ?? 99; |
| #80 | if (statusA !== statusB) { |
| #81 | return statusA - statusB; |
| #82 | } |
| #83 | // Within same status, sort by startTime descending |
| #84 | const timeA = a.startTime as string || ''; |
| #85 | const timeB = b.startTime as string || ''; |
| #86 | return timeB.localeCompare(timeA); |
| #87 | }); |
| #88 | |
| #89 | return c.json({ count: processes.length, processes: processData }); |
| #90 | } catch (error) { |
| #91 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; |
| #92 | return c.json({ error: errorMessage }, 500); |
| #93 | } |
| #94 | }); |
| #95 | |
| #96 | // GET /debug/gateway-api - Probe the moltbot gateway HTTP API |
| #97 | debug.get('/gateway-api', async (c) => { |
| #98 | const sandbox = c.get('sandbox'); |
| #99 | const path = c.req.query('path') || '/'; |
| #100 | const MOLTBOT_PORT = 18789; |
| #101 | |
| #102 | try { |
| #103 | const url = `http://localhost:${MOLTBOT_PORT}${path}`; |
| #104 | const response = await sandbox.containerFetch(new Request(url), MOLTBOT_PORT); |
| #105 | const contentType = response.headers.get('content-type') || ''; |
| #106 | |
| #107 | let body: string | object; |
| #108 | if (contentType.includes('application/json')) { |
| #109 | body = await response.json(); |
| #110 | } else { |
| #111 | body = await response.text(); |
| #112 | } |
| #113 | |
| #114 | return c.json({ |
| #115 | path, |
| #116 | status: response.status, |
| #117 | contentType, |
| #118 | body, |
| #119 | }); |
| #120 | } catch (error) { |
| #121 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; |
| #122 | return c.json({ error: errorMessage, path }, 500); |
| #123 | } |
| #124 | }); |
| #125 | |
| #126 | // GET /debug/cli - Test moltbot CLI commands (CLI is still named clawdbot) |
| #127 | debug.get('/cli', async (c) => { |
| #128 | const sandbox = c.get('sandbox'); |
| #129 | const cmd = c.req.query('cmd') || 'clawdbot --help'; |
| #130 | |
| #131 | try { |
| #132 | const proc = await sandbox.startProcess(cmd); |
| #133 | |
| #134 | // Wait longer for command to complete |
| #135 | let attempts = 0; |
| #136 | while (attempts < 30) { |
| #137 | await new Promise(r => setTimeout(r, 500)); |
| #138 | if (proc.status !== 'running') break; |
| #139 | attempts++; |
| #140 | } |
| #141 | |
| #142 | const logs = await proc.getLogs(); |
| #143 | return c.json({ |
| #144 | command: cmd, |
| #145 | status: proc.status, |
| #146 | exitCode: proc.exitCode, |
| #147 | attempts, |
| #148 | stdout: logs.stdout || '', |
| #149 | stderr: logs.stderr || '', |
| #150 | }); |
| #151 | } catch (error) { |
| #152 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; |
| #153 | return c.json({ error: errorMessage, command: cmd }, 500); |
| #154 | } |
| #155 | }); |
| #156 | |
| #157 | // GET /debug/logs - Returns container logs for debugging |
| #158 | debug.get('/logs', async (c) => { |
| #159 | const sandbox = c.get('sandbox'); |
| #160 | try { |
| #161 | const processId = c.req.query('id'); |
| #162 | let process = null; |
| #163 | |
| #164 | if (processId) { |
| #165 | const processes = await sandbox.listProcesses(); |
| #166 | process = processes.find(p => p.id === processId); |
| #167 | if (!process) { |
| #168 | return c.json({ |
| #169 | status: 'not_found', |
| #170 | message: `Process ${processId} not found`, |
| #171 | stdout: '', |
| #172 | stderr: '', |
| #173 | }, 404); |
| #174 | } |
| #175 | } else { |
| #176 | process = await findExistingMoltbotProcess(sandbox); |
| #177 | if (!process) { |
| #178 | return c.json({ |
| #179 | status: 'no_process', |
| #180 | message: 'No Moltbot process is currently running', |
| #181 | stdout: '', |
| #182 | stderr: '', |
| #183 | }); |
| #184 | } |
| #185 | } |
| #186 | |
| #187 | const logs = await process.getLogs(); |
| #188 | return c.json({ |
| #189 | status: 'ok', |
| #190 | process_id: process.id, |
| #191 | process_status: process.status, |
| #192 | stdout: logs.stdout || '', |
| #193 | stderr: logs.stderr || '', |
| #194 | }); |
| #195 | } catch (error) { |
| #196 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; |
| #197 | return c.json({ |
| #198 | status: 'error', |
| #199 | message: `Failed to get logs: ${errorMessage}`, |
| #200 | stdout: '', |
| #201 | stderr: '', |
| #202 | }, 500); |
| #203 | } |
| #204 | }); |
| #205 | |
| #206 | // GET /debug/ws-test - Interactive WebSocket debug page |
| #207 | debug.get('/ws-test', async (c) => { |
| #208 | const host = c.req.header('host') || 'localhost'; |
| #209 | const protocol = c.req.header('x-forwarded-proto') || 'https'; |
| #210 | const wsProtocol = protocol === 'https' ? 'wss' : 'ws'; |
| #211 | |
| #212 | const html = `<!DOCTYPE html> |
| #213 | <html> |
| #214 | <head> |
| #215 | <title>WebSocket Debug</title> |
| #216 | <style> |
| #217 | body { font-family: monospace; padding: 20px; background: #1a1a1a; color: #0f0; } |
| #218 | #log { white-space: pre-wrap; background: #000; padding: 10px; height: 400px; overflow-y: auto; border: 1px solid #333; } |
| #219 | button { margin: 5px; padding: 10px; } |
| #220 | input { padding: 10px; width: 300px; } |
| #221 | .error { color: #f00; } |
| #222 | .sent { color: #0ff; } |
| #223 | .received { color: #0f0; } |
| #224 | .info { color: #ff0; } |
| #225 | </style> |
| #226 | </head> |
| #227 | <body> |
| #228 | <h1>WebSocket Debug Tool</h1> |
| #229 | <div> |
| #230 | <button id="connect">Connect</button> |
| #231 | <button id="disconnect" disabled>Disconnect</button> |
| #232 | <button id="clear">Clear Log</button> |
| #233 | </div> |
| #234 | <div style="margin: 10px 0;"> |
| #235 | <input id="message" placeholder="JSON message to send..." /> |
| #236 | <button id="send" disabled>Send</button> |
| #237 | </div> |
| #238 | <div style="margin: 10px 0;"> |
| #239 | <button id="sendConnect" disabled>Send Connect Frame</button> |
| #240 | </div> |
| #241 | <div id="log"></div> |
| #242 | |
| #243 | <script> |
| #244 | const wsUrl = '${wsProtocol}://${host}/'; |
| #245 | let ws = null; |
| #246 | |
| #247 | const log = (msg, className = '') => { |
| #248 | const logEl = document.getElementById('log'); |
| #249 | const time = new Date().toISOString().substr(11, 12); |
| #250 | logEl.innerHTML += '<span class="' + className + '">[' + time + '] ' + msg + '</span>\\n'; |
| #251 | logEl.scrollTop = logEl.scrollHeight; |
| #252 | }; |
| #253 | |
| #254 | document.getElementById('connect').onclick = () => { |
| #255 | log('Connecting to ' + wsUrl + '...', 'info'); |
| #256 | ws = new WebSocket(wsUrl); |
| #257 | |
| #258 | ws.onopen = () => { |
| #259 | log('Connected!', 'info'); |
| #260 | document.getElementById('connect').disabled = true; |
| #261 | document.getElementById('disconnect').disabled = false; |
| #262 | document.getElementById('send').disabled = false; |
| #263 | document.getElementById('sendConnect').disabled = false; |
| #264 | }; |
| #265 | |
| #266 | ws.onmessage = (e) => { |
| #267 | log('RECV: ' + e.data, 'received'); |
| #268 | try { |
| #269 | const parsed = JSON.parse(e.data); |
| #270 | log(' Parsed: ' + JSON.stringify(parsed, null, 2), 'received'); |
| #271 | } catch {} |
| #272 | }; |
| #273 | |
| #274 | ws.onerror = (e) => { |
| #275 | log('ERROR: ' + JSON.stringify(e), 'error'); |
| #276 | }; |
| #277 | |
| #278 | ws.onclose = (e) => { |
| #279 | log('Closed: code=' + e.code + ' reason=' + e.reason, 'info'); |
| #280 | document.getElementById('connect').disabled = false; |
| #281 | document.getElementById('disconnect').disabled = true; |
| #282 | document.getElementById('send').disabled = true; |
| #283 | document.getElementById('sendConnect').disabled = true; |
| #284 | ws = null; |
| #285 | }; |
| #286 | }; |
| #287 | |
| #288 | document.getElementById('disconnect').onclick = () => { |
| #289 | if (ws) ws.close(); |
| #290 | }; |
| #291 | |
| #292 | document.getElementById('clear').onclick = () => { |
| #293 | document.getElementById('log').innerHTML = ''; |
| #294 | }; |
| #295 | |
| #296 | document.getElementById('send').onclick = () => { |
| #297 | const msg = document.getElementById('message').value; |
| #298 | if (ws && msg) { |
| #299 | log('SEND: ' + msg, 'sent'); |
| #300 | ws.send(msg); |
| #301 | } |
| #302 | }; |
| #303 | |
| #304 | document.getElementById('sendConnect').onclick = () => { |
| #305 | if (!ws) return; |
| #306 | const connectFrame = { |
| #307 | type: 'req', |
| #308 | id: 'debug-' + Date.now(), |
| #309 | method: 'connect', |
| #310 | params: { |
| #311 | minProtocol: 1, |
| #312 | maxProtocol: 1, |
| #313 | client: { |
| #314 | id: 'debug-tool', |
| #315 | displayName: 'Debug Tool', |
| #316 | version: '1.0.0', |
| #317 | mode: 'webchat', |
| #318 | platform: 'web' |
| #319 | }, |
| #320 | role: 'operator', |
| #321 | scopes: [] |
| #322 | } |
| #323 | }; |
| #324 | const msg = JSON.stringify(connectFrame); |
| #325 | log('SEND Connect Frame: ' + msg, 'sent'); |
| #326 | ws.send(msg); |
| #327 | }; |
| #328 | |
| #329 | document.getElementById('message').onkeypress = (e) => { |
| #330 | if (e.key === 'Enter') document.getElementById('send').click(); |
| #331 | }; |
| #332 | </script> |
| #333 | </body> |
| #334 | </html>`; |
| #335 | |
| #336 | return c.html(html); |
| #337 | }); |
| #338 | |
| #339 | // GET /debug/env - Show environment configuration (sanitized) |
| #340 | debug.get('/env', async (c) => { |
| #341 | return c.json({ |
| #342 | has_anthropic_key: !!c.env.ANTHROPIC_API_KEY, |
| #343 | has_openai_key: !!c.env.OPENAI_API_KEY, |
| #344 | has_gateway_token: !!c.env.MOLTBOT_GATEWAY_TOKEN, |
| #345 | has_r2_access_key: !!c.env.R2_ACCESS_KEY_ID, |
| #346 | has_r2_secret_key: !!c.env.R2_SECRET_ACCESS_KEY, |
| #347 | has_cf_account_id: !!c.env.CF_ACCOUNT_ID, |
| #348 | dev_mode: c.env.DEV_MODE, |
| #349 | debug_routes: c.env.DEBUG_ROUTES, |
| #350 | bind_mode: c.env.CLAWDBOT_BIND_MODE, |
| #351 | cf_access_team_domain: c.env.CF_ACCESS_TEAM_DOMAIN, |
| #352 | has_cf_access_aud: !!c.env.CF_ACCESS_AUD, |
| #353 | }); |
| #354 | }); |
| #355 | |
| #356 | // GET /debug/container-config - Read the moltbot config from inside the container |
| #357 | debug.get('/container-config', async (c) => { |
| #358 | const sandbox = c.get('sandbox'); |
| #359 | |
| #360 | try { |
| #361 | const proc = await sandbox.startProcess('cat /root/.clawdbot/clawdbot.json'); |
| #362 | |
| #363 | let attempts = 0; |
| #364 | while (attempts < 10) { |
| #365 | await new Promise(r => setTimeout(r, 200)); |
| #366 | if (proc.status !== 'running') break; |
| #367 | attempts++; |
| #368 | } |
| #369 | |
| #370 | const logs = await proc.getLogs(); |
| #371 | const stdout = logs.stdout || ''; |
| #372 | const stderr = logs.stderr || ''; |
| #373 | |
| #374 | let config = null; |
| #375 | try { |
| #376 | config = JSON.parse(stdout); |
| #377 | } catch { |
| #378 | // Not valid JSON |
| #379 | } |
| #380 | |
| #381 | return c.json({ |
| #382 | status: proc.status, |
| #383 | exitCode: proc.exitCode, |
| #384 | config, |
| #385 | raw: config ? undefined : stdout, |
| #386 | stderr, |
| #387 | }); |
| #388 | } catch (error) { |
| #389 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; |
| #390 | return c.json({ error: errorMessage }, 500); |
| #391 | } |
| #392 | }); |
| #393 | |
| #394 | export { debug }; |
| #395 |