@chrysb/alphaclaw 0.6.1 → 0.6.2-beta.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 (50) hide show
  1. package/lib/public/css/agents.css +1 -1
  2. package/lib/public/css/cron.css +535 -0
  3. package/lib/public/css/theme.css +72 -0
  4. package/lib/public/js/app.js +45 -10
  5. package/lib/public/js/components/action-button.js +26 -20
  6. package/lib/public/js/components/agents-tab/agent-detail-panel.js +98 -17
  7. package/lib/public/js/components/agents-tab/agent-tools/index.js +105 -0
  8. package/lib/public/js/components/agents-tab/agent-tools/tool-catalog.js +289 -0
  9. package/lib/public/js/components/agents-tab/agent-tools/use-agent-tools.js +128 -0
  10. package/lib/public/js/components/agents-tab/index.js +4 -0
  11. package/lib/public/js/components/cron-tab/cron-calendar-helpers.js +385 -0
  12. package/lib/public/js/components/cron-tab/cron-calendar.js +441 -0
  13. package/lib/public/js/components/cron-tab/cron-helpers.js +326 -0
  14. package/lib/public/js/components/cron-tab/cron-job-detail.js +425 -0
  15. package/lib/public/js/components/cron-tab/cron-job-list.js +305 -0
  16. package/lib/public/js/components/cron-tab/cron-job-usage.js +70 -0
  17. package/lib/public/js/components/cron-tab/cron-overview.js +599 -0
  18. package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +277 -0
  19. package/lib/public/js/components/cron-tab/index.js +100 -0
  20. package/lib/public/js/components/cron-tab/use-cron-tab.js +366 -0
  21. package/lib/public/js/components/doctor/summary-cards.js +5 -11
  22. package/lib/public/js/components/icons.js +13 -0
  23. package/lib/public/js/components/pill-tabs.js +33 -0
  24. package/lib/public/js/components/pop-actions.js +58 -0
  25. package/lib/public/js/components/routes/agents-route.js +4 -0
  26. package/lib/public/js/components/routes/cron-route.js +9 -0
  27. package/lib/public/js/components/routes/index.js +1 -0
  28. package/lib/public/js/components/segmented-control.js +15 -9
  29. package/lib/public/js/components/summary-stat-card.js +17 -0
  30. package/lib/public/js/components/tooltip.js +50 -4
  31. package/lib/public/js/components/watchdog-tab.js +46 -1
  32. package/lib/public/js/lib/api.js +94 -0
  33. package/lib/public/js/lib/app-navigation.js +2 -0
  34. package/lib/public/js/lib/storage-keys.js +1 -0
  35. package/lib/public/setup.html +1 -0
  36. package/lib/server/agents/agents.js +15 -0
  37. package/lib/server/constants.js +1 -0
  38. package/lib/server/cost-utils.js +312 -0
  39. package/lib/server/cron-service.js +461 -0
  40. package/lib/server/db/usage/index.js +100 -1
  41. package/lib/server/db/usage/pricing.js +1 -83
  42. package/lib/server/db/usage/sessions.js +4 -1
  43. package/lib/server/db/usage/shared.js +2 -1
  44. package/lib/server/db/usage/summary.js +5 -1
  45. package/lib/server/onboarding/index.js +39 -5
  46. package/lib/server/onboarding/openclaw.js +25 -19
  47. package/lib/server/onboarding/validation.js +28 -0
  48. package/lib/server/routes/cron.js +148 -0
  49. package/lib/server.js +13 -0
  50. package/package.json +1 -1
@@ -74,7 +74,7 @@
74
74
 
75
75
  .agents-detail-content {
76
76
  flex: 1;
77
- padding: 24px 0;
77
+ padding: 16px 0;
78
78
  }
79
79
 
80
80
  /* ── Empty state ─────────────────────────────── */
@@ -0,0 +1,535 @@
1
+ .app-content-pane.cron-pane {
2
+ padding: 0;
3
+ }
4
+
5
+ .cron-tab-shell {
6
+ height: 100%;
7
+ display: flex;
8
+ flex-direction: column;
9
+ min-height: 0;
10
+ }
11
+
12
+ .cron-tab-header {
13
+ padding: 16px 20px 12px;
14
+ border-bottom: 1px solid var(--border);
15
+ }
16
+
17
+ .cron-tab-main {
18
+ flex: 1;
19
+ min-height: 0;
20
+ display: flex;
21
+ }
22
+
23
+ .cron-list-panel {
24
+ min-width: 220px;
25
+ max-width: 480px;
26
+ border-right: 1px solid var(--border);
27
+ background: var(--bg-sidebar);
28
+ overflow-y: auto;
29
+ }
30
+
31
+ .cron-list-panel-inner {
32
+ padding: 0 10px 10px;
33
+ }
34
+
35
+ .cron-list-sticky-search {
36
+ position: sticky;
37
+ top: 0;
38
+ z-index: 2;
39
+ padding: 10px 0 8px;
40
+ background: var(--bg-sidebar);
41
+ }
42
+
43
+ .cron-list-search-input {
44
+ width: 100%;
45
+ height: 30px;
46
+ border-radius: 8px;
47
+ border: 1px solid var(--border);
48
+ background: rgba(255, 255, 255, 0.02);
49
+ color: var(--text);
50
+ font-size: 12px;
51
+ padding: 0 9px;
52
+ outline: none;
53
+ font-family: inherit;
54
+ }
55
+
56
+ .cron-list-search-input::placeholder {
57
+ color: var(--text-dim);
58
+ }
59
+
60
+ .cron-list-search-input:focus {
61
+ border-color: rgba(99, 235, 255, 0.45);
62
+ box-shadow: 0 0 0 1px rgba(99, 235, 255, 0.18);
63
+ }
64
+
65
+ .cron-list-separator {
66
+ border-top: 1px solid var(--border);
67
+ margin: 8px 2px;
68
+ }
69
+
70
+ .cron-list-items {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: 6px;
74
+ }
75
+
76
+ .cron-list-group {
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: 6px;
80
+ }
81
+
82
+ .cron-list-group + .cron-list-group {
83
+ margin-top: 2px;
84
+ }
85
+
86
+ .cron-list-group-header {
87
+ position: sticky;
88
+ top: 48px;
89
+ z-index: 1;
90
+ font-size: 10px;
91
+ letter-spacing: 0.08em;
92
+ text-transform: uppercase;
93
+ color: var(--text-dim);
94
+ background: var(--bg-sidebar);
95
+ padding: 4px 2px;
96
+ }
97
+
98
+ .cron-list-group-items {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 6px;
102
+ }
103
+
104
+ .cron-list-item {
105
+ width: 100%;
106
+ border: 1px solid var(--border);
107
+ border-radius: 10px;
108
+ background: rgba(255, 255, 255, 0.015);
109
+ color: var(--text-muted);
110
+ text-align: left;
111
+ font: inherit;
112
+ padding: 8px 10px;
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 4px;
116
+ }
117
+
118
+ .cron-list-item:hover {
119
+ border-color: rgba(148, 163, 184, 0.45);
120
+ color: var(--text);
121
+ }
122
+
123
+ .cron-list-item.is-selected {
124
+ border-color: rgba(99, 235, 255, 0.55);
125
+ background: rgba(99, 235, 255, 0.08);
126
+ color: var(--text);
127
+ }
128
+
129
+ .cron-list-all {
130
+ margin-bottom: 8px;
131
+ }
132
+
133
+ .cron-list-item-row {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: space-between;
137
+ gap: 8px;
138
+ }
139
+
140
+ .cron-list-status-inline {
141
+ display: inline-flex;
142
+ align-items: center;
143
+ gap: 6px;
144
+ flex: 0 0 auto;
145
+ }
146
+
147
+ .cron-list-last-run {
148
+ font-size: 11px;
149
+ color: var(--text-muted);
150
+ line-height: 1;
151
+ }
152
+
153
+ .cron-list-item-title {
154
+ font-size: 12px;
155
+ font-weight: 700;
156
+ color: var(--text);
157
+ }
158
+
159
+ .cron-list-item-subtitle {
160
+ font-size: 11px;
161
+ color: var(--text-muted);
162
+ }
163
+
164
+ .cron-list-health-dot {
165
+ width: 8px;
166
+ height: 8px;
167
+ border-radius: 999px;
168
+ flex: 0 0 auto;
169
+ }
170
+
171
+ .cron-list-resizer {
172
+ cursor: col-resize;
173
+ position: relative;
174
+ width: 6px;
175
+ margin-left: -3px;
176
+ z-index: 4;
177
+ }
178
+
179
+ .cron-list-resizer::after {
180
+ content: "";
181
+ position: absolute;
182
+ left: 2px;
183
+ top: 0;
184
+ bottom: 0;
185
+ width: 2px;
186
+ background: transparent;
187
+ transition: background 0.12s;
188
+ }
189
+
190
+ .cron-list-resizer:hover::after,
191
+ .cron-list-resizer.is-resizing::after {
192
+ background: rgba(99, 235, 255, 0.55);
193
+ }
194
+
195
+ .cron-detail-panel {
196
+ flex: 1;
197
+ min-width: 0;
198
+ min-height: 0;
199
+ overflow: hidden;
200
+ }
201
+
202
+ .cron-detail-scroll {
203
+ height: 100%;
204
+ overflow-y: auto;
205
+ }
206
+
207
+ .cron-detail-content {
208
+ padding: 16px 20px 24px;
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 12px;
212
+ min-height: 100%;
213
+ }
214
+
215
+ .cron-prompt-editor-shell {
216
+ height: 280px;
217
+ min-height: 180px;
218
+ border: 1px solid var(--border);
219
+ border-radius: 10px;
220
+ overflow: hidden;
221
+ resize: vertical;
222
+ background: rgba(255, 255, 255, 0.01);
223
+ }
224
+
225
+ .cron-calendar-repeating-strip {
226
+ border: 1px solid var(--border);
227
+ border-radius: 10px;
228
+ padding: 8px;
229
+ background: rgba(255, 255, 255, 0.015);
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 8px;
233
+ }
234
+
235
+ .cron-calendar-repeating-list {
236
+ display: flex;
237
+ flex-wrap: wrap;
238
+ gap: 6px;
239
+ }
240
+
241
+ .cron-calendar-repeating-pill {
242
+ min-width: 220px;
243
+ max-width: 100%;
244
+ border: 1px solid transparent;
245
+ border-radius: 8px;
246
+ padding: 6px 8px;
247
+ display: flex;
248
+ flex-direction: column;
249
+ gap: 2px;
250
+ }
251
+
252
+ .cron-calendar-legend {
253
+ display: flex;
254
+ align-items: center;
255
+ gap: 6px;
256
+ flex-wrap: wrap;
257
+ }
258
+
259
+ .cron-calendar-title {
260
+ color: var(--card-label-bright);
261
+ }
262
+
263
+ .cron-calendar-legend-label {
264
+ font-size: 11px;
265
+ color: var(--text-dim);
266
+ margin-right: 2px;
267
+ }
268
+
269
+ .cron-calendar-legend-pill {
270
+ font-size: 10px;
271
+ line-height: 1;
272
+ border: 1px solid transparent;
273
+ border-radius: 999px;
274
+ padding: 4px 7px;
275
+ }
276
+
277
+ .cron-calendar-grid-wrap {
278
+ border: 1px solid var(--border);
279
+ border-radius: 10px;
280
+ overflow: auto;
281
+ background: rgba(255, 255, 255, 0.01);
282
+ }
283
+
284
+ .cron-calendar-grid-header,
285
+ .cron-calendar-grid-row {
286
+ display: grid;
287
+ grid-template-columns: 88px repeat(7, minmax(130px, 1fr));
288
+ }
289
+
290
+ .cron-calendar-day-header {
291
+ font-size: 11px;
292
+ color: var(--text-muted);
293
+ padding: 8px;
294
+ border-left: 1px solid var(--border);
295
+ border-bottom: 1px solid var(--border);
296
+ background: rgba(0, 0, 0, 0.12);
297
+ position: sticky;
298
+ top: 0;
299
+ z-index: 1;
300
+ }
301
+
302
+ .cron-calendar-day-header.is-today {
303
+ background: rgba(99, 235, 255, 0.08);
304
+ }
305
+
306
+ .cron-calendar-hour-cell {
307
+ font-size: 11px;
308
+ color: var(--text-dim);
309
+ padding: 8px;
310
+ border-bottom: 1px solid var(--border);
311
+ border-right: 1px solid var(--border);
312
+ background: rgba(0, 0, 0, 0.15);
313
+ }
314
+
315
+ .cron-calendar-grid-cell {
316
+ min-height: 44px;
317
+ border-left: 1px solid var(--border);
318
+ border-bottom: 1px solid var(--border);
319
+ padding: 5px;
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: 4px;
323
+ }
324
+
325
+ .cron-calendar-grid-cell.is-today {
326
+ background: rgba(99, 235, 255, 0.04);
327
+ }
328
+
329
+ .cron-calendar-slot-chip {
330
+ font-size: 11px;
331
+ line-height: 1.2;
332
+ border: 1px solid transparent;
333
+ border-radius: 7px;
334
+ padding: 4px 6px;
335
+ display: inline-flex;
336
+ align-items: center;
337
+ min-height: 20px;
338
+ width: 100%;
339
+ max-width: 100%;
340
+ overflow: hidden;
341
+ }
342
+
343
+ .cron-calendar-slot-overflow {
344
+ font-size: 10px;
345
+ color: var(--text-dim);
346
+ padding-left: 2px;
347
+ }
348
+
349
+ .cron-calendar-slot-tier-low {
350
+ background: rgba(34, 211, 238, 0.14);
351
+ border-color: rgba(34, 211, 238, 0.38);
352
+ color: #c8f6ff;
353
+ }
354
+
355
+ .cron-calendar-slot-tier-unknown {
356
+ background: rgba(148, 163, 184, 0.08);
357
+ border-color: rgba(148, 163, 184, 0.24);
358
+ color: #c9d2df;
359
+ }
360
+
361
+ .cron-calendar-slot-tier-medium {
362
+ background: rgba(59, 130, 246, 0.14);
363
+ border-color: rgba(59, 130, 246, 0.38);
364
+ color: #d6e6ff;
365
+ }
366
+
367
+ .cron-calendar-slot-tier-high {
368
+ background: rgba(251, 191, 36, 0.14);
369
+ border-color: rgba(251, 191, 36, 0.38);
370
+ color: #ffecc1;
371
+ }
372
+
373
+ .cron-calendar-slot-tier-very-high {
374
+ background: rgba(239, 68, 68, 0.14);
375
+ border-color: rgba(239, 68, 68, 0.38);
376
+ color: #ffd6d6;
377
+ }
378
+
379
+ .cron-calendar-slot-tier-disabled {
380
+ background: rgba(148, 163, 184, 0.08);
381
+ border-color: rgba(148, 163, 184, 0.28);
382
+ color: #98a5b8;
383
+ }
384
+
385
+ .cron-calendar-slot-upcoming {
386
+ opacity: 1;
387
+ }
388
+
389
+ .cron-calendar-slot-past {
390
+ opacity: 0.75;
391
+ }
392
+
393
+ .cron-calendar-slot-ok {
394
+ box-shadow: inset 0 0 0 1px rgba(34, 197, 94, 0.38);
395
+ }
396
+
397
+ .cron-calendar-slot-error {
398
+ box-shadow: inset 0 0 0 1px rgba(239, 68, 68, 0.45);
399
+ }
400
+
401
+ .cron-calendar-slot-skipped {
402
+ box-shadow: inset 0 0 0 1px rgba(250, 204, 21, 0.45);
403
+ }
404
+
405
+ .cron-calendar-compact-strip {
406
+ display: flex;
407
+ flex-wrap: wrap;
408
+ gap: 6px;
409
+ align-items: center;
410
+ }
411
+
412
+ .cron-calendar-compact-chip {
413
+ font-size: 11px;
414
+ line-height: 1.2;
415
+ border: 1px solid rgba(148, 163, 184, 0.32);
416
+ border-radius: 7px;
417
+ padding: 4px 8px;
418
+ display: inline-flex;
419
+ align-items: center;
420
+ gap: 6px;
421
+ max-width: 260px;
422
+ cursor: pointer;
423
+ }
424
+
425
+ .cron-calendar-compact-time {
426
+ font-size: 10px;
427
+ font-family: var(--font-mono, monospace);
428
+ opacity: 0.7;
429
+ white-space: nowrap;
430
+ }
431
+
432
+ .cron-runs-trend-bars {
433
+ display: grid;
434
+ grid-template-columns: repeat(var(--cron-runs-trend-columns, 7), minmax(0, 1fr));
435
+ gap: 4px;
436
+ align-items: end;
437
+ }
438
+
439
+ .cron-runs-trend-col {
440
+ display: flex;
441
+ flex-direction: column;
442
+ align-items: center;
443
+ gap: 4px;
444
+ width: 100%;
445
+ cursor: pointer;
446
+ transition: opacity 140ms ease, transform 140ms ease;
447
+ }
448
+
449
+ .cron-runs-trend-col.is-dimmed {
450
+ opacity: 0.35;
451
+ }
452
+
453
+ .cron-runs-trend-col.is-selected .cron-runs-trend-track {
454
+ border-color: rgba(148, 163, 184, 0.55);
455
+ box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.2);
456
+ }
457
+
458
+ .cron-runs-trend-col:focus-visible {
459
+ outline: none;
460
+ }
461
+
462
+ .cron-runs-trend-col:focus-visible .cron-runs-trend-track {
463
+ border-color: rgba(148, 163, 184, 0.65);
464
+ box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.24);
465
+ }
466
+
467
+ .cron-runs-trend-track {
468
+ width: 100%;
469
+ height: 120px;
470
+ border: 1px solid var(--border);
471
+ border-radius: 6px;
472
+ background: rgba(0, 0, 0, 0.16);
473
+ overflow: hidden;
474
+ display: flex;
475
+ align-items: flex-end;
476
+ }
477
+
478
+ .cron-runs-trend-bar {
479
+ width: 100%;
480
+ min-height: 2px;
481
+ display: flex;
482
+ flex-direction: column;
483
+ justify-content: flex-end;
484
+ }
485
+
486
+ .cron-runs-trend-segment-ok {
487
+ background: rgba(34, 197, 94, 0.75);
488
+ }
489
+
490
+ .cron-runs-trend-segment-error {
491
+ background: rgba(239, 68, 68, 0.78);
492
+ }
493
+
494
+ .cron-runs-trend-segment-skipped {
495
+ background: rgba(250, 204, 21, 0.7);
496
+ }
497
+
498
+ .cron-runs-trend-label {
499
+ font-size: 10px;
500
+ color: var(--text-dim);
501
+ min-height: 12px;
502
+ }
503
+
504
+ .cron-runs-trend-legend {
505
+ margin-top: 2px;
506
+ display: flex;
507
+ align-items: center;
508
+ gap: 10px;
509
+ }
510
+
511
+ .cron-runs-trend-legend-item {
512
+ display: inline-flex;
513
+ align-items: center;
514
+ gap: 5px;
515
+ font-size: 10px;
516
+ color: var(--text-dim);
517
+ }
518
+
519
+ .cron-runs-trend-legend-dot {
520
+ width: 7px;
521
+ height: 7px;
522
+ border-radius: 999px;
523
+ }
524
+
525
+ .cron-runs-trend-legend-dot.is-ok {
526
+ background: rgba(34, 197, 94, 0.95);
527
+ }
528
+
529
+ .cron-runs-trend-legend-dot.is-error {
530
+ background: rgba(239, 68, 68, 0.95);
531
+ }
532
+
533
+ .cron-runs-trend-legend-dot.is-skipped {
534
+ background: rgba(250, 204, 21, 0.95);
535
+ }
@@ -9,6 +9,7 @@
9
9
  --text: #c9d1d9;
10
10
  --text-muted: #6e7681;
11
11
  --text-dim: #424854;
12
+ --card-label-bright: #dbe7f6;
12
13
  --accent: #63ebff;
13
14
  --accent-dim: rgba(99, 235, 255, 0.4);
14
15
  --accent-link: rgba(99, 235, 255, 0.6);
@@ -53,6 +54,10 @@ body::before {
53
54
  color: var(--text-muted);
54
55
  }
55
56
 
57
+ .card-label-bright {
58
+ color: var(--card-label-bright);
59
+ }
60
+
56
61
  .ac-small-heading {
57
62
  font-size: 11px;
58
63
  font-weight: 500;
@@ -593,6 +598,15 @@ textarea:focus {
593
598
  flex: 1 1 0%;
594
599
  }
595
600
 
601
+ .ac-segmented-control > .inline-flex {
602
+ align-self: stretch;
603
+ display: flex;
604
+ }
605
+
606
+ .ac-segmented-control-full > .inline-flex {
607
+ flex: 1 1 0%;
608
+ }
609
+
596
610
  .ac-segmented-control-lg {
597
611
  height: 36px;
598
612
  border-radius: 12px;
@@ -612,3 +626,61 @@ textarea:focus {
612
626
  color: var(--accent);
613
627
  background: var(--bg-active);
614
628
  }
629
+
630
+ .ac-segmented-control-dark {
631
+ background: rgba(0, 0, 0, 0.25);
632
+ }
633
+
634
+ /* ── PopActions: animated action group ───────── */
635
+
636
+ .ac-pop-actions {
637
+ display: flex;
638
+ align-items: flex-start;
639
+ gap: 8px;
640
+ flex-shrink: 0;
641
+ }
642
+
643
+ .ac-pop-actions-hidden {
644
+ display: none;
645
+ }
646
+
647
+ .ac-pop-actions-in > * {
648
+ animation: acPopIn 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both;
649
+ }
650
+
651
+ .ac-pop-actions-in > *:nth-child(2) {
652
+ animation-delay: 0.06s;
653
+ }
654
+
655
+ .ac-pop-actions-in .ac-btn-cyan {
656
+ animation: acPopIn 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) 0.06s both;
657
+ }
658
+
659
+ .ac-pop-actions-out > * {
660
+ animation: acPopOut 0.18s ease-in both;
661
+ }
662
+
663
+ .ac-pop-actions-out > *:first-child {
664
+ animation-delay: 0.03s;
665
+ }
666
+
667
+ .ac-pop-actions > button:active:not(:disabled) {
668
+ transform: translateY(1px) scale(0.98);
669
+ }
670
+
671
+ @keyframes acPopIn {
672
+ from { opacity: 0; transform: scale(0.85); }
673
+ to { opacity: 1; transform: scale(1); }
674
+ }
675
+
676
+ @keyframes acPopOut {
677
+ from { opacity: 1; transform: scale(1); }
678
+ to { opacity: 0; transform: scale(0.85); }
679
+ }
680
+
681
+ .watchdog-logs-panel {
682
+ min-height: 160px;
683
+ max-height: 80vh;
684
+ resize: vertical;
685
+ }
686
+
@@ -16,6 +16,7 @@ import { AppSidebar } from "./components/sidebar.js";
16
16
  import {
17
17
  AgentsRoute,
18
18
  BrowseRoute,
19
+ CronRoute,
19
20
  DoctorRoute,
20
21
  EnvarsRoute,
21
22
  GeneralRoute,
@@ -75,10 +76,20 @@ const App = () => {
75
76
  } = useAgents();
76
77
 
77
78
  const isAgentsRoute = location.startsWith("/agents");
79
+ const isCronRoute = location.startsWith("/cron");
78
80
  const selectedAgentId = (() => {
79
81
  const match = location.match(/^\/agents\/([^/]+)/);
80
82
  return match ? decodeURIComponent(match[1]) : "";
81
83
  })();
84
+ const agentDetailTab = (() => {
85
+ const match = location.match(/^\/agents\/[^/]+\/([^/]+)/);
86
+ const tab = match ? match[1] : "";
87
+ return tab === "tools" ? "tools" : "overview";
88
+ })();
89
+ const selectedCronJobId = (() => {
90
+ const match = location.match(/^\/cron\/([^/]+)/);
91
+ return match ? decodeURIComponent(match[1]) : "";
92
+ })();
82
93
 
83
94
  useEffect(() => {
84
95
  if (!isAgentsRoute) return;
@@ -233,15 +244,31 @@ const App = () => {
233
244
  saving=${agentsState.saving}
234
245
  agentsActions=${agentsActions}
235
246
  selectedAgentId=${selectedAgentId}
247
+ activeTab=${agentDetailTab}
236
248
  onSelectAgent=${(agentId) => setLocation(`/agents/${encodeURIComponent(agentId)}`)}
249
+ onSelectTab=${(tab) => {
250
+ const safePath = tab && tab !== "overview"
251
+ ? `/agents/${encodeURIComponent(selectedAgentId)}/${tab}`
252
+ : `/agents/${encodeURIComponent(selectedAgentId)}`;
253
+ setLocation(safePath);
254
+ }}
237
255
  onNavigateToBrowseFile=${browseActions.navigateToBrowseFile}
238
256
  onSetLocation=${setLocation}
239
257
  />
240
258
  </div>
259
+ <div
260
+ class="app-content-pane cron-pane"
261
+ style=${{ display: isCronRoute ? "block" : "none" }}
262
+ >
263
+ <${CronRoute}
264
+ jobId=${selectedCronJobId}
265
+ onSetLocation=${setLocation}
266
+ />
267
+ </div>
241
268
  <div
242
269
  class="app-content-pane"
243
270
  onscroll=${shellActions.handlePaneScroll}
244
- style=${{ display: browseState.isBrowseRoute || isAgentsRoute ? "none" : "block" }}
271
+ style=${{ display: browseState.isBrowseRoute || isAgentsRoute || isCronRoute ? "none" : "block" }}
245
272
  >
246
273
  <div
247
274
  class=${`mobile-topbar ${shellState.mobileTopbarScrolled ? "is-scrolled" : ""}`}
@@ -269,7 +296,7 @@ const App = () => {
269
296
  </span>
270
297
  </div>
271
298
  <div class="max-w-2xl w-full mx-auto">
272
- ${!browseState.isBrowseRoute && !isAgentsRoute
299
+ ${!browseState.isBrowseRoute && !isAgentsRoute && !isCronRoute
273
300
  ? html`
274
301
  <${Switch}>
275
302
  <${Route} path="/general">
@@ -408,11 +435,19 @@ const App = () => {
408
435
  `;
409
436
  };
410
437
 
411
- render(
412
- html`
413
- <${Router} hook=${useHashLocation}>
414
- <${App} />
415
- </${Router}>
416
- `,
417
- document.getElementById("app"),
418
- );
438
+ const rootElement = document.getElementById("app");
439
+ if (rootElement) {
440
+ const appBootCounter = "__alphaclawSetupAppBootCount";
441
+ window[appBootCounter] = Number(window[appBootCounter] || 0) + 1;
442
+ // Defensive: clear root so duplicate bootstraps cannot stack full app shells.
443
+ render(null, rootElement);
444
+ rootElement.replaceChildren();
445
+ render(
446
+ html`
447
+ <${Router} hook=${useHashLocation}>
448
+ <${App} />
449
+ </${Router}>
450
+ `,
451
+ rootElement,
452
+ );
453
+ }