claw-dashboard 1.9.0 → 2.1.0

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.
Files changed (135) hide show
  1. package/.c8rc.json +38 -0
  2. package/.dockerignore +68 -0
  3. package/.github/dependabot.yml +45 -0
  4. package/.github/pull_request_template.md +39 -0
  5. package/.github/workflows/ci.yml +147 -0
  6. package/.github/workflows/release.yml +40 -0
  7. package/.github/workflows/security.yml +84 -0
  8. package/.husky/pre-commit +1 -0
  9. package/.planning/codebase/ARCHITECTURE.md +206 -0
  10. package/.planning/codebase/INTEGRATIONS.md +150 -0
  11. package/.planning/codebase/STACK.md +122 -0
  12. package/.planning/codebase/STRUCTURE.md +201 -0
  13. package/CHANGELOG.md +293 -0
  14. package/CONTRIBUTING.md +378 -0
  15. package/Dockerfile +54 -0
  16. package/FEATURES.md +195 -21
  17. package/README.md +83 -6
  18. package/TODO.md +28 -0
  19. package/build-cjs.js +127 -0
  20. package/cjs-shim.js +13 -0
  21. package/dist/clawdash +1729 -0
  22. package/dist/clawdash.meta.json +2236 -0
  23. package/dist/widgets.cjs +6511 -0
  24. package/docker-compose.yml +67 -0
  25. package/docs/API.md +1050 -0
  26. package/docs/PLUGINS.md +1504 -0
  27. package/esbuild.config.js +158 -0
  28. package/eslint.config.js +56 -0
  29. package/examples/plugins/README.md +122 -0
  30. package/examples/plugins/api-status/index.js +294 -0
  31. package/examples/plugins/api-status/plugin.json +19 -0
  32. package/examples/plugins/hello-world/index.js +104 -0
  33. package/examples/plugins/hello-world/plugin.json +15 -0
  34. package/examples/plugins/system-metrics-chart/index.js +339 -0
  35. package/examples/plugins/system-metrics-chart/plugin.json +18 -0
  36. package/examples/plugins/weather-widget/index.js +136 -0
  37. package/examples/plugins/weather-widget/plugin.json +19 -0
  38. package/index.cjs +23005 -0
  39. package/index.js +5285 -566
  40. package/jest.config.js +11 -0
  41. package/man/clawdash.1 +276 -0
  42. package/package.json +54 -6
  43. package/schemas/plugin-manifest.json +128 -0
  44. package/scripts/release.js +595 -0
  45. package/src/alerts.js +693 -0
  46. package/src/auto-save.js +584 -0
  47. package/src/cache.js +390 -0
  48. package/src/checksum.js +146 -0
  49. package/src/cli/args.js +110 -0
  50. package/src/cli/export-schedule.js +423 -0
  51. package/src/cli/export-snapshot.js +138 -0
  52. package/src/cli/help.js +69 -0
  53. package/src/cli/import-snapshot.js +230 -0
  54. package/src/cli/index.js +14 -0
  55. package/src/cli/list-templates.js +69 -0
  56. package/src/cli/validate-config.js +76 -0
  57. package/src/cli/validate-plugin.js +187 -0
  58. package/src/cli/version.js +15 -0
  59. package/src/config-validator.js +586 -0
  60. package/src/config-watcher.js +401 -0
  61. package/src/config.js +684 -0
  62. package/src/container-detector.js +499 -0
  63. package/src/database.js +734 -0
  64. package/src/differential-render.js +327 -0
  65. package/src/errors.js +169 -0
  66. package/src/export-scheduler.js +581 -0
  67. package/src/gateway-manager.js +685 -0
  68. package/src/hints.js +371 -0
  69. package/src/loading-states.js +509 -0
  70. package/src/logger.js +185 -0
  71. package/src/memory-pressure.js +467 -0
  72. package/src/performance-monitor.js +374 -0
  73. package/src/plugin-errors.js +586 -0
  74. package/src/plugin-manifest-validator.js +219 -0
  75. package/src/plugin-reload.js +481 -0
  76. package/src/plugin-scaffold.js +1934 -0
  77. package/src/plugin-validator.js +284 -0
  78. package/src/retry.js +218 -0
  79. package/src/security.js +860 -0
  80. package/src/snapshot.js +372 -0
  81. package/src/splash.js +115 -0
  82. package/src/theme-selector.js +411 -0
  83. package/src/themes.js +874 -0
  84. package/src/transitions.js +534 -0
  85. package/src/types.d.ts +174 -0
  86. package/src/validation.js +926 -0
  87. package/src/web-server.js +885 -0
  88. package/src/widgets/builtin-widgets.js +1056 -0
  89. package/src/widgets/config-processor.js +401 -0
  90. package/src/widgets/dependency-resolver.js +596 -0
  91. package/src/widgets/index.js +79 -0
  92. package/src/widgets/plugin-api.js +845 -0
  93. package/src/widgets/widget-error-boundary.js +773 -0
  94. package/src/widgets/widget-error-isolation.js +551 -0
  95. package/src/widgets/widget-loader.js +1437 -0
  96. package/src/workers/system-worker.js +130 -0
  97. package/src/workers/worker-pool.js +633 -0
  98. package/tests/alerts.test.js +437 -0
  99. package/tests/auto-save.test.js +529 -0
  100. package/tests/cache.test.js +317 -0
  101. package/tests/cli.test.js +351 -0
  102. package/tests/command-palette.test.js +320 -0
  103. package/tests/config-processor.test.js +452 -0
  104. package/tests/config-validator.test.js +710 -0
  105. package/tests/config-watcher.test.js +594 -0
  106. package/tests/config.test.js +755 -0
  107. package/tests/database.test.js +438 -0
  108. package/tests/errors.test.js +189 -0
  109. package/tests/example-plugins.test.js +624 -0
  110. package/tests/integration.test.js +1321 -0
  111. package/tests/loading-states.test.js +300 -0
  112. package/tests/manifest-validation-on-load.test.js +671 -0
  113. package/tests/performance-monitor.test.js +190 -0
  114. package/tests/plugin-api-rate-limit.test.js +302 -0
  115. package/tests/plugin-errors.test.js +311 -0
  116. package/tests/plugin-lifecycle-e2e.test.js +1036 -0
  117. package/tests/plugin-reload.test.js +764 -0
  118. package/tests/rate-limiter.test.js +539 -0
  119. package/tests/retry.test.js +308 -0
  120. package/tests/security.test.js +411 -0
  121. package/tests/settings-modal.test.js +226 -0
  122. package/tests/theme-selector.test.js +57 -0
  123. package/tests/utils.js +242 -0
  124. package/tests/utils.test.js +317 -0
  125. package/tests/validate-plugin-cli.test.js +359 -0
  126. package/tests/validation.test.js +837 -0
  127. package/tests/web-server.test.js +646 -0
  128. package/tests/widget-arrange-mode.test.js +183 -0
  129. package/tests/widget-config-hot-reload.test.js +465 -0
  130. package/tests/widget-dependency.test.js +591 -0
  131. package/tests/widget-error-boundary.test.js +749 -0
  132. package/tests/widget-error-isolation.test.js +469 -0
  133. package/tests/widget-integration.test.js +1147 -0
  134. package/tests/widget-refresh-intervals.test.js +284 -0
  135. package/tests/worker-pool.test.js +522 -0
package/docs/API.md ADDED
@@ -0,0 +1,1050 @@
1
+ # Claw Dashboard API Documentation
2
+
3
+ This document describes the internal API modules available in Claw Dashboard.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Cache Module](#cache-module)
8
+ - [Gateway Manager](#gateway-manager)
9
+ - [Error Classes](#error-classes)
10
+ - [Logger](#logger)
11
+ - [Configuration](#configuration)
12
+ - [Container Detector](#container-detector)
13
+ - [Worker Pool](#worker-pool)
14
+
15
+ ---
16
+
17
+ ## Cache Module
18
+
19
+ **File:** `src/cache.js`
20
+
21
+ The cache module provides TTL-based caching for expensive system information calls with optional worker thread support.
22
+
23
+ ### Functions
24
+
25
+ #### `get(key)`
26
+
27
+ Retrieves a cached value if it exists and hasn't expired.
28
+
29
+ **Parameters:**
30
+ - `key` (string): Cache key
31
+
32
+ **Returns:** `any|null` - The cached value or null if expired/missing
33
+
34
+ **Example:**
35
+ ```javascript
36
+ import { get } from './src/cache.js';
37
+
38
+ const cpuData = get('cpu');
39
+ if (cpuData) {
40
+ console.log('Cached CPU:', cpuData);
41
+ }
42
+ ```
43
+
44
+ ---
45
+
46
+ #### `set(key, value, ttl?)`
47
+
48
+ Stores a value in the cache with an optional TTL override.
49
+
50
+ **Parameters:**
51
+ - `key` (string): Cache key
52
+ - `value` (any): Value to cache
53
+ - `ttl` (number, optional): Time to live in milliseconds (uses config default if not provided)
54
+
55
+ **Example:**
56
+ ```javascript
57
+ import { set } from './src/cache.js';
58
+
59
+ set('cpu', cpuData, 5000); // Cache for 5 seconds
60
+ ```
61
+
62
+ ---
63
+
64
+ #### `getOrFetch(key, fetcher, ttl?)`
65
+
66
+ Gets cached data or fetches fresh data if cache miss.
67
+
68
+ **Parameters:**
69
+ - `key` (string): Cache key
70
+ - `fetcher` (Function): Async function to fetch data if cache miss
71
+ - `ttl` (number, optional): TTL override
72
+
73
+ **Returns:** `Promise<any>` - Cached or fresh data
74
+
75
+ **Example:**
76
+ ```javascript
77
+ import { getOrFetch } from './src/cache.js';
78
+ import si from 'systeminformation';
79
+
80
+ const cpuData = await getOrFetch('cpu', async () => {
81
+ return await si.currentLoad();
82
+ }, 1000);
83
+ ```
84
+
85
+ ---
86
+
87
+ #### `getCpuData()`
88
+
89
+ Gets cached CPU data or fetches fresh data using worker threads when available.
90
+
91
+ **Returns:** `Promise<Object>` - CPU load data
92
+
93
+ **Example:**
94
+ ```javascript
95
+ import { getCpuData } from './src/cache.js';
96
+
97
+ const cpu = await getCpuData();
98
+ console.log(`CPU: ${cpu.currentLoad.toFixed(1)}%`);
99
+ ```
100
+
101
+ ---
102
+
103
+ #### `getMemoryData()`
104
+
105
+ Gets cached memory data or fetches fresh data.
106
+
107
+ **Returns:** `Promise<Object>` - Memory data
108
+
109
+ ---
110
+
111
+ #### `getGpuData()`
112
+
113
+ Gets cached GPU data or fetches fresh data.
114
+
115
+ **Returns:** `Promise<Object>` - GPU/graphics data
116
+
117
+ ---
118
+
119
+ #### `getNetworkData()`
120
+
121
+ Gets cached network data or fetches fresh data.
122
+
123
+ **Returns:** `Promise<Object>` - Network stats data
124
+
125
+ ---
126
+
127
+ #### `getDiskData()`
128
+
129
+ Gets cached disk data or fetches fresh data.
130
+
131
+ **Returns:** `Promise<Object>` - Disk size data
132
+
133
+ ---
134
+
135
+ #### `getSystemData()`
136
+
137
+ Gets cached system info data or fetches fresh data.
138
+
139
+ **Returns:** `Promise<Object>` - System info (osInfo, versions, time)
140
+
141
+ ---
142
+
143
+ #### `invalidate(key)`
144
+
145
+ Force refreshes a specific cache entry by deleting it.
146
+
147
+ **Parameters:**
148
+ - `key` (string): Cache key to refresh
149
+
150
+ ---
151
+
152
+ #### `clear()`
153
+
154
+ Clears all cache entries.
155
+
156
+ ---
157
+
158
+ #### `getStatus()`
159
+
160
+ Gets cache status for debugging.
161
+
162
+ **Returns:** `Object` - Cache status info with keys containing:
163
+ - `cached` (boolean): Whether entry exists
164
+ - `age` (number): Age in milliseconds
165
+ - `ttlRemaining` (number): Remaining TTL in milliseconds
166
+ - `configTtl` (number): Configured TTL
167
+
168
+ ---
169
+
170
+ #### `debounce(fn, delay)`
171
+
172
+ Creates a debounced function that delays execution until after wait period.
173
+
174
+ **Parameters:**
175
+ - `fn` (Function): Function to debounce
176
+ - `delay` (number): Delay in milliseconds
177
+
178
+ **Returns:** `Function` - Debounced function
179
+
180
+ ---
181
+
182
+ #### `throttle(fn, limit)`
183
+
184
+ Creates a throttled function that limits execution rate.
185
+
186
+ **Parameters:**
187
+ - `fn` (Function): Function to throttle
188
+ - `limit` (number): Minimum interval between calls in ms
189
+
190
+ **Returns:** `Function` - Throttled function
191
+
192
+ ---
193
+
194
+ ## Gateway Manager
195
+
196
+ **File:** `src/gateway-manager.js`
197
+
198
+ Manages multiple OpenClaw gateway endpoints and aggregates session data.
199
+
200
+ ### Class: GatewayManager
201
+
202
+ #### `gatewayManager.init(settings)`
203
+
204
+ Initialize the gateway manager with settings.
205
+
206
+ **Parameters:**
207
+ - `settings` (Object): Dashboard settings containing `gatewayEndpoints` array
208
+
209
+ **Example:**
210
+ ```javascript
211
+ import { gatewayManager } from './src/gateway-manager.js';
212
+
213
+ gatewayManager.init({
214
+ gatewayEndpoints: [
215
+ { name: 'local', host: 'localhost', port: 18789, enabled: true },
216
+ { name: 'remote', host: '192.168.1.100', port: 18789, token: 'secret', enabled: true }
217
+ ]
218
+ });
219
+ ```
220
+
221
+ ---
222
+
223
+ #### `gatewayManager.getEnabledEndpoints()`
224
+
225
+ Gets all enabled endpoints.
226
+
227
+ **Returns:** `GatewayEndpoint[]` - Array of enabled endpoints
228
+
229
+ ---
230
+
231
+ #### `gatewayManager.getAllEndpoints()`
232
+
233
+ Gets all endpoints (including disabled).
234
+
235
+ **Returns:** `GatewayEndpoint[]` - Array of all endpoints
236
+
237
+ ---
238
+
239
+ #### `gatewayManager.getEndpoint(name)`
240
+
241
+ Gets a specific endpoint by name.
242
+
243
+ **Parameters:**
244
+ - `name` (string): Endpoint name
245
+
246
+ **Returns:** `GatewayEndpoint|undefined`
247
+
248
+ ---
249
+
250
+ #### `gatewayManager.addEndpoint(endpointConfig)`
251
+
252
+ Adds a new gateway endpoint.
253
+
254
+ **Parameters:**
255
+ - `endpointConfig` (Object): Endpoint configuration
256
+ - `name` (string): Display name
257
+ - `host` (string): Hostname or IP
258
+ - `port` (number): Port number
259
+ - `token` (string, optional): Authentication token
260
+ - `type` (string): 'local', 'remote', or 'cloud'
261
+
262
+ **Returns:** `GatewayEndpoint|null` - The added endpoint or null if failed
263
+
264
+ **Example:**
265
+ ```javascript
266
+ const endpoint = gatewayManager.addEndpoint({
267
+ name: 'prod-server',
268
+ host: '10.0.0.5',
269
+ port: 18789,
270
+ token: 'api-token-here',
271
+ type: 'remote'
272
+ });
273
+ ```
274
+
275
+ ---
276
+
277
+ #### `gatewayManager.removeEndpoint(name)`
278
+
279
+ Removes an endpoint by name.
280
+
281
+ **Parameters:**
282
+ - `name` (string): Endpoint name to remove
283
+
284
+ **Returns:** `boolean` - True if removed, false if not found or last endpoint
285
+
286
+ ---
287
+
288
+ #### `gatewayManager.updateEndpoint(name, updates)`
289
+
290
+ Updates an existing endpoint.
291
+
292
+ **Parameters:**
293
+ - `name` (string): Endpoint name to update
294
+ - `updates` (Object): Fields to update
295
+
296
+ **Returns:** `GatewayEndpoint|null` - Updated endpoint or null
297
+
298
+ **Example:**
299
+ ```javascript
300
+ gatewayManager.updateEndpoint('prod-server', {
301
+ port: 8080,
302
+ token: 'new-token'
303
+ });
304
+ ```
305
+
306
+ ---
307
+
308
+ #### `gatewayManager.toggleEndpoint(name, enabled)`
309
+
310
+ Toggles endpoint enabled state.
311
+
312
+ **Parameters:**
313
+ - `name` (string): Endpoint name
314
+ - `enabled` (boolean): New enabled state
315
+
316
+ **Returns:** `boolean` - True if toggled, false if not found
317
+
318
+ ---
319
+
320
+ #### `gatewayManager.fetchAllSessions()`
321
+
322
+ Fetches sessions from all enabled endpoints in parallel.
323
+
324
+ **Returns:** `Promise<Object>` - Object containing:
325
+ - `sessions` (AggregatedSession[]): All sessions from all endpoints
326
+ - `stats` (Object): Fetch statistics
327
+ - `totalEndpoints` (number): Total enabled endpoints
328
+ - `reachableEndpoints` (number): Successfully reached endpoints
329
+ - `unreachableEndpoints` (number): Failed endpoints
330
+
331
+ **Example:**
332
+ ```javascript
333
+ const { sessions, stats } = await gatewayManager.fetchAllSessions();
334
+ console.log(`Fetched ${sessions.length} sessions from ${stats.reachableEndpoints} endpoints`);
335
+ ```
336
+
337
+ ---
338
+
339
+ #### `gatewayManager.getEndpointHealth()`
340
+
341
+ Gets health status for all endpoints.
342
+
343
+ **Returns:** `Object[]` - Array of endpoint health objects containing:
344
+ - `name` (string): Endpoint name
345
+ - `host` (string): Host
346
+ - `port` (number): Port
347
+ - `enabled` (boolean): Enabled state
348
+ - `reachable` (boolean): Last known reachability
349
+ - `lastSeen` (number|null): Timestamp of last successful connection
350
+ - `latency` (number|null): Response latency in ms
351
+ - `failCount` (number): Consecutive failure count
352
+ - `error` (string|null): Last error message
353
+
354
+ ---
355
+
356
+ #### `gatewayManager.getSettingsForSave()`
357
+
358
+ Gets settings object for persistence.
359
+
360
+ **Returns:** `Object` - Settings containing `gatewayEndpoints` array
361
+
362
+ ---
363
+
364
+ ### Types
365
+
366
+ #### `GatewayEndpoint`
367
+
368
+ ```typescript
369
+ {
370
+ name: string; // Display name
371
+ host: string; // Hostname or IP
372
+ port: number; // Port number
373
+ token: string | null; // Auth token
374
+ enabled: boolean; // Whether enabled
375
+ type: string; // 'local', 'remote', 'cloud'
376
+ reachable?: boolean; // Last known status
377
+ lastSeen?: number; // Timestamp
378
+ error?: string; // Last error
379
+ }
380
+ ```
381
+
382
+ #### `AggregatedSession`
383
+
384
+ ```typescript
385
+ {
386
+ key: string;
387
+ channel: string;
388
+ displayName: string;
389
+ updatedAt: number;
390
+ sessionId: string;
391
+ model: string;
392
+ contextTokens: number;
393
+ totalTokens: number;
394
+ kind: string;
395
+ gatewayEndpoint: string; // Source endpoint name
396
+ gatewayHost: string; // Source endpoint host
397
+ // ... other session fields
398
+ }
399
+ ```
400
+
401
+ ---
402
+
403
+ #### `gatewayManager.forceRetry(endpointName?)`
404
+
405
+ Force a retry for a specific endpoint or all unreachable endpoints.
406
+
407
+ **Parameters:**
408
+ - `endpointName` (string|null): Name of endpoint to retry, or null for all unreachable
409
+
410
+ **Returns:** `Promise<Object>` - Result containing:
411
+ - `attempted` (number): Number of endpoints attempted
412
+ - `successful` (number): Number of endpoints that reconnected
413
+ - `results` (Array): Per-endpoint results
414
+
415
+ ---
416
+
417
+ #### `gatewayManager.getEndpointFailCount(name)`
418
+
419
+ Gets the consecutive failure count for an endpoint.
420
+
421
+ **Parameters:**
422
+ - `name` (string): Endpoint name
423
+
424
+ **Returns:** `number` - Consecutive failure count
425
+
426
+ ---
427
+
428
+ #### `gatewayManager.clearEndpointFailCount(name)`
429
+
430
+ Clears the failure count for a specific endpoint.
431
+
432
+ **Parameters:**
433
+ - `name` (string): Endpoint name
434
+
435
+ ---
436
+
437
+ #### `gatewayManager.getTotalFailCount()`
438
+
439
+ Gets the total failure count across all endpoints.
440
+
441
+ **Returns:** `number` - Total consecutive failures
442
+
443
+ ---
444
+
445
+ #### `gatewayManager.clearAllFailCounts()`
446
+
447
+ Clears all failure counts for all endpoints.
448
+
449
+ ---
450
+
451
+ ## Auto-Retry Configuration
452
+
453
+ **File:** `src/config.js` (constants), `index.js` (implementation)
454
+
455
+ The dashboard automatically retries failed gateway connections with configurable exponential backoff to prevent overwhelming unresponsive gateways.
456
+
457
+ ### Configuration Options
458
+
459
+ Add an `autoRetry` section to your `~/.openclaw/dashboard-settings.json`:
460
+
461
+ ```json
462
+ {
463
+ "autoRetry": {
464
+ "enabled": true,
465
+ "intervalMs": 30000,
466
+ "exponentialBackoff": true,
467
+ "backoffMultiplier": 2,
468
+ "maxBackoffIntervalMs": 300000,
469
+ "resetAfterSuccess": true,
470
+ "consecutiveFailureThreshold": 3
471
+ }
472
+ }
473
+ ```
474
+
475
+ ### Options Reference
476
+
477
+ | Option | Type | Default | Description |
478
+ |--------|------|---------|-------------|
479
+ | `enabled` | boolean | `true` | Enable/disable auto-retry |
480
+ | `intervalMs` | number | `30000` | Base interval between retries (ms) |
481
+ | `exponentialBackoff` | boolean | `true` | Enable exponential backoff |
482
+ | `backoffMultiplier` | number | `2` | Multiply interval by this after each failure |
483
+ | `maxBackoffIntervalMs` | number | `300000` | Cap backoff at this value (5 min) |
484
+ | `resetAfterSuccess` | boolean | `true` | Reset backoff after successful connection |
485
+ | `consecutiveFailureThreshold` | number | `3` | Failures before backoff kicks in |
486
+
487
+ ### Exponential Backoff Behavior
488
+
489
+ When `exponentialBackoff` is enabled:
490
+
491
+ 1. First 2 failures: Retry at `intervalMs` (30s)
492
+ 2. 3rd failure: Retry at `30s × 2 = 60s`
493
+ 3. 4th failure: Retry at `30s × 2² = 120s`
494
+ 4. 5th failure: Retry at `30s × 2³ = 240s`
495
+ 5. And so on, up to `maxBackoffIntervalMs` (5 min)
496
+
497
+ After a successful connection, if `resetAfterSuccess` is true, the backoff resets to the base interval.
498
+
499
+ ### Validation Constraints
500
+
501
+ - `intervalMs`: 5000ms (5s) to 300000ms (5min)
502
+ - `backoffMultiplier`: 1 to 10
503
+ - `maxBackoffIntervalMs`: 10000ms (10s) to 600000ms (10min)
504
+ - `consecutiveFailureThreshold`: 1 to 10
505
+
506
+ ### Disabling Auto-Retry
507
+
508
+ To disable auto-retry completely:
509
+
510
+ ```json
511
+ {
512
+ "autoRetry": {
513
+ "enabled": false
514
+ }
515
+ }
516
+ ```
517
+
518
+ ---
519
+
520
+ ## Troubleshooting Gateway Connectivity
521
+
522
+ ### Common Issues
523
+
524
+ #### "Gateway unreachable" warnings
525
+
526
+ The dashboard shows yellow/red status for gateways it cannot reach. Check:
527
+
528
+ 1. **Is the OpenClaw agent running?**
529
+ ```bash
530
+ # Check if agent is listening
531
+ curl http://localhost:18789/sessions
532
+ ```
533
+
534
+ 2. **Is the correct host/port configured?**
535
+ Verify your `gatewayEndpoints` configuration in settings.
536
+
537
+ 3. **Network/firewall issues?**
538
+ Remote gateways may require VPN or specific network access.
539
+
540
+ #### Auto-retry not working
541
+
542
+ 1. **Check if auto-retry is enabled:**
543
+ Verify `autoRetry.enabled` is `true` in settings.
544
+
545
+ 2. **Verify interval settings:**
546
+ Default is 30 seconds. If you set it higher, retries will be less frequent.
547
+
548
+ 3. **Check logs for retry attempts:**
549
+ Look for `[RETRY]` log messages indicating retry activity.
550
+
551
+ #### Excessive retry delays
552
+
553
+ If gateways are retrying too slowly:
554
+
555
+ 1. **Reduce the backoff multiplier:**
556
+ ```json
557
+ { "backoffMultiplier": 1.5 }
558
+ ```
559
+
560
+ 2. **Lower the max backoff:**
561
+ ```json
562
+ { "maxBackoffIntervalMs": 60000 }
563
+ ```
564
+
565
+ 3. **Lower the failure threshold:**
566
+ ```json
567
+ { "consecutiveFailureThreshold": 1 }
568
+ ```
569
+
570
+ ### Force Retry
571
+
572
+ Manually trigger a retry from the dashboard:
573
+
574
+ 1. Press `r` to retry all unreachable gateways immediately
575
+ 2. Or use the Gateway Status widget to retry individual endpoints
576
+
577
+ ### Debug Logging
578
+
579
+ Enable debug logging to see detailed retry behavior:
580
+
581
+ ```bash
582
+ # Start with debug logging
583
+ DEBUG=claw-dashboard npm start
584
+ ```
585
+
586
+ Or set in settings:
587
+ ```json
588
+ { "logLevelFilter": "debug" }
589
+ ```
590
+
591
+ ---
592
+
593
+ ## Error Classes
594
+
595
+ **File:** `src/errors.js`
596
+
597
+ Custom error classes for better error handling and debugging.
598
+
599
+ ### Base Class: DashboardError
600
+
601
+ All dashboard errors extend this base class.
602
+
603
+ **Properties:**
604
+ - `name` (string): Error name
605
+ - `message` (string): Error message
606
+ - `code` (string): Error code constant
607
+ - `details` (Object): Additional error details
608
+ - `timestamp` (string): ISO timestamp
609
+ - `stack` (string): Stack trace
610
+
611
+ **Method:** `toJSON()`
612
+
613
+ Returns a JSON-serializable representation of the error.
614
+
615
+ ---
616
+
617
+ ### Error Types
618
+
619
+ | Class | Code | Use Case |
620
+ |-------|------|----------|
621
+ | `ConfigError` | `CONFIG_ERROR` | Configuration issues |
622
+ | `SettingsError` | `SETTINGS_ERROR` | Settings load/save errors |
623
+ | `GatewayError` | `GATEWAY_ERROR` | Gateway communication |
624
+ | `SessionError` | `SESSION_ERROR` | Session-related errors |
625
+ | `DataFetchError` | `DATA_FETCH_ERROR` | System info fetch failures |
626
+ | `AuthError` | `AUTH_ERROR` | Authentication failures |
627
+ | `NetworkError` | `NETWORK_ERROR` | Network issues |
628
+ | `UIError` | `UI_ERROR` | UI/rendering errors |
629
+ | `DatabaseError` | `DATABASE_ERROR` | Database operations |
630
+ | `ValidationError` | `VALIDATION_ERROR` | Input validation |
631
+ | `TimeoutError` | `TIMEOUT_ERROR` | Operation timeouts |
632
+
633
+ ---
634
+
635
+ ### Helper Functions
636
+
637
+ #### `isDashboardError(error)`
638
+
639
+ Checks if an error is a DashboardError instance.
640
+
641
+ **Parameters:**
642
+ - `error` (Error): Error to check
643
+
644
+ **Returns:** `boolean`
645
+
646
+ ---
647
+
648
+ #### `getErrorCode(error)`
649
+
650
+ Gets the error code from a DashboardError or returns 'UNKNOWN_ERROR'.
651
+
652
+ **Parameters:**
653
+ - `error` (Error): Error to check
654
+
655
+ **Returns:** `string`
656
+
657
+ ---
658
+
659
+ ### Constants
660
+
661
+ #### `ERROR_CODES`
662
+
663
+ Object containing all error code constants:
664
+
665
+ ```javascript
666
+ import { ERROR_CODES } from './src/errors.js';
667
+
668
+ // Available codes:
669
+ // CONFIG_ERROR, SETTINGS_ERROR, GATEWAY_ERROR, SESSION_ERROR,
670
+ // DATA_FETCH_ERROR, AUTH_ERROR, NETWORK_ERROR, UI_ERROR,
671
+ // DATABASE_ERROR, VALIDATION_ERROR, TIMEOUT_ERROR, DASHBOARD_ERROR
672
+ ```
673
+
674
+ ---
675
+
676
+ ## Logger
677
+
678
+ **File:** `src/logger.js`
679
+
680
+ File-only logging module that avoids interfering with the blessed TUI.
681
+
682
+ **Log Location:** `~/.openclaw/claw-dashboard.log`
683
+
684
+ ### Methods
685
+
686
+ #### `logger.error(...args)`
687
+
688
+ Logs error level messages.
689
+
690
+ **Example:**
691
+ ```javascript
692
+ import logger from './src/logger.js';
693
+
694
+ logger.error('Failed to fetch sessions:', error.message);
695
+ ```
696
+
697
+ ---
698
+
699
+ #### `logger.warn(...args)`
700
+
701
+ Logs warning level messages.
702
+
703
+ ---
704
+
705
+ #### `logger.info(...args)`
706
+
707
+ Logs info level messages.
708
+
709
+ ---
710
+
711
+ #### `logger.debug(...args)`
712
+
713
+ Logs debug level messages (only when `DEBUG` env var is set).
714
+
715
+ **Example:**
716
+ ```bash
717
+ DEBUG=1 node index.js
718
+ ```
719
+
720
+ ---
721
+
722
+ ### Export: `LOG_FILE_PATH`
723
+
724
+ The full path to the log file.
725
+
726
+ ```javascript
727
+ import { LOG_FILE_PATH } from './src/logger.js';
728
+ console.log('Logs at:', LOG_FILE_PATH);
729
+ ```
730
+
731
+ ---
732
+
733
+ ## Configuration
734
+
735
+ **File:** `src/config.js`
736
+
737
+ Centralized configuration containing all constants and defaults.
738
+
739
+ ### Refresh Intervals
740
+
741
+ ```javascript
742
+ import { REFRESH_INTERVALS } from './src/config.js';
743
+
744
+ REFRESH_INTERVALS.DEFAULT // 2000ms
745
+ REFRESH_INTERVALS.ACTIVE // 2000ms
746
+ REFRESH_INTERVALS.IDLE // 10000ms
747
+ REFRESH_INTERVALS.OPTIONS // [1000, 2000, 5000, 10000]
748
+ ```
749
+
750
+ ### Gateway Settings
751
+
752
+ ```javascript
753
+ import { GATEWAY, DEFAULT_GATEWAY_ENDPOINT } from './src/config.js';
754
+
755
+ GATEWAY.DEFAULT_PORT // 18789
756
+ GATEWAY.TIMEOUT_MS // 3000
757
+ GATEWAY.MAX_ENDPOINTS // 10
758
+ ```
759
+
760
+ ### Cache TTL
761
+
762
+ ```javascript
763
+ import { CACHE_TTL, CACHE_CONFIG } from './src/config.js';
764
+
765
+ CACHE_TTL.CPU // 1000ms
766
+ CACHE_TTL.MEMORY // 1000ms
767
+ CACHE_TTL.GPU // 5000ms
768
+ CACHE_TTL.NETWORK // 1000ms
769
+ CACHE_TTL.DISK // 30000ms
770
+ CACHE_TTL.SYSTEM // 5000ms
771
+ CACHE_TTL.CONTAINER // 30000ms
772
+ CACHE_TTL.DEFAULT // 2000ms
773
+ ```
774
+
775
+ ### Alert Thresholds
776
+
777
+ ```javascript
778
+ import { ALERT_THRESHOLDS, ALERT_RATE_LIMIT } from './src/config.js';
779
+
780
+ ALERT_THRESHOLDS.CPU // { warning: 70, critical: 90 }
781
+ ALERT_THRESHOLDS.MEMORY // { warning: 75, critical: 90 }
782
+ ALERT_THRESHOLDS.DISK // { warning: 80, critical: 95 }
783
+
784
+ ALERT_RATE_LIMIT.ENABLED // true
785
+ ALERT_RATE_LIMIT.WINDOW_MS // 60000
786
+ ALERT_RATE_LIMIT.MAX_ALERTS // 5
787
+ ```
788
+
789
+ ### Validation Constraints
790
+
791
+ ```javascript
792
+ import { VALIDATION } from './src/config.js';
793
+
794
+ VALIDATION.REFRESH_INTERVAL.MIN // 500
795
+ VALIDATION.REFRESH_INTERVAL.MAX // 60000
796
+ VALIDATION.VALID_THEMES // ['default', 'dark', 'high-contrast', 'ocean', 'auto']
797
+ VALIDATION.VALID_SORT_MODES // ['time', 'tokens', 'idle', 'name']
798
+ VALIDATION.ENDPOINT_NAME.PATTERN // /^[a-zA-Z0-9_-]+$/
799
+ ```
800
+
801
+ ### Worker Settings
802
+
803
+ ```javascript
804
+ import { WORKERS } from './src/config.js';
805
+
806
+ WORKERS.ENABLED // true
807
+ WORKERS.MAX_WORKERS // 2
808
+ WORKERS.TASK_TIMEOUT // 10000
809
+ WORKERS.FALLBACK_ON_ERROR // true
810
+ ```
811
+
812
+ ### Paths
813
+
814
+ ```javascript
815
+ import { PATHS } from './src/config.js';
816
+
817
+ PATHS.SETTINGS // ~/.openclaw/dashboard-settings.json
818
+ PATHS.EXPORTS // ~/.openclaw/exports
819
+ PATHS.LOG // ~/.openclaw/claw-dashboard.log
820
+ PATHS.HOME_DIR // ~
821
+ PATHS.OPENCLAW_DIR // ~/.openclaw
822
+ PATHS.AGENTS_DIR // ~/.openclaw/agents
823
+ ```
824
+
825
+ ### Default Settings
826
+
827
+ ```javascript
828
+ import { DEFAULT_SETTINGS } from './src/config.js';
829
+
830
+ // Complete default settings object for dashboard initialization
831
+ ```
832
+
833
+ ### Version
834
+
835
+ ```javascript
836
+ import { DASHBOARD_VERSION } from './src/config.js';
837
+
838
+ console.log('Version:', DASHBOARD_VERSION); // e.g., "1.9.0"
839
+ ```
840
+
841
+ ---
842
+
843
+ ## Container Detector
844
+
845
+ **File:** `src/container-detector.js`
846
+
847
+ Detects containerized environments (Docker, Kubernetes, WSL).
848
+
849
+ ### Functions
850
+
851
+ #### `detectContainerEnvironment()`
852
+
853
+ Detects if running in a container environment.
854
+
855
+ **Returns:** `Promise<ContainerEnvironment>`
856
+
857
+ ```typescript
858
+ {
859
+ isContainer: boolean;
860
+ runtime: string | null; // 'docker', 'kubernetes', 'containerd', etc.
861
+ containerId: string | null;
862
+ containerName: string | null;
863
+ podName: string | null;
864
+ namespace: string | null;
865
+ wslVersion: number | null; // 1 or 2 for WSL
866
+ wslDistro: string | null; // WSL distribution name
867
+ }
868
+ ```
869
+
870
+ **Example:**
871
+ ```javascript
872
+ import { detectContainerEnvironment } from './src/container-detector.js';
873
+
874
+ const env = await detectContainerEnvironment();
875
+ if (env.isContainer) {
876
+ console.log(`Running in ${env.runtime}`);
877
+ if (env.wslVersion) {
878
+ console.log(`WSL${env.wslVersion} - ${env.wslDistro}`);
879
+ }
880
+ }
881
+ ```
882
+
883
+ ---
884
+
885
+ #### `checkDocker()`
886
+
887
+ Checks if running inside a Docker container.
888
+
889
+ **Returns:** `Promise<Object|null>` - Container info or null
890
+
891
+ ---
892
+
893
+ #### `checkKubernetes()`
894
+
895
+ Checks if running inside a Kubernetes pod.
896
+
897
+ **Returns:** `Promise<Object|null>` - Pod info or null
898
+
899
+ ---
900
+
901
+ #### `checkWSL()`
902
+
903
+ Checks if running in WSL (Windows Subsystem for Linux).
904
+
905
+ **Returns:** `Promise<Object|null>` - WSL info or null
906
+
907
+ ---
908
+
909
+ ## Worker Pool
910
+
911
+ **File:** `src/workers/worker-pool.js`
912
+
913
+ Manages worker threads for executing heavy systeminformation commands off the main thread.
914
+
915
+ ### Class: WorkerPool
916
+
917
+ #### `workerPool.execute(command)`
918
+
919
+ Execute a systeminformation command in a worker thread.
920
+
921
+ **Parameters:**
922
+ - `command` (string): Command name
923
+ - `'currentLoad'` - CPU load
924
+ - `'mem'` - Memory info
925
+ - `'graphics'` - GPU info
926
+ - `'networkStats'` - Network stats
927
+ - `'fsSize'` - Filesystem size
928
+ - `'systemData'` - OS info, versions, time
929
+ - `'processes'` - Process list
930
+ - `'diskLayout'` - Disk layout
931
+ - `'battery'` - Battery info
932
+ - `'users'` - User list
933
+
934
+ **Returns:** `Promise<any>` - Command result
935
+
936
+ **Example:**
937
+ ```javascript
938
+ import { workerPool } from './src/workers/worker-pool.js';
939
+
940
+ try {
941
+ const cpuData = await workerPool.execute('currentLoad');
942
+ console.log('CPU Load:', cpuData.currentLoad);
943
+ } catch (error) {
944
+ console.error('Worker failed:', error);
945
+ }
946
+ ```
947
+
948
+ ---
949
+
950
+ #### `workerPool.terminate()`
951
+
952
+ Gracefully terminates all worker threads.
953
+
954
+ **Returns:** `Promise<void>`
955
+
956
+ ---
957
+
958
+ ### Export: `isWorkerThreadsSupported`
959
+
960
+ Boolean indicating if worker threads are supported in the current Node.js environment.
961
+
962
+ ```javascript
963
+ import { isWorkerThreadsSupported } from './src/workers/worker-pool.js';
964
+
965
+ if (isWorkerThreadsSupported) {
966
+ // Can use worker threads
967
+ }
968
+ ```
969
+
970
+ ---
971
+
972
+ ## Usage Examples
973
+
974
+ ### Complete Dashboard Setup
975
+
976
+ ```javascript
977
+ import { gatewayManager } from './src/gateway-manager.js';
978
+ import logger from './src/logger.js';
979
+ import { DEFAULT_SETTINGS } from './src/config.js';
980
+
981
+ // Initialize gateway manager
982
+ gatewayManager.init(DEFAULT_SETTINGS);
983
+
984
+ // Add a remote endpoint
985
+ const endpoint = gatewayManager.addEndpoint({
986
+ name: 'production',
987
+ host: 'prod.example.com',
988
+ port: 18789,
989
+ token: process.env.OPENCLAW_TOKEN,
990
+ type: 'remote'
991
+ });
992
+
993
+ if (endpoint) {
994
+ logger.info('Added production endpoint');
995
+ }
996
+
997
+ // Fetch all sessions
998
+ try {
999
+ const { sessions, stats } = await gatewayManager.fetchAllSessions();
1000
+ logger.info(`Fetched ${sessions.length} sessions from ${stats.reachableEndpoints} endpoints`);
1001
+ } catch (error) {
1002
+ logger.error('Failed to fetch sessions:', error.message);
1003
+ }
1004
+ ```
1005
+
1006
+ ### Error Handling Pattern
1007
+
1008
+ ```javascript
1009
+ import {
1010
+ isDashboardError,
1011
+ getErrorCode,
1012
+ ERROR_CODES,
1013
+ GatewayError,
1014
+ NetworkError
1015
+ } from './src/errors.js';
1016
+ import logger from './src/logger.js';
1017
+
1018
+ async function fetchWithErrorHandling() {
1019
+ try {
1020
+ return await fetchData();
1021
+ } catch (error) {
1022
+ if (isDashboardError(error)) {
1023
+ const code = getErrorCode(error);
1024
+
1025
+ switch (code) {
1026
+ case ERROR_CODES.GATEWAY_ERROR:
1027
+ logger.warn('Gateway unavailable:', error.message);
1028
+ return null;
1029
+ case ERROR_CODES.NETWORK_ERROR:
1030
+ logger.warn('Network issue:', error.message);
1031
+ return null;
1032
+ default:
1033
+ logger.error('Dashboard error:', error.toJSON());
1034
+ throw error;
1035
+ }
1036
+ }
1037
+
1038
+ // Re-throw non-dashboard errors
1039
+ throw error;
1040
+ }
1041
+ }
1042
+ ```
1043
+
1044
+ ---
1045
+
1046
+ ## See Also
1047
+
1048
+ - [README.md](../README.md) - Project overview
1049
+ - [CHANGELOG.md](../CHANGELOG.md) - Version history
1050
+ - [TODO.md](../TODO.md) - Planned features and known issues