clawport-ui 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,14 +3,8 @@ export const runtime = 'nodejs'
3
3
  import { getAgent } from '@/lib/agents'
4
4
  import { validateChatMessages } from '@/lib/validation'
5
5
  import { hasImageContent, extractImageAttachments, buildTextPrompt, sendViaOpenClaw } from '@/lib/anthropic'
6
- import OpenAI from 'openai'
7
- import { gatewayBaseUrl } from '@/lib/env'
8
-
9
- // Route through the OpenClaw gateway — no separate API key needed
10
- const openai = new OpenAI({
11
- baseURL: gatewayBaseUrl(),
12
- apiKey: process.env.OPENCLAW_GATEWAY_TOKEN,
13
- })
6
+ import { getOpenAIClient } from '@/lib/openai'
7
+ import type OpenAI from 'openai'
14
8
 
15
9
  const GATEWAY_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN || ''
16
10
 
@@ -18,6 +12,7 @@ export async function POST(
18
12
  request: Request,
19
13
  { params }: { params: Promise<{ id: string }> }
20
14
  ) {
15
+ const openai = getOpenAIClient()
21
16
  const { id } = await params
22
17
  const agent = await getAgent(id)
23
18
 
@@ -1,13 +1,8 @@
1
1
  export const runtime = 'nodejs'
2
2
 
3
3
  import { getAgent } from '@/lib/agents'
4
- import OpenAI from 'openai'
5
- import { gatewayBaseUrl } from '@/lib/env'
6
-
7
- const openai = new OpenAI({
8
- baseURL: gatewayBaseUrl(),
9
- apiKey: process.env.OPENCLAW_GATEWAY_TOKEN,
10
- })
4
+ import { getOpenAIClient } from '@/lib/openai'
5
+ import type OpenAI from 'openai'
11
6
 
12
7
  const MAX_TITLE = 500
13
8
  const MAX_DESC = 5000
@@ -27,6 +22,7 @@ export async function POST(
27
22
  request: Request,
28
23
  { params }: { params: Promise<{ id: string }> }
29
24
  ) {
25
+ const openai = getOpenAIClient()
30
26
  const { id } = await params
31
27
  const agent = await getAgent(id)
32
28
 
@@ -1,14 +1,9 @@
1
1
  export const runtime = 'nodejs'
2
2
 
3
- import OpenAI from 'openai'
4
- import { gatewayBaseUrl } from '@/lib/env'
5
-
6
- const openai = new OpenAI({
7
- baseURL: gatewayBaseUrl(),
8
- apiKey: process.env.OPENCLAW_GATEWAY_TOKEN,
9
- })
3
+ import { getOpenAIClient } from '@/lib/openai'
10
4
 
11
5
  export async function POST(request: Request) {
6
+ const openai = getOpenAIClient()
12
7
  let formData: FormData
13
8
  try {
14
9
  formData = await request.formData()
@@ -1,14 +1,9 @@
1
1
  export const runtime = 'nodejs'
2
2
 
3
- import OpenAI from 'openai'
4
- import { gatewayBaseUrl } from '@/lib/env'
5
-
6
- const openai = new OpenAI({
7
- baseURL: gatewayBaseUrl(),
8
- apiKey: process.env.OPENCLAW_GATEWAY_TOKEN,
9
- })
3
+ import { getOpenAIClient } from '@/lib/openai'
10
4
 
11
5
  export async function POST(request: Request) {
6
+ const openai = getOpenAIClient()
12
7
  try {
13
8
  const { text, voice } = await request.json()
14
9
 
package/lib/crons.test.ts CHANGED
@@ -254,18 +254,25 @@ describe('getCrons - error and lastError', () => {
254
254
  })
255
255
  })
256
256
 
257
- // --- Error propagation (current implementation throws) ---
257
+ // --- Graceful degradation (returns empty array on failure) ---
258
258
 
259
- describe('getCrons - error propagation', () => {
260
- it('throws when execSync throws (CLI not installed)', async () => {
259
+ describe('getCrons - graceful degradation', () => {
260
+ it('returns empty array when execSync throws (CLI not installed)', async () => {
261
261
  mockExecSync.mockImplementation(() => { throw new Error('ENOENT') })
262
- await expect(getCrons()).rejects.toThrow('Failed to fetch cron jobs')
263
- await expect(getCrons()).rejects.toThrow('ENOENT')
262
+ const result = await getCrons()
263
+ expect(result).toEqual([])
264
264
  })
265
265
 
266
- it('throws for invalid JSON output', async () => {
266
+ it('returns empty array for invalid JSON output', async () => {
267
267
  mockExecSync.mockReturnValue('not valid json {{')
268
- await expect(getCrons()).rejects.toThrow('Failed to fetch cron jobs')
268
+ const result = await getCrons()
269
+ expect(result).toEqual([])
270
+ })
271
+
272
+ it('returns empty array when OPENCLAW_BIN is not set', async () => {
273
+ vi.unstubAllEnvs()
274
+ const result = await getCrons()
275
+ expect(result).toEqual([])
269
276
  })
270
277
  })
271
278
 
package/lib/crons.ts CHANGED
@@ -32,11 +32,22 @@ export async function getCrons(): Promise<CronJob[]> {
32
32
  }
33
33
 
34
34
  try {
35
- const openclawBin = requireEnv('OPENCLAW_BIN')
36
- const raw = execSync(`${openclawBin} cron list --json`, {
37
- encoding: 'utf-8',
38
- timeout: 10000,
39
- })
35
+ const openclawBin = process.env.OPENCLAW_BIN
36
+ if (!openclawBin) {
37
+ // No binary configured -- return empty list instead of crashing
38
+ return []
39
+ }
40
+
41
+ let raw: string
42
+ try {
43
+ raw = execSync(`${openclawBin} cron list --json`, {
44
+ encoding: 'utf-8',
45
+ timeout: 10000,
46
+ })
47
+ } catch {
48
+ // CLI failed (binary not found, no crons, gateway down) -- return empty
49
+ return []
50
+ }
40
51
 
41
52
  const parsed = extractJson(raw) as Record<string, unknown>
42
53
  const jobs: unknown[] = Array.isArray(parsed)
@@ -113,9 +124,8 @@ export async function getCrons(): Promise<CronJob[]> {
113
124
 
114
125
  _cronsCache = { result, ts: Date.now() }
115
126
  return result
116
- } catch (err) {
117
- throw new Error(
118
- `Failed to fetch cron jobs: ${err instanceof Error ? err.message : String(err)}`
119
- )
127
+ } catch {
128
+ // JSON extraction or parsing failed -- return empty rather than 500
129
+ return []
120
130
  }
121
131
  }
package/lib/openai.ts ADDED
@@ -0,0 +1,20 @@
1
+ import OpenAI from 'openai'
2
+ import { gatewayBaseUrl } from './env'
3
+
4
+ /**
5
+ * Lazy-initialized OpenAI client routed through the OpenClaw gateway.
6
+ *
7
+ * Call inside route handlers, not at module top level, so builds don't
8
+ * fail when environment variables aren't set.
9
+ */
10
+ let _openai: OpenAI | null = null
11
+
12
+ export function getOpenAIClient(): OpenAI {
13
+ if (!_openai) {
14
+ _openai = new OpenAI({
15
+ baseURL: gatewayBaseUrl(),
16
+ apiKey: process.env.OPENCLAW_GATEWAY_TOKEN || '',
17
+ })
18
+ }
19
+ return _openai
20
+ }
@@ -21,15 +21,28 @@ import { execSync } from 'child_process'
21
21
  * Detect the default workspace path.
22
22
  *
23
23
  * Checks in order:
24
- * 1. ~/.openclaw/agents/main/workspace (current agent-scoped layout)
25
- * 2. ~/.openclaw/workspace-main (multi-agent layout, main agent)
26
- * 3. ~/.openclaw/workspace-* (multi-agent layout, any agent)
27
- * 4. ~/.openclaw/workspace (legacy single-workspace layout)
24
+ * 1. ~/.openclaw/openclaw.json agents.defaults.workspace (canonical)
25
+ * 2. ~/.openclaw/agents/main/workspace (current agent-scoped layout)
26
+ * 3. ~/.openclaw/workspace-main (multi-agent layout, main agent)
27
+ * 4. ~/.openclaw/workspace-* (multi-agent layout, any agent)
28
+ * 5. ~/.openclaw/workspace (legacy single-workspace layout)
28
29
  */
29
30
  export function detectWorkspacePath(): string | null {
30
31
  const base = join(homedir(), '.openclaw')
31
32
 
32
- // 1. Current agent-scoped layout
33
+ // 1. Read from openclaw.json (canonical source)
34
+ const configPath = join(base, 'openclaw.json')
35
+ if (existsSync(configPath)) {
36
+ try {
37
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'))
38
+ const ws = config?.agents?.defaults?.workspace
39
+ if (typeof ws === 'string' && existsSync(ws)) return ws
40
+ } catch {
41
+ // Invalid JSON, fall through
42
+ }
43
+ }
44
+
45
+ // 2. Current agent-scoped layout
33
46
  const agentPath = join(base, 'agents', 'main', 'workspace')
34
47
  if (existsSync(agentPath)) return agentPath
35
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawport-ui",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Open-source dashboard for managing, monitoring, and chatting with your OpenClaw AI agents.",
5
5
  "homepage": "https://clawport.dev",
6
6
  "repository": {
package/scripts/setup.mjs CHANGED
@@ -45,7 +45,19 @@ function exec(cmd) {
45
45
  function detectWorkspacePath() {
46
46
  const base = join(homedir(), '.openclaw')
47
47
 
48
- // 1. Current agent-scoped layout
48
+ // 1. Read from openclaw.json (canonical source)
49
+ const configPath = join(base, 'openclaw.json')
50
+ if (existsSync(configPath)) {
51
+ try {
52
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'))
53
+ const ws = config?.agents?.defaults?.workspace
54
+ if (typeof ws === 'string' && existsSync(ws)) return ws
55
+ } catch {
56
+ // Invalid JSON, fall through
57
+ }
58
+ }
59
+
60
+ // 2. Current agent-scoped layout
49
61
  const agentPath = join(base, 'agents', 'main', 'workspace')
50
62
  if (existsSync(agentPath)) return agentPath
51
63