@flame0510/project-aether 1.1.9

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 (290) hide show
  1. package/README.md +150 -0
  2. package/agent-templates/README.md +37 -0
  3. package/agent-templates/argus/AGENTS.md +52 -0
  4. package/agent-templates/argus/IDENTITY.md +7 -0
  5. package/agent-templates/argus/MEMORY.md +9 -0
  6. package/agent-templates/argus/SOUL.md +24 -0
  7. package/agent-templates/argus/TOOLS.md +8 -0
  8. package/agent-templates/atlas/AGENTS.md +47 -0
  9. package/agent-templates/atlas/HEARTBEAT.md +10 -0
  10. package/agent-templates/atlas/IDENTITY.md +6 -0
  11. package/agent-templates/atlas/MEMORY.md +9 -0
  12. package/agent-templates/atlas/SOUL.md +21 -0
  13. package/agent-templates/atlas/TOOLS.md +4 -0
  14. package/agent-templates/base-image/.trigger +0 -0
  15. package/agent-templates/base-image/Dockerfile +90 -0
  16. package/agent-templates/base-image/entrypoint.sh +37 -0
  17. package/agent-templates/custom/AGENTS.md +29 -0
  18. package/agent-templates/custom/BOOTSTRAP.md +56 -0
  19. package/agent-templates/custom/MEMORY.md +8 -0
  20. package/agent-templates/prometheus/AGENTS.md +37 -0
  21. package/agent-templates/prometheus/IDENTITY.md +6 -0
  22. package/agent-templates/prometheus/MEMORY.md +9 -0
  23. package/agent-templates/prometheus/SOUL.md +21 -0
  24. package/agent-templates/prometheus/TOOLS.md +4 -0
  25. package/app/agents/ChannelManager.tsx +465 -0
  26. package/app/agents/ImageDownloadBanner.tsx +159 -0
  27. package/app/agents/PageClient.tsx +1285 -0
  28. package/app/agents/create/PageClient.tsx +437 -0
  29. package/app/agents/create/page.tsx +126 -0
  30. package/app/agents/loading.tsx +14 -0
  31. package/app/agents/page.tsx +5 -0
  32. package/app/api/agents/[id]/backup/route.ts +169 -0
  33. package/app/api/agents/[id]/channels/pairing/route.ts +69 -0
  34. package/app/api/agents/[id]/channels/route.ts +14 -0
  35. package/app/api/agents/[id]/channels/telegram/route.ts +50 -0
  36. package/app/api/agents/[id]/lifecycle/route.ts +94 -0
  37. package/app/api/agents/[id]/recreate/route.ts +199 -0
  38. package/app/api/agents/[id]/restart/route.ts +33 -0
  39. package/app/api/agents/[id]/restore/route.ts +81 -0
  40. package/app/api/agents/[id]/route.ts +280 -0
  41. package/app/api/agents/channels-summary/route.ts +39 -0
  42. package/app/api/agents/config-json/route.ts +58 -0
  43. package/app/api/agents/create/route.ts +488 -0
  44. package/app/api/agents/download-image/route.ts +32 -0
  45. package/app/api/agents/image-status/route.ts +126 -0
  46. package/app/api/agents/route.ts +213 -0
  47. package/app/api/agents/token/route.ts +94 -0
  48. package/app/api/agents-active/route.ts +190 -0
  49. package/app/api/agents-config/route.ts +629 -0
  50. package/app/api/alerts/smoke/route.ts +26 -0
  51. package/app/api/aliases/add/route.ts +28 -0
  52. package/app/api/aliases/remove/route.ts +27 -0
  53. package/app/api/assistant/route.ts +204 -0
  54. package/app/api/auth/check/route.ts +38 -0
  55. package/app/api/auth/login/route.ts +85 -0
  56. package/app/api/auth/logout/route.ts +7 -0
  57. package/app/api/auth/status/route.ts +33 -0
  58. package/app/api/config/env/route.ts +132 -0
  59. package/app/api/config/restart/route.ts +26 -0
  60. package/app/api/containers/route.ts +75 -0
  61. package/app/api/cost-override/route.ts +46 -0
  62. package/app/api/costs/route.ts +84 -0
  63. package/app/api/credentials/[id]/reveal/route.ts +21 -0
  64. package/app/api/credentials/[id]/route.ts +52 -0
  65. package/app/api/credentials/[id]/sync/route.ts +58 -0
  66. package/app/api/credentials/detect/route.ts +246 -0
  67. package/app/api/credentials/route.ts +33 -0
  68. package/app/api/crons/[id]/route.ts +69 -0
  69. package/app/api/crons/route.ts +12 -0
  70. package/app/api/crons/runs/route.ts +31 -0
  71. package/app/api/debug/route.ts +11 -0
  72. package/app/api/envcheck/route.ts +18 -0
  73. package/app/api/events/route.ts +24 -0
  74. package/app/api/gateway/agent/route.ts +154 -0
  75. package/app/api/gateway/provider/balance/route.ts +38 -0
  76. package/app/api/gateway/provider/keys.ts +38 -0
  77. package/app/api/gateway/provider/oauth/route.ts +304 -0
  78. package/app/api/gateway/provider/route.ts +209 -0
  79. package/app/api/gateway/route.ts +223 -0
  80. package/app/api/gateway/sync.ts +189 -0
  81. package/app/api/lineage/route.ts +54 -0
  82. package/app/api/memory-context/route.ts +15 -0
  83. package/app/api/metrics/route.ts +35 -0
  84. package/app/api/models/route.ts +77 -0
  85. package/app/api/plugins/route.js +37 -0
  86. package/app/api/provider/upstream.ts +142 -0
  87. package/app/api/provider/v1/chat/completions/route.ts +146 -0
  88. package/app/api/provider/v1/models/route.ts +167 -0
  89. package/app/api/provider-usage/route.ts +629 -0
  90. package/app/api/session/route.ts +39 -0
  91. package/app/api/sessions/[id]/route.ts +38 -0
  92. package/app/api/sessions/route.ts +23 -0
  93. package/app/api/setup/agent-image/route.ts +102 -0
  94. package/app/api/setup/password/route.ts +111 -0
  95. package/app/api/setup/restart/route.ts +76 -0
  96. package/app/api/skills/delete/route.js +105 -0
  97. package/app/api/skills/promote/route.js +129 -0
  98. package/app/api/skills/route.js +203 -0
  99. package/app/api/skills/save/route.js +142 -0
  100. package/app/api/stats/route.ts +34 -0
  101. package/app/api/stats-since/route.ts +20 -0
  102. package/app/api/stream/route.ts +70 -0
  103. package/app/api/system-health/route.ts +233 -0
  104. package/app/api/tool-calls/route.ts +24 -0
  105. package/app/api/tools-config/route.ts +163 -0
  106. package/app/api/update-check/route.ts +127 -0
  107. package/app/api/version/route.ts +25 -0
  108. package/app/api/wizard/complete/route.ts +32 -0
  109. package/app/api/wizard/reset/route.ts +28 -0
  110. package/app/api/wizard/status/route.ts +28 -0
  111. package/app/api/workspace/route.ts +392 -0
  112. package/app/api/workspace/stream/route.ts +136 -0
  113. package/app/components/AppShell.tsx +33 -0
  114. package/app/components/AuthGuard.tsx +91 -0
  115. package/app/components/CostBreakdown.tsx +37 -0
  116. package/app/components/DashboardHeader.tsx +73 -0
  117. package/app/components/DashboardLayout.tsx +76 -0
  118. package/app/components/DashboardToolbar.tsx +61 -0
  119. package/app/components/FloatingActions.tsx +33 -0
  120. package/app/components/Icons.tsx +78 -0
  121. package/app/components/LineageGraphPage.tsx +142 -0
  122. package/app/components/LiveFeed.tsx +48 -0
  123. package/app/components/MobileBottomNav.tsx +84 -0
  124. package/app/components/ModelPickerModal.tsx +94 -0
  125. package/app/components/PasswordInput.tsx +86 -0
  126. package/app/components/PulseChat.tsx +362 -0
  127. package/app/components/Rev4aLoader.tsx +48 -0
  128. package/app/components/SessionDrawer.tsx +263 -0
  129. package/app/components/SessionTopology.tsx +289 -0
  130. package/app/components/Sidebar.tsx +294 -0
  131. package/app/components/Skeleton.tsx +133 -0
  132. package/app/components/SystemCockpit.tsx +169 -0
  133. package/app/components/VersionBanner.tsx +105 -0
  134. package/app/components/WizardButton.tsx +30 -0
  135. package/app/components/ui/Badge.tsx +30 -0
  136. package/app/components/ui/Button.tsx +59 -0
  137. package/app/components/ui/ConfirmModal.tsx +46 -0
  138. package/app/components/ui/FilterBar.tsx +33 -0
  139. package/app/components/ui/Input.tsx +25 -0
  140. package/app/components/ui/ItemList.tsx +83 -0
  141. package/app/components/ui/LoadingSpinner.tsx +30 -0
  142. package/app/components/ui/Metric.tsx +22 -0
  143. package/app/components/ui/Modal.tsx +42 -0
  144. package/app/components/ui/Page.tsx +18 -0
  145. package/app/components/ui/Pill.tsx +6 -0
  146. package/app/components/ui/PropertyList.tsx +24 -0
  147. package/app/components/ui/Select.tsx +26 -0
  148. package/app/components/ui/StatusCard.tsx +32 -0
  149. package/app/components/ui/Surface.tsx +14 -0
  150. package/app/components/ui/Tabs.tsx +55 -0
  151. package/app/components/ui/TemplateOption.tsx +59 -0
  152. package/app/components/ui/Toast.tsx +44 -0
  153. package/app/components/ui/index.ts +19 -0
  154. package/app/components/ui/tokens.ts +18 -0
  155. package/app/config/PageClient.tsx +293 -0
  156. package/app/config/loading.tsx +10 -0
  157. package/app/config/page.tsx +7 -0
  158. package/app/containers/ContainersClient.tsx +129 -0
  159. package/app/containers/loading.tsx +21 -0
  160. package/app/containers/page.tsx +5 -0
  161. package/app/containers/terminal/[id]/TerminalClient.tsx +313 -0
  162. package/app/containers/terminal/[id]/page.tsx +6 -0
  163. package/app/credentials/PageClient.tsx +598 -0
  164. package/app/credentials/loading.tsx +10 -0
  165. package/app/credentials/page.tsx +7 -0
  166. package/app/crons/PageClient.tsx +538 -0
  167. package/app/crons/loading.tsx +10 -0
  168. package/app/crons/page.tsx +5 -0
  169. package/app/design-system.ts +57 -0
  170. package/app/gateway/PageClient.tsx +1048 -0
  171. package/app/gateway/loading.tsx +24 -0
  172. package/app/gateway/page.tsx +5 -0
  173. package/app/globals.css +1474 -0
  174. package/app/layout.tsx +45 -0
  175. package/app/lib/models-context.tsx +41 -0
  176. package/app/lineage/loading.tsx +10 -0
  177. package/app/lineage/page.tsx +13 -0
  178. package/app/loading.tsx +16 -0
  179. package/app/login/page.tsx +160 -0
  180. package/app/memory/MemoryContextPageClient.tsx +196 -0
  181. package/app/memory/loading.tsx +10 -0
  182. package/app/memory/page.tsx +8 -0
  183. package/app/page.tsx +31 -0
  184. package/app/plugins/PageClient.tsx +296 -0
  185. package/app/plugins/loading.tsx +10 -0
  186. package/app/plugins/page.tsx +5 -0
  187. package/app/setup/PageClient.tsx +227 -0
  188. package/app/setup/loading.tsx +21 -0
  189. package/app/setup/page.tsx +7 -0
  190. package/app/skills/PageClient.tsx +499 -0
  191. package/app/skills/loading.tsx +10 -0
  192. package/app/skills/page.tsx +5 -0
  193. package/app/tools/ToolsPageClient.tsx +308 -0
  194. package/app/tools/loading.tsx +10 -0
  195. package/app/tools/page.tsx +62 -0
  196. package/app/tools/tools-catalog.ts +141 -0
  197. package/app/wizard/PageClient.tsx +485 -0
  198. package/app/wizard/icons.tsx +89 -0
  199. package/app/wizard/loading.tsx +24 -0
  200. package/app/wizard/page.tsx +7 -0
  201. package/app/wizard/storage.ts +33 -0
  202. package/app/wizard/useWizard.ts +101 -0
  203. package/app/workspace/WorkspaceClient.tsx +633 -0
  204. package/app/workspace/loading.tsx +36 -0
  205. package/app/workspace/page.tsx +8 -0
  206. package/bin/docker-entrypoint.sh +21 -0
  207. package/bin/postinstall.js +20 -0
  208. package/bin/rev4a.js +696 -0
  209. package/daemon.js +783 -0
  210. package/docs/ARCHITECTURE.md +557 -0
  211. package/docs/CONTAINER-TERMINAL.md +280 -0
  212. package/docs/DESIGN-SYSTEM.md +99 -0
  213. package/docs/FRONTEND-ARCHITECTURE.md +119 -0
  214. package/docs/REV4A.md +504 -0
  215. package/docs/dev/API-REFERENCE.md +1835 -0
  216. package/docs/dev/DATABASE.md +224 -0
  217. package/docs/dev/GATEWAY.md +300 -0
  218. package/docs/dev/PROVIDERS.md +229 -0
  219. package/docs/dev/SESSION-MAINTENANCE-PLAN.md +607 -0
  220. package/docs/dev/WORKSPACE.md +199 -0
  221. package/docs/rag/DATA-FRESHNESS.md +109 -0
  222. package/docs/rag/GLOSSARY.md +125 -0
  223. package/docs/rag/REV4A-OVERVIEW.md +135 -0
  224. package/docs/rag/WHAT-I-CAN-ANSWER.md +124 -0
  225. package/lib/agent-setup.ts +130 -0
  226. package/lib/alerts.ts +115 -0
  227. package/lib/apiFetch.ts +7 -0
  228. package/lib/auth.ts +28 -0
  229. package/lib/billing.ts +100 -0
  230. package/lib/buildAgentImage.ts +346 -0
  231. package/lib/channelManager.ts +386 -0
  232. package/lib/container.ts +60 -0
  233. package/lib/credentials/db.ts +86 -0
  234. package/lib/credentials/delivery.ts +448 -0
  235. package/lib/credentials/detect.ts +149 -0
  236. package/lib/credentials/hash.ts +9 -0
  237. package/lib/credentials/providers.ts +110 -0
  238. package/lib/credentials/vault.ts +195 -0
  239. package/lib/db-bootstrap.d.ts +3 -0
  240. package/lib/db-bootstrap.mjs +144 -0
  241. package/lib/db.ts +25 -0
  242. package/lib/docker-utils.ts +60 -0
  243. package/lib/hooks/useDashboard.ts +264 -0
  244. package/lib/hooks/useRev4aTimezone.ts +28 -0
  245. package/lib/hooks/useScreenshot.ts +30 -0
  246. package/lib/memory-context.ts +385 -0
  247. package/lib/model-catalogue.ts +131 -0
  248. package/lib/model-pricing.ts +63 -0
  249. package/lib/openclaw-cron.ts +350 -0
  250. package/lib/patterns/ApiAdapter.ts +73 -0
  251. package/lib/patterns/ApiClient.ts +65 -0
  252. package/lib/patterns/EventBus.ts +113 -0
  253. package/lib/patterns/FilterStrategy.ts +79 -0
  254. package/lib/patterns/SessionFactory.ts +117 -0
  255. package/lib/patterns/sessionPresentation.ts +117 -0
  256. package/lib/provider-balance.ts +163 -0
  257. package/lib/requireAuth.tsx +38 -0
  258. package/lib/rev4a-auth.d.ts +3 -0
  259. package/lib/rev4a-auth.js +36 -0
  260. package/lib/rev4a-paths.ts +145 -0
  261. package/lib/timezone.ts +46 -0
  262. package/lib/types/index.ts +96 -0
  263. package/lib/utils/format.ts +82 -0
  264. package/lineage.js +56 -0
  265. package/model-pricing.json +482 -0
  266. package/models.config.json +844 -0
  267. package/next-env.d.ts +6 -0
  268. package/next.config.mjs +7 -0
  269. package/package.json +82 -0
  270. package/public/apple-touch-icon.png +0 -0
  271. package/public/favicon-64.png +0 -0
  272. package/public/favicon.svg +58 -0
  273. package/public/icon-192-maskable.png +0 -0
  274. package/public/icon-192.png +0 -0
  275. package/public/icon-512-maskable.png +0 -0
  276. package/public/icon-512.png +0 -0
  277. package/public/manifest.json +38 -0
  278. package/public/rev4a-logo.png +0 -0
  279. package/public/rev4a-logo.svg +58 -0
  280. package/rev4a-rules/AGENTS.md +37 -0
  281. package/scripts/add-auth.cjs +185 -0
  282. package/scripts/backup.sh +66 -0
  283. package/scripts/check-providers.mjs +143 -0
  284. package/scripts/docker/traefik/docker-compose.yml +32 -0
  285. package/scripts/docker/traefik/dynamic/.gitkeep +18 -0
  286. package/scripts/docker/traefik/traefik.yml +37 -0
  287. package/scripts/remove-auth-profile.js +56 -0
  288. package/scripts/restore.sh +82 -0
  289. package/terminal-ws-server.js +139 -0
  290. package/tsconfig.json +42 -0
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import type { Costs } from '@/lib/types';
5
+ import { formatUsd } from '@/lib/utils/format';
6
+ import { useScreenshot } from '@/lib/hooks/useScreenshot';
7
+ import { useRev4aTimezone } from '@/lib/hooks/useRev4aTimezone';
8
+ import { formatDateTimeInTimezone } from '@/lib/timezone';
9
+ import { Skeleton } from './Skeleton';
10
+
11
+ interface DashboardHeaderProps {
12
+ costs: Costs;
13
+ loading?: boolean;
14
+ hideLogo?: boolean;
15
+ title?: string;
16
+ }
17
+
18
+ const CameraIcon = () => (
19
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
20
+ <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
21
+ <circle cx="12" cy="13" r="4"/>
22
+ </svg>
23
+ );
24
+
25
+ export default function DashboardHeader({ costs, loading: dataLoading = false, hideLogo = false, title }: DashboardHeaderProps) {
26
+ const [clock, setClock] = useState('');
27
+ const { loading, takeScreenshot } = useScreenshot();
28
+ const timezone = useRev4aTimezone();
29
+
30
+ useEffect(() => {
31
+ const tick = () =>
32
+ setClock(formatDateTimeInTimezone(Date.now(), {
33
+ day: '2-digit', month: '2-digit', year: 'numeric',
34
+ hour: '2-digit', minute: '2-digit', second: '2-digit',
35
+ hour12: false,
36
+ }, timezone));
37
+ tick();
38
+ const id = setInterval(tick, 1000);
39
+ return () => clearInterval(id);
40
+ }, [timezone]);
41
+
42
+ return (
43
+ <header className="header">
44
+ <div className="logo">
45
+ {!hideLogo && <img src="/rev4a-logo.svg" alt="Rev4a" style={{ width: 22, height: 22, objectFit: 'contain' }} />}
46
+ <span>{title ?? 'REV4A'}</span>
47
+ </div>
48
+ <div className="meta">
49
+ <div suppressHydrationWarning>
50
+ <span className="meta-value" suppressHydrationWarning>{clock}</span>
51
+ </div>
52
+ <div>
53
+ <span className="meta-label">TODAY COST</span>
54
+ <span className="meta-value cost">{dataLoading ? <Skeleton className="skeleton--text" style={{ width: 58 }} /> : formatUsd(costs.today)}</span>
55
+ </div>
56
+ <div>
57
+ <span className="meta-label">ALL TIME</span>
58
+ <span className="meta-value cost">{dataLoading ? <Skeleton className="skeleton--text" style={{ width: 72 }} /> : formatUsd(costs.allTime)}</span>
59
+ </div>
60
+ <button
61
+ type="button"
62
+ className="agent-btn screenshot-btn-desktop"
63
+ onClick={takeScreenshot}
64
+ disabled={loading}
65
+ title="Screenshot"
66
+ style={{ padding: '4px 8px' }}
67
+ >
68
+ {loading ? '…' : <CameraIcon />}
69
+ </button>
70
+ </div>
71
+ </header>
72
+ );
73
+ }
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import { useResponsive } from '../design-system';
4
+ import type { Costs } from '@/lib/types';
5
+ import { useDashboard } from '@/lib/hooks/useDashboard';
6
+ import { useScreenshot } from '@/lib/hooks/useScreenshot';
7
+ import DashboardHeader from './DashboardHeader';
8
+ import SystemCockpit from './SystemCockpit';
9
+ import SessionDrawer from './SessionDrawer';
10
+
11
+ interface DashboardLayoutProps {
12
+ initialCosts?: Partial<Costs>;
13
+ }
14
+
15
+ export default function DashboardLayout({ initialCosts }: DashboardLayoutProps) {
16
+ const {
17
+ sessions,
18
+ events,
19
+ costs,
20
+ filter,
21
+ visibleEvents,
22
+ selectedSessionId,
23
+ selectSession,
24
+ hasSessionsLoaded,
25
+ hasCostLoaded,
26
+ } = useDashboard({ initialCosts });
27
+
28
+ const isMobile = useResponsive('md');
29
+ const { loading: screenshotLoading, takeScreenshot } = useScreenshot();
30
+
31
+ return (
32
+ <main className="shell">
33
+ <DashboardHeader costs={costs} loading={!hasCostLoaded} hideLogo title="DASHBOARD" />
34
+ {isMobile && (
35
+ <div style={{ display: 'flex', gap: 8, padding: '8px 12px', borderBottom: '1px solid var(--border)', background: 'var(--bg2)', justifyContent: 'flex-end' }}>
36
+ <button
37
+ type="button"
38
+ onClick={takeScreenshot}
39
+ disabled={screenshotLoading}
40
+ title="Screenshot"
41
+ style={{
42
+ padding: '6px 10px',
43
+ border: '1px solid var(--border)',
44
+ background: 'transparent',
45
+ color: 'var(--text-dim)',
46
+ cursor: 'pointer',
47
+ display: 'flex',
48
+ alignItems: 'center',
49
+ }}
50
+ >
51
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
52
+ <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
53
+ <circle cx="12" cy="13" r="4"/>
54
+ </svg>
55
+ </button>
56
+ </div>
57
+ )}
58
+
59
+ <section className="layout layout--cockpit">
60
+ <SystemCockpit
61
+ sessions={sessions}
62
+ events={events}
63
+ costs={costs}
64
+ filter={filter}
65
+ visibleEvents={visibleEvents}
66
+ loading={!hasSessionsLoaded}
67
+ />
68
+ </section>
69
+
70
+ <SessionDrawer
71
+ sessionId={selectedSessionId}
72
+ onClose={() => selectSession(null)}
73
+ />
74
+ </main>
75
+ );
76
+ }
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ import type { FilterConfig, Period } from '@/lib/types';
4
+ import { PERIODS } from '@/lib/types';
5
+
6
+ interface DashboardToolbarProps {
7
+ filter: FilterConfig;
8
+ onChange: (patch: Partial<FilterConfig>) => void;
9
+ agents?: string[];
10
+ }
11
+
12
+ export default function DashboardToolbar({ filter, onChange, agents = ['all'] }: DashboardToolbarProps) {
13
+ return (
14
+ <section className="toolbar">
15
+ <span className="toolbar-label">AGENTI</span>
16
+
17
+ {agents.map((name) => (
18
+ <button
19
+ key={name}
20
+ type="button"
21
+ className={`agent-btn ${filter.agent === name ? 'active' : ''}`}
22
+ onClick={() => onChange({ agent: name })}
23
+ >
24
+ {name}
25
+ </button>
26
+ ))}
27
+
28
+ <span className="toolbar-sep">|</span>
29
+
30
+ <button
31
+ type="button"
32
+ className={`agent-btn ${filter.showOnlyActive ? 'active' : ''}`}
33
+ onClick={() => onChange({ showOnlyActive: !filter.showOnlyActive })}
34
+ >
35
+ solo attivi
36
+ </button>
37
+
38
+ <button
39
+ type="button"
40
+ className={`agent-btn ${!filter.showCron ? 'active' : ''}`}
41
+ onClick={() => onChange({ showCron: !filter.showCron })}
42
+ >
43
+ no cron
44
+ </button>
45
+
46
+ <span className="toolbar-sep">|</span>
47
+ <span className="toolbar-label">PERIODO</span>
48
+
49
+ {PERIODS.map((p) => (
50
+ <button
51
+ key={p}
52
+ type="button"
53
+ className={`agent-btn ${filter.period === p ? 'active' : ''}`}
54
+ onClick={() => onChange({ period: p })}
55
+ >
56
+ {p}
57
+ </button>
58
+ ))}
59
+ </section>
60
+ );
61
+ }
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { usePathname } from 'next/navigation';
4
+ import PulseChat from './PulseChat';
5
+ import WizardButton from './WizardButton';
6
+
7
+ /**
8
+ * FloatingActions
9
+ *
10
+ * Single container wrapping both floating buttons:
11
+ * - WizardButton (left)
12
+ * - PulseChat (right)
13
+ *
14
+ * Hidden on login, setup, and /wizard full-page.
15
+ */
16
+ export default function FloatingActions() {
17
+ const pathname = usePathname();
18
+
19
+ if (pathname === '/login' || pathname === '/setup' || pathname === '/wizard') {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <div className="floating-actions">
25
+ <div className="floating-actions__left">
26
+ <WizardButton />
27
+ </div>
28
+ <div className="floating-actions__right">
29
+ <PulseChat />
30
+ </div>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ import { SVGProps } from 'react';
4
+
5
+ type IconProps = SVGProps<SVGSVGElement> & { size?: number };
6
+
7
+ export function EyeIcon({ size = 20, ...props }: IconProps) {
8
+ return (
9
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
10
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
11
+ <circle cx="12" cy="12" r="3" />
12
+ </svg>
13
+ );
14
+ }
15
+
16
+ export function EyeOffIcon({ size = 20, ...props }: IconProps) {
17
+ return (
18
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
19
+ <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" />
20
+ <path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" />
21
+ <line x1="1" y1="1" x2="23" y2="23" />
22
+ </svg>
23
+ );
24
+ }
25
+
26
+ /** Shared wrapper so all section icons render with identical stroke styling. */
27
+ function Svg({ size = 20, children, ...props }: IconProps & { children: React.ReactNode }) {
28
+ return (
29
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
30
+ {children}
31
+ </svg>
32
+ );
33
+ }
34
+
35
+ // PROPERTIES
36
+ export function InfoIcon(p: IconProps) {
37
+ return <Svg {...p}><circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" /></Svg>;
38
+ }
39
+
40
+ // CONFIG (openclaw.json)
41
+ export function BracesIcon(p: IconProps) {
42
+ return <Svg {...p}><path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h1" /><path d="M16 3h1a2 2 0 0 1 2 2v5a2 2 0 0 0 2 2 2 2 0 0 0-2 2v5a2 2 0 0 1-2 2h-1" /></Svg>;
43
+ }
44
+
45
+ // ACCESS TOKEN
46
+ export function KeyIcon(p: IconProps) {
47
+ return <Svg {...p}><path d="m15.5 7.5 3 3L22 7l-3-3" /><path d="m21 2-9.6 9.6" /><circle cx="7.5" cy="15.5" r="5.5" /></Svg>;
48
+ }
49
+
50
+ // DANGER ZONE (delete)
51
+ export function TrashIcon(p: IconProps) {
52
+ return <Svg {...p}><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" y1="11" x2="10" y2="17" /><line x1="14" y1="11" x2="14" y2="17" /></Svg>;
53
+ }
54
+
55
+ // ENV (AGENT_ / MODEL_)
56
+ export function TerminalIcon(p: IconProps) {
57
+ return <Svg {...p}><polyline points="4 17 10 11 4 5" /><line x1="12" y1="19" x2="20" y2="19" /></Svg>;
58
+ }
59
+
60
+ // INTEGRATIONS
61
+ export function LinkIcon(p: IconProps) {
62
+ return <Svg {...p}><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></Svg>;
63
+ }
64
+
65
+ // BACKUP
66
+ export function ArchiveIcon(p: IconProps) {
67
+ return <Svg {...p}><polyline points="21 8 21 21 3 21 3 8" /><rect x="1" y="3" width="22" height="5" /><line x1="10" y1="12" x2="14" y2="12" /></Svg>;
68
+ }
69
+
70
+ // RESTART
71
+ export function RotateIcon(p: IconProps) {
72
+ return <Svg {...p}><polyline points="23 4 23 10 17 10" /><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" /></Svg>;
73
+ }
74
+
75
+ // RECREATE (rebuild from image)
76
+ export function PackageIcon(p: IconProps) {
77
+ return <Svg {...p}><line x1="16.5" y1="9.4" x2="7.5" y2="4.21" /><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /><polyline points="3.27 6.96 12 12.01 20.73 6.96" /><line x1="12" y1="22.08" x2="12" y2="12" /></Svg>;
78
+ }
@@ -0,0 +1,142 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { useResponsive } from '../design-system';
5
+ import type { CSSProperties } from 'react';
6
+ import type { Costs } from '@/lib/types';
7
+ import { useDashboard } from '@/lib/hooks/useDashboard';
8
+ import { useScreenshot } from '@/lib/hooks/useScreenshot';
9
+ import DashboardHeader from './DashboardHeader';
10
+ import DashboardToolbar from './DashboardToolbar';
11
+ import LiveFeed from './LiveFeed';
12
+ import CostBreakdown from './CostBreakdown';
13
+ import SessionTopology, { type SessionTopologyHandle } from './SessionTopology';
14
+ import SessionDrawer from './SessionDrawer';
15
+
16
+ interface LineageGraphPageProps {
17
+ initialCosts?: Partial<Costs>;
18
+ }
19
+
20
+ export default function LineageGraphPage({ initialCosts }: LineageGraphPageProps) {
21
+ const {
22
+ costs,
23
+ filter,
24
+ selectedSessionId,
25
+ availableAgents,
26
+ visibleSessions,
27
+ visibleEvents,
28
+ setFilter,
29
+ selectSession,
30
+ hasSessionsLoaded,
31
+ hasCostLoaded,
32
+ } = useDashboard({ initialCosts });
33
+
34
+ const topologyRef = useRef<SessionTopologyHandle>(null);
35
+ const isMobile = useResponsive('md');
36
+ const { loading: screenshotLoading, takeScreenshot } = useScreenshot();
37
+ const [mobileTab, setMobileTab] = useState<'graph' | 'feed' | 'costs'>('graph');
38
+
39
+ const resetButtonStyle: CSSProperties = isMobile
40
+ ? { width: '100%', justifyContent: 'center', minHeight: 38 }
41
+ : { flexShrink: 0, minWidth: 138 };
42
+
43
+ return (
44
+ <main className="shell">
45
+ <DashboardHeader costs={costs} loading={!hasCostLoaded} hideLogo title="LINEAGE" />
46
+ <DashboardToolbar filter={filter} onChange={setFilter} agents={availableAgents} />
47
+
48
+ {isMobile && (
49
+ <div style={{ display: 'flex', gap: 8, padding: '8px 12px', borderBottom: '1px solid var(--border)', background: 'var(--bg2)' }}>
50
+ {[
51
+ { key: 'graph', label: 'GRAFO' },
52
+ { key: 'feed', label: 'FEED' },
53
+ { key: 'costs', label: 'COSTI' },
54
+ ].map((t) => (
55
+ <button
56
+ key={t.key}
57
+ onClick={() => setMobileTab(t.key as 'graph' | 'feed' | 'costs')}
58
+ style={{
59
+ fontSize: 10,
60
+ letterSpacing: '0.08em',
61
+ padding: '6px 10px',
62
+ border: '1px solid var(--border)',
63
+ background: mobileTab === t.key ? 'var(--bg3)' : 'transparent',
64
+ color: mobileTab === t.key ? 'var(--violet)' : 'var(--text-dim)',
65
+ }}
66
+ >
67
+ {t.label}
68
+ </button>
69
+ ))}
70
+ <button
71
+ type="button"
72
+ onClick={takeScreenshot}
73
+ disabled={screenshotLoading}
74
+ title="Screenshot"
75
+ style={{
76
+ marginLeft: 'auto',
77
+ padding: '6px 10px',
78
+ border: '1px solid var(--border)',
79
+ background: 'transparent',
80
+ color: 'var(--text-dim)',
81
+ cursor: 'pointer',
82
+ display: 'flex',
83
+ alignItems: 'center',
84
+ }}
85
+ >
86
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
87
+ <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
88
+ <circle cx="12" cy="13" r="4"/>
89
+ </svg>
90
+ </button>
91
+ </div>
92
+ )}
93
+
94
+ <section className="layout" style={isMobile ? { display: 'block', height: 'calc(100vh - 150px)' } : undefined}>
95
+ {(!isMobile || mobileTab === 'graph') && (
96
+ <article className="panel graph-panel" style={isMobile ? { height: '100%', borderRight: 'none', padding: 16 } : { padding: 16 }}>
97
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, gap: 12 }}>
98
+ <div style={{ color: 'var(--text-dim)', fontSize: 11 }}>
99
+ Lineage map of sessions, sub-agents and operational relationships.
100
+ </div>
101
+ <button
102
+ type="button"
103
+ className="agent-btn"
104
+ aria-label="Reimposta vista grafo"
105
+ onClick={() => topologyRef.current?.resetView()}
106
+ style={resetButtonStyle}
107
+ >
108
+ ⊙ Reimposta vista
109
+ </button>
110
+ </div>
111
+ {hasSessionsLoaded ? (
112
+ <SessionTopology
113
+ ref={topologyRef}
114
+ sessions={visibleSessions}
115
+ filter={filter.agent}
116
+ onNodeClick={selectSession}
117
+ emptyMessage={filter.showOnlyActive ? 'No active sessions in the selected period' : 'No sessions visible with current filters'}
118
+ />
119
+ ) : (
120
+ <div className="empty-state">
121
+ <span className="empty-state-icon">◎</span>
122
+ <span className="empty-state-msg">Loading lineage…</span>
123
+ </div>
124
+ )}
125
+ </article>
126
+ )}
127
+
128
+ {(!isMobile || mobileTab !== 'graph') && (
129
+ <aside className="side" style={isMobile ? { height: '100%' } : undefined}>
130
+ {(!isMobile || mobileTab === 'feed') && <LiveFeed events={hasSessionsLoaded ? visibleEvents : []} />}
131
+ {(!isMobile || mobileTab === 'costs') && <CostBreakdown byModel={costs.byModel} />}
132
+ </aside>
133
+ )}
134
+ </section>
135
+
136
+ <SessionDrawer
137
+ sessionId={selectedSessionId}
138
+ onClose={() => selectSession(null)}
139
+ />
140
+ </main>
141
+ );
142
+ }
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import type { SessionEvent } from '@/lib/types';
4
+ import { deriveSessionDisplayLabel } from '@/lib/patterns/sessionPresentation';
5
+ import { formatTimeFromUnixSeconds } from '@/lib/utils/format';
6
+ import { SkeletonLines } from './Skeleton';
7
+
8
+ interface LiveFeedProps {
9
+ events: SessionEvent[];
10
+ loading?: boolean;
11
+ }
12
+
13
+ export default function LiveFeed({ events, loading = false }: LiveFeedProps) {
14
+ return (
15
+ <article className="panel feed-panel">
16
+ <h2>Live Events</h2>
17
+ <div>
18
+ {loading ? (
19
+ <div style={{ padding: 12 }}>
20
+ <SkeletonLines count={5} />
21
+ </div>
22
+ ) : events.length === 0 ? (
23
+ <div className="empty-state">
24
+ <div className="empty-state-msg">Waiting for activity…</div>
25
+ </div>
26
+ ) : (
27
+ events.map((item, index) => (
28
+ <div
29
+ key={`${item.ts ?? 0}-${item.event ?? 'evt'}-${index}`}
30
+ className="feed-item"
31
+ >
32
+ <span className="feed-time">{formatTimeFromUnixSeconds(item.ts)}</span>
33
+ &nbsp;<span className="feed-type">{item.type ?? item.event ?? 'event'}</span>
34
+ <span className="feed-label">
35
+ {deriveSessionDisplayLabel({
36
+ session_id: item.session_id ?? '',
37
+ label: item.session_label ?? item.label ?? null,
38
+ lineage_label: null,
39
+ task_preview: null,
40
+ })}
41
+ </span>
42
+ </div>
43
+ ))
44
+ )}
45
+ </div>
46
+ </article>
47
+ );
48
+ }
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { usePathname } from 'next/navigation';
5
+
6
+ const navItems = [
7
+ {
8
+ label: 'Home',
9
+ path: '/',
10
+ icon: (
11
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
12
+ <path d="M3 9l6-7 6 7v6a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
13
+ <polyline points="7,17 7,10 11,10 11,17"/>
14
+ </svg>
15
+ ),
16
+ },
17
+ {
18
+ label: 'Agents',
19
+ path: '/agents',
20
+ icon: (
21
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
22
+ <path d="M9 2a2.5 2.5 0 100 5 2.5 2.5 0 000-5z"/>
23
+ <path d="M5 16v-1.5A3.5 3.5 0 018.5 11h1A3.5 3.5 0 0113 14.5V16"/>
24
+ <circle cx="5" cy="5" r="1" fill="currentColor" stroke="none"/><circle cx="13" cy="5" r="1" fill="currentColor" stroke="none"/>
25
+ </svg>
26
+ ),
27
+ },
28
+ {
29
+ label: 'Workspace',
30
+ path: '/workspace',
31
+ icon: (
32
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
33
+ <path d="M2.5 5a1.5 1.5 0 0 1 1.5-1.5h2.3l1.2 1.5H14a1.5 1.5 0 0 1 1.5 1.5v6A1.5 1.5 0 0 1 14 14H4a1.5 1.5 0 0 1-1.5-1.5V5z"/>
34
+ <path d="M2.5 6.5h13"/>
35
+ </svg>
36
+ ),
37
+ },
38
+ {
39
+ label: 'Gateway',
40
+ path: '/gateway',
41
+ icon: (
42
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
43
+ <path d="M2.5 5.5H7M11 5.5h4.5M4.75 5.5a2 2 0 1 0 0 .01ZM13.25 5.5a2 2 0 1 0 0 .01ZM2.5 12.5H6M8 12.5h7.5M7 12.5a2 2 0 1 0 0 .01Z"/>
44
+ </svg>
45
+ ),
46
+ },
47
+ {
48
+ label: 'Containers',
49
+ path: '/containers',
50
+ icon: (
51
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
52
+ <rect x="2" y="2" width="5.5" height="5.5" rx="1.5"/>
53
+ <rect x="10.5" y="2" width="5.5" height="5.5" rx="1.5"/>
54
+ <rect x="2" y="10.5" width="5.5" height="5.5" rx="1.5"/>
55
+ <rect x="10.5" y="10.5" width="5.5" height="5.5" rx="1.5"/>
56
+ </svg>
57
+ ),
58
+ },
59
+ ];
60
+
61
+ export default function MobileBottomNav() {
62
+ const pathname = usePathname();
63
+
64
+ return (
65
+ <nav className="mobile-bottom-nav">
66
+ <div className="mobile-bottom-nav__scroll">
67
+ {navItems.map((item) => {
68
+ const isActive = item.path === '/' ? pathname === '/' : pathname.startsWith(item.path);
69
+ return (
70
+ <Link
71
+ key={item.path}
72
+ href={item.path}
73
+ className={`mobile-bottom-nav__item ${isActive ? 'mobile-bottom-nav__item--active' : ''}`}
74
+ prefetch={true}
75
+ >
76
+ <span className="mobile-bottom-nav__icon">{item.icon}</span>
77
+ <span className="mobile-bottom-nav__label">{item.label}</span>
78
+ </Link>
79
+ );
80
+ })}
81
+ </div>
82
+ </nav>
83
+ );
84
+ }