abstractdrift Claude Opus 4.6 commited on
Commit
be80b82
·
unverified ·
1 Parent(s): 6331d61

fix: macOS compatibility for status commands and gateway client id

Browse files

Replace Linux-only commands (uptime -s, free -m, df --output=pcent) with
cross-platform alternatives using process.platform detection and Node.js
os module. Rename gateway client ID from control-ui to openclaw-control-ui.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

.env.example CHANGED
@@ -65,7 +65,7 @@ NEXT_PUBLIC_GATEWAY_PROTOCOL=
65
  NEXT_PUBLIC_GATEWAY_URL=
66
  # NEXT_PUBLIC_GATEWAY_TOKEN= # Optional, set if gateway requires auth token
67
  # Gateway client id used in websocket handshake (role=operator UI client).
68
- NEXT_PUBLIC_GATEWAY_CLIENT_ID=control-ui
69
 
70
  # === Data Paths (all optional, defaults to .data/ in project root) ===
71
  # MISSION_CONTROL_DATA_DIR=.data
 
65
  NEXT_PUBLIC_GATEWAY_URL=
66
  # NEXT_PUBLIC_GATEWAY_TOKEN= # Optional, set if gateway requires auth token
67
  # Gateway client id used in websocket handshake (role=operator UI client).
68
+ NEXT_PUBLIC_GATEWAY_CLIENT_ID=openclaw-control-ui
69
 
70
  # === Data Paths (all optional, defaults to .data/ in project root) ===
71
  # MISSION_CONTROL_DATA_DIR=.data
src/app/api/status/route.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { NextRequest, NextResponse } from 'next/server'
2
  import net from 'node:net'
 
3
  import { existsSync, statSync } from 'node:fs'
4
  import path from 'node:path'
5
  import { runCommand, runOpenClaw, runClawdbot } from '@/lib/command'
@@ -195,29 +196,48 @@ async function getSystemStatus(workspaceId: number) {
195
  }
196
 
197
  try {
198
- // System uptime
199
- const { stdout: uptimeOutput } = await runCommand('uptime', ['-s'], {
200
- timeoutMs: 3000
201
- })
202
- const bootTime = new Date(uptimeOutput.trim())
203
- status.uptime = Date.now() - bootTime.getTime()
 
 
 
 
 
 
 
 
 
 
 
204
  } catch (error) {
205
  logger.error({ err: error }, 'Error getting uptime')
206
  }
207
 
208
  try {
209
- // Memory info
210
- const { stdout: memOutput } = await runCommand('free', ['-m'], {
211
- timeoutMs: 3000
212
- })
213
- const memLines = memOutput.split('\n')
214
- const memLine = memLines.find(line => line.startsWith('Mem:'))
215
- if (memLine) {
216
- const parts = memLine.split(/\s+/)
217
- status.memory = {
218
- total: parseInt(parts[1]) || 0,
219
- used: parseInt(parts[2]) || 0,
220
- available: parseInt(parts[6]) || 0
 
 
 
 
 
 
 
 
221
  }
222
  }
223
  } catch (error) {
@@ -414,14 +434,17 @@ async function performHealthCheck() {
414
  })
415
  }
416
 
417
- // Check disk space
418
  try {
419
- const { stdout } = await runCommand('df', ['/', '--output=pcent'], {
420
  timeoutMs: 3000
421
  })
422
  const lines = stdout.trim().split('\n')
423
  const last = lines[lines.length - 1] || ''
424
- const usagePercent = parseInt(last.replace('%', '').trim() || '0')
 
 
 
425
 
426
  health.checks.push({
427
  name: 'Disk Space',
@@ -436,15 +459,21 @@ async function performHealthCheck() {
436
  })
437
  }
438
 
439
- // Check memory usage
440
  try {
441
- const { stdout } = await runCommand('free', ['-m'], { timeoutMs: 3000 })
442
- const lines = stdout.split('\n')
443
- const memLine = lines.find((line) => line.startsWith('Mem:'))
444
- const parts = (memLine || '').split(/\s+/)
445
- const total = parseInt(parts[1] || '0')
446
- const available = parseInt(parts[6] || '0')
447
- const usagePercent = Math.round(((total - available) / total) * 100)
 
 
 
 
 
 
448
 
449
  health.checks.push({
450
  name: 'Memory Usage',
 
1
  import { NextRequest, NextResponse } from 'next/server'
2
  import net from 'node:net'
3
+ import os from 'node:os'
4
  import { existsSync, statSync } from 'node:fs'
5
  import path from 'node:path'
6
  import { runCommand, runOpenClaw, runClawdbot } from '@/lib/command'
 
196
  }
197
 
198
  try {
199
+ // System uptime (cross-platform)
200
+ if (process.platform === 'darwin') {
201
+ const { stdout } = await runCommand('sysctl', ['-n', 'kern.boottime'], {
202
+ timeoutMs: 3000
203
+ })
204
+ // Output format: { sec = 1234567890, usec = 0 } ...
205
+ const match = stdout.match(/sec\s*=\s*(\d+)/)
206
+ if (match) {
207
+ status.uptime = Date.now() - parseInt(match[1]) * 1000
208
+ }
209
+ } else {
210
+ const { stdout } = await runCommand('uptime', ['-s'], {
211
+ timeoutMs: 3000
212
+ })
213
+ const bootTime = new Date(stdout.trim())
214
+ status.uptime = Date.now() - bootTime.getTime()
215
+ }
216
  } catch (error) {
217
  logger.error({ err: error }, 'Error getting uptime')
218
  }
219
 
220
  try {
221
+ // Memory info (cross-platform)
222
+ if (process.platform === 'darwin') {
223
+ const totalBytes = os.totalmem()
224
+ const freeBytes = os.freemem()
225
+ const totalMB = Math.round(totalBytes / (1024 * 1024))
226
+ const usedMB = Math.round((totalBytes - freeBytes) / (1024 * 1024))
227
+ const availableMB = Math.round(freeBytes / (1024 * 1024))
228
+ status.memory = { total: totalMB, used: usedMB, available: availableMB }
229
+ } else {
230
+ const { stdout: memOutput } = await runCommand('free', ['-m'], {
231
+ timeoutMs: 3000
232
+ })
233
+ const memLine = memOutput.split('\n').find(line => line.startsWith('Mem:'))
234
+ if (memLine) {
235
+ const parts = memLine.split(/\s+/)
236
+ status.memory = {
237
+ total: parseInt(parts[1]) || 0,
238
+ used: parseInt(parts[2]) || 0,
239
+ available: parseInt(parts[6]) || 0
240
+ }
241
  }
242
  }
243
  } catch (error) {
 
434
  })
435
  }
436
 
437
+ // Check disk space (cross-platform: use df -h / and parse capacity column)
438
  try {
439
+ const { stdout } = await runCommand('df', ['-h', '/'], {
440
  timeoutMs: 3000
441
  })
442
  const lines = stdout.trim().split('\n')
443
  const last = lines[lines.length - 1] || ''
444
+ const parts = last.split(/\s+/)
445
+ // On macOS capacity is col 4 ("85%"), on Linux use% is col 4 as well
446
+ const pctField = parts.find(p => p.endsWith('%')) || '0%'
447
+ const usagePercent = parseInt(pctField.replace('%', '') || '0')
448
 
449
  health.checks.push({
450
  name: 'Disk Space',
 
459
  })
460
  }
461
 
462
+ // Check memory usage (cross-platform)
463
  try {
464
+ let usagePercent: number
465
+ if (process.platform === 'darwin') {
466
+ const totalBytes = os.totalmem()
467
+ const freeBytes = os.freemem()
468
+ usagePercent = Math.round(((totalBytes - freeBytes) / totalBytes) * 100)
469
+ } else {
470
+ const { stdout } = await runCommand('free', ['-m'], { timeoutMs: 3000 })
471
+ const memLine = stdout.split('\n').find((line) => line.startsWith('Mem:'))
472
+ const parts = (memLine || '').split(/\s+/)
473
+ const total = parseInt(parts[1] || '0')
474
+ const available = parseInt(parts[6] || '0')
475
+ usagePercent = Math.round(((total - available) / total) * 100)
476
+ }
477
 
478
  health.checks.push({
479
  name: 'Memory Usage',
src/lib/websocket.ts CHANGED
@@ -16,7 +16,7 @@ const log = createClientLogger('WebSocket')
16
 
17
  // Gateway protocol version (v3 required by OpenClaw 2026.x)
18
  const PROTOCOL_VERSION = 3
19
- const DEFAULT_GATEWAY_CLIENT_ID = process.env.NEXT_PUBLIC_GATEWAY_CLIENT_ID || 'control-ui'
20
 
21
  // Heartbeat configuration
22
  const PING_INTERVAL_MS = 30_000
 
16
 
17
  // Gateway protocol version (v3 required by OpenClaw 2026.x)
18
  const PROTOCOL_VERSION = 3
19
+ const DEFAULT_GATEWAY_CLIENT_ID = process.env.NEXT_PUBLIC_GATEWAY_CLIENT_ID || 'openclaw-control-ui'
20
 
21
  // Heartbeat configuration
22
  const PING_INTERVAL_MS = 30_000