@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,280 @@
1
+ # Container Terminal — WebSocket PTY
2
+
3
+ > **Status:** Active — `main` branch
4
+ > **Last updated:** 2026-06-30
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Rev4a provides an interactive web-based terminal for Docker containers.
11
+ It connects to any container with `AGENT_ID` label, spawning a real PTY session
12
+ via `node-pty` and streaming I/O over a dedicated WebSocket server.
13
+
14
+ ```
15
+ Browser (xterm-compatible DOM terminal)
16
+ │ WebSocket wss://rev4a/containers/terminal/{id}
17
+ ▼
18
+ terminal-ws-server.js (port 3741, proxied by Traefik)
19
+ │ node-pty
20
+ ▼
21
+ docker exec -it {containerId} env TERM=xterm-256color bash
22
+ │
23
+ ▼
24
+ Container Shell (bash, interactive, full PTY)
25
+ ```
26
+
27
+ ## Architecture
28
+
29
+ ### Two-process model
30
+
31
+ | Process | Port | Role |
32
+ |------------------|-------|------|
33
+ | `rev4a-next` | 3740 | Next.js app (pages, API routes, auth) |
34
+ | `rev4a-terminal-ws` | 3741 | Standalone WebSocket server for terminal sessions |
35
+
36
+ The terminal server is a separate systemd service (rev4a-terminal-ws). It was isolated from
37
+ the Next.js app to avoid event-loop contention during high-throughput I/O (fast
38
+ `seq`, `cat` on large files, interactive shell sessions).
39
+
40
+ ### Traefik routing
41
+
42
+ ```
43
+ /containers/terminal/{id} → Next.js (page serving TerminalClient)
44
+ /api/terminal-ws?id={containerId} → WebSocket proxy → ws://127.0.0.1:3741
45
+ ```
46
+
47
+ ### Data flow
48
+
49
+ 1. User opens `/containers/terminal/openclaw-atlas`
50
+ 2. `page.tsx` renders `<TerminalClient containerId="openclaw-atlas" />`
51
+ 3. `TerminalClient` opens a WebSocket to `/api/terminal-ws?id=openclaw-atlas`
52
+ 4. `terminal-ws-server.js` receives connection, spawns a PTY via node-pty:
53
+ ```
54
+ docker exec -it openclaw-atlas env TERM=xterm-256color bash
55
+ ```
56
+ 5. PTY output streams → WebSocket → browser (DOM terminal)
57
+ 6. User keystrokes → WebSocket → PTY stdin
58
+
59
+ ## Terminal Client (Browser)
60
+
61
+ ### Why not xterm.js?
62
+
63
+ The first implementation used **xterm.js** (v5.3.0) with both **canvas** and
64
+ **DOM** renderers. Both suffered from:
65
+
66
+ - **Black screen on large output** — the canvas renderer went blank when
67
+ hundreds of lines were received in a single burst. The DOM renderer
68
+ (xterm.addons.dom) had the same issue under load.
69
+ - **Broken scrolling** — xterm.js manages its own scrollback buffer internally,
70
+ but the canvas never grew, so native browser scrolling didn't exist. Mouse
71
+ wheel scrolling often failed or produced visual artifacts.
72
+ - **CSS conflicts** — the parent app sets `html, body { overflow: hidden }`,
73
+ which interfered with xterm.js viewport calculation. Multiple workarounds
74
+ (`position: fixed`, `minHeight: 0`, `height: 100dvh`) didn't fully resolve
75
+ layout instability.
76
+ - **Selection issues** — xterm.js intercepts mouse events for its own
77
+ selection system, making it hard to copy text natively.
78
+
79
+ ### Custom DOM terminal (current)
80
+
81
+ The current terminal replaces xterm.js entirely with a plain HTML structure:
82
+
83
+ ```
84
+ ┌─────────────────────────────────┐
85
+ │ Top bar (44px, container info) │
86
+ ├─────────────────────────────────┤
87
+ │ │
88
+ │ Output area (div, overflow:auto│
89
+ │ white-space: pre-wrap) │
90
+ │ │
91
+ │ root@hostname:~# ls -la │
92
+ │ total 12 │
93
+ │ drwxr-xr-x ... │
94
+ │ -rw-r--r-- ... │
95
+ │ │
96
+ │ root@hostname:~# █ │
97
+ ├─────────────────────────────────┤
98
+ │ (hidden <textarea> for input) │
99
+ └─────────────────────────────────┘
100
+ ```
101
+
102
+ Key design decisions:
103
+
104
+ | Decision | Rationale |
105
+ |---|---|
106
+ | **`<textarea>` hidden off-screen** | Captures all keystrokes including Tab, Arrows, Ctrl+letter. No focus/IME issues. Single-line only (no Enter for newlines). |
107
+ | **`<div>` output area** | Native browser scrolling (`overflow: auto`). Normal text selection (copy/paste with Cmd+C). Render millions of lines without black screen. |
108
+ | **`white-space: pre-wrap`** | Preserves ANSI-visible indentation and spacing while wrapping long lines. |
109
+ | **Last output line + input inline** | The cursor and text being typed appear on the same line as the last prompt from the server, simulating a real terminal feel. |
110
+ | **`requestAnimationFrame` auto-scroll** | After every buffer update, scrolls to bottom. Smooth, native scroll behavior. |
111
+
112
+ ### Input handling
113
+
114
+ - **Hidden `<textarea>`** captures all keyboard input
115
+ - `value` + `onChange` → React state `currentInput`
116
+ - On `Enter` → command sent to WebSocket, input cleared
117
+ - `Arrows` → local command history (client-side array)
118
+ - `Tab` → `\t` sent to server (bash autocomplete)
119
+ - `Ctrl+C/L/D/U` → respective control characters sent to server
120
+ - **Multi-line paste** → split by `\n`, each line sent as a separate command
121
+
122
+ ### Pending command flash fix
123
+
124
+ When the user presses Enter, `currentInput` is immediately cleared.
125
+ However, the server echoes the command back with a newline + new prompt.
126
+ Between the clear and the server echo, there is a visible frame where the
127
+ input line appears blank.
128
+
129
+ **Fix:** A `pendingCmdRef` ref holds the last submitted command string.
130
+ The rendering logic uses:
131
+
132
+ ```ts
133
+ const showInput = pendingCmdRef.current ? pendingCmdRef.current : currentInput;
134
+ ```
135
+
136
+ `pendingCmdRef` is reset as soon as any server output arrives (the echo),
137
+ so the command text stays visible without flicker.
138
+
139
+ ### ANSI parsing (color rendering)
140
+
141
+ The terminal now includes a real ANSI parser (`parseAnsi()`) that converts
142
+ SGR (Select Graphic Rendition) escape sequences into styled `<span>`
143
+ elements:
144
+
145
+ | Feature | Supported |
146
+ |---|---|
147
+ | Foreground colors 30-37 (normal) | ✅ |
148
+ | Foreground colors 90-97 (bright) | ✅ |
149
+ | Background colors 40-47 | ✅ |
150
+ | Background colors 100-107 (bright) | ✅ |
151
+ | Bold (1) | ✅ `fontWeight: 700` |
152
+ | Dim (2) | ✅ `opacity: 0.6` |
153
+ | Italic (3) | ✅ |
154
+ | Underline (4) | ✅ |
155
+ | Reset (0) | ✅ clears all styles |
156
+ | Bracketed paste `[?2004h/l` | 🚫 stripped |
157
+ | Non-SGR CSI (cursor, erase) | 🚫 stripped |
158
+ | OSC sequences (title, clipboard) | 🚫 stripped |
159
+
160
+ **Implementation:**
161
+
162
+ ```ts
163
+ type AnsiStyle = {
164
+ fg?: string; // CSS color
165
+ bg?: string; // CSS background-color
166
+ bold?: boolean;
167
+ dim?: boolean;
168
+ italic?: boolean;
169
+ underline?: boolean;
170
+ };
171
+ type Segment = { text: string; style: AnsiStyle };
172
+ ```
173
+
174
+ The parser walks the raw ANSI string character by character. When it
175
+ encounters `\x1b[`, it reads until `m` to get SGR parameters. Each
176
+ parameter (e.g. `31` for red foreground) is translated into a color
177
+ from the ANSI color map. All non-SGR escape sequences (cursor movement,
178
+ erase display, bracketed paste) are silently stripped.
179
+
180
+ The raw PTY output is kept in state as-is (with ANSI), split by `\n`,
181
+ and each line is passed through `RenderAnsi` which uses `useMemo` to
182
+ avoid re-parsing on every render.
183
+
184
+ **Color palette** follows the One Dark theme used by the UI:
185
+
186
+ | Code | Color | Hex |
187
+ |---|---|---|
188
+ | 30/90 (black) | Dark gray | `#1d1d1d` / `#5c6370` |
189
+ | 31/91 (red) | Soft red | `#e06c75` |
190
+ | 32/92 (green) | Soft green | `#98c379` |
191
+ | 33/93 (yellow) | Amber | `#d19a66` |
192
+ | 34/94 (blue) | Soft blue | `#61afef` |
193
+ | 35/95 (magenta) | Purple | `#c678dd` |
194
+ | 36/96 (cyan) | Teal | `#56b6c2` |
195
+ | 37/97 (white) | Light gray / white | `#abb2bf` / `#ffffff` |
196
+
197
+ ### Legacy: ANSI stripping (pre-color support)
198
+
199
+ The original implementation stripped all ANSI sequences, keeping only
200
+ visible text. This was replaced by the parser above on 2026-06-25.
201
+
202
+ ## Terminal Server (`terminal-ws-server.js`)
203
+
204
+ ### Dependencies
205
+
206
+ - **ws** (WebSocket server)
207
+ - **node-pty** (pseudo-terminal for child processes)
208
+
209
+ ### Session lifecycle
210
+
211
+ 1. WebSocket connection established with `?id=openclaw-atlas`
212
+ 2. `node-pty` spawns `docker exec -it {containerId} bash` with:
213
+ - `name: 'xterm-256color'`
214
+ - `cols: 80, rows: 30` (initial, resized on client fit)
215
+ 3. PTY `onData` → WebSocket `send`
216
+ 4. WebSocket `message` → PTY `write`
217
+ 5. Idle timeout: 30 minutes (reset on any client input)
218
+ 6. On disconnect or `exit` command → cleanup child process, close socket
219
+
220
+ ### Resize handling
221
+
222
+ The client sends a JSON message when the terminal dimensions change:
223
+
224
+ ```json
225
+ { "type": "resize", "cols": 120, "rows": 40 }
226
+ ```
227
+
228
+ The server calls `term.resize(cols, rows)` to update the PTY dimensions.
229
+ This ensures commands like `top`, `less`, `vim` use the correct viewport.
230
+
231
+ ### Chunked output
232
+
233
+ PTY output is buffered and flushed every 10ms to avoid overwhelming the
234
+ browser's DOM renderer with a single giant chunk. If the buffer exceeds
235
+ 2000 characters, it is flushed immediately in sub-chunks.
236
+
237
+ This was added because `seq 1 10000` or `cat` on a large file would
238
+ send 100KB+ in one frame, causing the browser to freeze.
239
+
240
+ ### Why node-pty over `spawn('script')` or `unbuffer`?
241
+
242
+ Earlier attempts used:
243
+
244
+ - `spawn('docker exec -i ...')` — no PTY, commands had no echo, no history,
245
+ no tab completion
246
+ - `spawn('script', ['-q', '-c', 'docker exec -i ...'])` — created a PTY but
247
+ output was fully buffered and never appeared until the process exited
248
+ - `spawn('unbuffer', ['-p', 'docker exec -i ...'])` — same buffering issue
249
+
250
+ `node-pty` is the only reliable way to create a real PTY and get
251
+ line-buffered or character-buffered I/O in real-time.
252
+
253
+ ## File Structure
254
+
255
+ ```
256
+ app/containers/terminal/
257
+ ├── [id]/
258
+ │ ├── page.tsx # Next.js page (auth-protected)
259
+ │ └── TerminalClient.tsx # Client-side terminal component
260
+ terminal-ws-server.js # WebSocket PTY server (rev4a-terminal-ws.service)
261
+ rev4a.service # Systemd target for all three services
262
+ ```
263
+
264
+ ## Known Limitations
265
+
266
+ - **No full-screen TUI support** — `vim`, `top`, `nano`, `htop` will
267
+ not render correctly because cursor positioning sequences are stripped.
268
+ These tools require a proper xterm-compatible terminal.
269
+ - **Single session per connection** — no multiplexing, no tabs.
270
+ Each WebSocket connection spawns one PTY.
271
+ - **No WebGL renderer** — DOM rendering is CPU-bound for very fast output.
272
+ In practice, up to ~500 lines/sec is smooth.
273
+
274
+ ## Future Improvements
275
+
276
+ - [ ] Detect TUI applications and fall back to xterm.js WebGL renderer
277
+ - [ ] Terminal multiplexer (multiple tabs, split panes)
278
+ - [ ] Download session log as text file
279
+ - [ ] Paste confirmation dialog for multi-line pastes
280
+ - [ ] Dark/light theme toggle
@@ -0,0 +1,99 @@
1
+ # Rev4a Design System
2
+
3
+ Source of truth: `app/design-system.ts` + CSS tokens in `app/globals.css`.
4
+
5
+ > **Last updated:** 2026-07-03
6
+
7
+ ---
8
+
9
+ ## Responsive breakpoints
10
+
11
+ Centralized in `app/design-system.ts` as `Breakpoint` object and exposed via the `useResponsive(key)` hook.
12
+
13
+ | Token | Value | Used by |
14
+ |-------|-------|---------|
15
+ | `Breakpoint.sm` | 576px | Mobile bottom nav centering |
16
+ | `Breakpoint.md` | 768px | Dashboard, Lineage, SessionDrawer, Providers |
17
+ | `Breakpoint.lg` | 992px | Agents (dense/fold devices), Crons, Plugins/Skills |
18
+ | `Breakpoint.xl` | 1200px | Reserved |
19
+ | `Breakpoint.xxl` | 1400px | Reserved |
20
+
21
+ ### CSS tokens (globals.css)
22
+
23
+ ```css
24
+ --bp-sm: 576px;
25
+ --bp-md: 768px;
26
+ --bp-lg: 992px;
27
+ --bp-xl: 1200px;
28
+ --bp-xxl: 1400px;
29
+ ```
30
+
31
+ Use the CSS custom property in `@media` queries, not raw pixel values.
32
+
33
+ ## Visual language
34
+
35
+ ### Shape
36
+
37
+ Rev4a uses a **square/sharp** visual language:
38
+ - Buttons, inputs, modals, cards — **0 border-radius by default** (no rounding).
39
+ - `Pill` uses inline rounded shapes where appropriate (status badges).
40
+ - No shadows, no gradients (except the login page background).
41
+
42
+ ### Color tokens
43
+
44
+ Defined in `app/globals.css` as CSS custom properties:
45
+
46
+ ```css
47
+ --bg: #0a0a0a /* Page background */
48
+ --bg2: #111 /* Slightly lighter surface */
49
+ --border: #222 /* Default border */
50
+ --text: #e0e0e0 /* Primary text */
51
+ --text-dim: #888 /* Muted/secondary text */
52
+ --violet: #a78bfa /* Brand accent */
53
+ --violet-bg: #1a1030 /* Violet-tinted hover/active background */
54
+ --green: #22c55e /* Success */
55
+ --red: #ef4444 /* Error */
56
+ --yellow: #eab308 /* Warning */
57
+ --blue: #60a5fa /* Info / links */
58
+ ```
59
+
60
+ ### Typography
61
+
62
+ - Monospace by default: `var(--font-mono-stack)`
63
+ - Serif (logotype): `var(--font-serif-stack)`
64
+ - UI buttons use `monospace` via CSS class or inline `fontFamily`
65
+ - No variable fonts — system monospace stack for consistency
66
+
67
+ ## Component design rules
68
+
69
+ 1. **Border radius: 0 everywhere.** `border-radius: 0` or sharp corners. Exceptions: `Pill`, avatar circles, status dots.
70
+ 2. **No shadows.** Flat design. Use `border: 1px solid var(--border)` to define surfaces.
71
+ 3. **Violet is the only accent.** `var(--violet)` for active states, primary actions, headings. `var(--violet-bg)` for hover/active backgrounds.
72
+ 4. **Mono font on buttons and inputs.** All interactive text uses `var(--font-mono-stack)`.
73
+ 5. **CSS vars over hardcoded colors.** Never inline `#22c55e`, `#ef4444`, `#888` — use the CSS token.
74
+ 6. **Loading feedback.** Use `loading` prop on `Button` (shows inline spinner). Do not render separate loader elements.
75
+ 7. **Minimal animation.** Transitions on hover/active (0.1s–0.12s), spin on loading spinner. No bounce, shake, or decorative animations.
76
+
77
+ ### React hook
78
+
79
+ ```ts
80
+ import { useResponsive, Breakpoint } from '../design-system';
81
+
82
+ const isNarrow = useResponsive('md'); // true when ≤767px
83
+ const isFoldable = useResponsive('lg'); // true when ≤991px
84
+ ```
85
+
86
+ The hook uses `matchMedia` (passive, zero-CPU) and returns `boolean`.
87
+
88
+ ---
89
+
90
+ ## Current page behaviors
91
+
92
+ | Page | Breakpoint | Behaviour when active |
93
+ |------|-----------|----------------------|
94
+ | Agents | `lg` (992px) | Single-column stacked: AGENTS → FILES+CONFIG → EDITOR |
95
+ | Crons | `lg` (992px) | Tab navigation: jobs / runs |
96
+ | Plugins/Skills | `lg` (992px) | Sidebar → Detail step |
97
+ | Dashboard | `md` (768px) | Mobile header nav |
98
+ | Lineage | `md` (768px) | Mobile tab: graph / feed |
99
+ | SessionDrawer | `md` (768px) | Full-width drawer / compact |
@@ -0,0 +1,119 @@
1
+ # Rev4a Frontend Architecture
2
+
3
+ > **Last updated:** 2026-07-19
4
+
5
+ ## Layering
6
+
7
+ Rev4a UI should follow a layered architecture:
8
+
9
+ 1. `app/*/page.tsx` — routing only.
10
+ 2. `app/components/*Page*.tsx` — page composition and data orchestration.
11
+ 3. `app/components/ui/*` — shared design-system primitives.
12
+ 4. `lib/patterns/*` — domain/data patterns.
13
+ 5. `lib/*` — infrastructure helpers and pure utilities.
14
+
15
+ Feature pages should not create new ad-hoc visual languages. Prefer `ui` primitives first.
16
+
17
+ ## UI primitives
18
+
19
+ All shared UI primitives live in `app/components/ui/` and are exported from `app/components/ui/index.ts`.
20
+
21
+ ### Complete catalog
22
+
23
+ | Component | File | Purpose |
24
+ |-----------|------|---------|
25
+ | `Button` | `Button.tsx` | Action button, 5 variants (`primary`, `secondary`, `danger`, `ghost`, `success`), 2 sizes (`sm`, `md`), loading spinner. |
26
+ | `Input` | `Input.tsx` | Text input with label, error state, placeholder. |
27
+ | `Select` | `Select.tsx` | Native select with typed options, label, error state. |
28
+ | `Modal` | `Modal.tsx` | Overlay modal, Escape-to-close, maxWidth prop, `type="button"` on close. |
29
+ | `Toast` | `Toast.tsx` | Lightweight toast notification with auto-dismiss (4s), `success` / `error` variants. |
30
+ | `LoadingSpinner` | `LoadingSpinner.tsx` | Inline or fullscreen spinner. |
31
+ | `Tabs` | `Tabs.tsx` | Tab navigation bar with optional count badges. |
32
+ | `ItemList` / `ListContainer` / `ListItem` / `ListItemSeparator` | `ItemList.tsx` | List primitives for sidebar/panel item lists. |
33
+ | `FilterBar` | `FilterBar.tsx` | Horizontal filter button group. |
34
+ | `PropertyList` | `PropertyList.tsx` | 2-column grid for label/value pairs. Children rendered as `<Fragment>` — do not wrap in extra `<div>`. |
35
+ | `TemplateOption` | `TemplateOption.tsx` | Agent template selection card with avatar, description, optional badge. Centralises create-agent card styling. |
36
+ | `Pill` | `Pill.tsx` | Small status/attribute label. Variants: `default`, `accent`. |
37
+ | `Metric` | `Metric.tsx` | Metric card with title, value, subtitle, tone. |
38
+ | `StatusCard` | `StatusCard.tsx` | Health/status report card. |
39
+ | `Surface` | `Surface.tsx` | Shared panel/card surface, variant prop. |
40
+ | `Page` / `PageHeader` | `Page.tsx` | Full-page layout shell. |
41
+ | `Icons` | `Icons.tsx` | SVG icons (`EyeIcon`, `EyeOffIcon`), 16/20px shared. |
42
+ | `PasswordInput` | `PasswordInput.tsx` | Password input with inline show/hide toggle (`<button type="button">` with `aria-label`). |
43
+ | `OnboardingPageClient` + step components | `app/onboarding/PageClient.tsx` | 4-step wizard (`WelcomeStep`, `ProvidersStep`, `AgentsStep`, `DoneStep`) + `WizardFrame` shell with mobile-first CSS. |
44
+ | `useOnboarding` | `app/onboarding/useOnboarding.ts` | Shared hook: onboarding state, step transitions, provider save, restart. Receives server-side initial state to avoid loading flash. |
45
+ | `OnboardingIcons` | `app/onboarding/icons.tsx` | Shared SVG icons (flyweight pattern): `ArrowRightIcon`, `CheckIcon`, `DockerIcon`, `GatewayIcon`, etc. |
46
+ | `tokens` | `tokens.ts` | TypeScript types for `Tone` and related token values. |
47
+ | `Badge` | `Badge.tsx` | Inline status tag with tone variants (success, danger, warning, neutral). Used for channel chips, pairing labels, error/success messages. |
48
+
49
+ ### Rules
50
+
51
+ 1. **Prefer existing primitives over new ad-hoc markup.** Every new component starts from the shared catalog.
52
+ 2. **No inline styles on interactive elements.** Buttons, inputs, selects use the `app/components/ui/*` component with component props. Only layout/wrapping containers use inline style.
53
+ 3. **No Italian in code, labels, or comments.** UI strings, error messages, aria-labels — all English.
54
+ 4. **Clickable/custom interactive elements must be `<button type="button">`.** Not `<span onClick>`, not `<div onClick>`. Always include `aria-label` for icon-only buttons.
55
+ 5. **Modals inside forms** — close button has `type="button"` to prevent accidental form submission.
56
+ 6. **Loading buttons** — set `loading={true}` on `Button`, do not render separate loaders next to the button. The spinner is built-in.
57
+ 7. **New UI component** — add it to `app/components/ui/`, export from `index.ts`, document it here. If it's specific to one page, keep it page-local unless another page needs it.
58
+
59
+ ## GoF pattern mapping
60
+
61
+ Already used:
62
+
63
+ - **Observer + Singleton**: `EventBus` owns one SSE stream and fan-outs updates.
64
+ - **Factory**: `SessionFactory` / `EventFactory` normalize raw runtime data.
65
+ - **Adapter**: `ApiAdapter` converts raw API payloads to domain types.
66
+ - **Strategy + Composite**: `FilterStrategy` composes dashboard filtering.
67
+
68
+ Added UI-side discipline:
69
+
70
+ - **Factory-ish tone mapping**: `toneFromHealth()` maps domain health into UI tone.
71
+ - **Strategy-ish variants**: `Surface` variant/tone classes choose presentation without inline restyling.
72
+ - **Composition**: page-specific components compose primitives instead of duplicating card markup.
73
+
74
+ ## Responsive system
75
+
76
+ Rev4a uses Bootstrap v5 breakpoint categories as project-wide tokens, declared in `app/globals.css`:
77
+
78
+ - `--bp-sm: 576px`
79
+ - `--bp-md: 768px`
80
+ - `--bp-lg: 992px`
81
+ - `--bp-xl: 1200px`
82
+ - `--bp-xxl: 1400px`
83
+
84
+ Rules:
85
+
86
+ 1. Prefer breakpoint tokens over raw pixel values.
87
+ 2. New responsive behavior should map to Bootstrap categories (`sm`, `md`, `lg`, ...), not ad-hoc thresholds.
88
+ 3. When an exception is needed, express it relative to a token and document why.
89
+
90
+ ## Current responsive behaviors
91
+
92
+ - **Agents page**
93
+ - below `lg` (`< 992px`) switches to stacked/mobile mode
94
+ - shows one section at a time (`AGENTS` → `FILES + CONFIG` → `EDITOR`)
95
+ - avoids split-column clipping on narrow, fold, and small-tablet devices
96
+ - **Mobile bottom nav**
97
+ - active below `md`
98
+ - item row centers from `sm` upward within the mobile range
99
+ - **PDF preview in Agents**
100
+ - rendered in-page for mobile browsers instead of relying on the native iframe PDF viewer
101
+
102
+ ## Request orchestration rule
103
+
104
+ For interactive pages that can change view/file/tab quickly:
105
+
106
+ 1. Abort obsolete fetches with `AbortController`.
107
+ 2. Guard against stale updates after `await` boundaries.
108
+ 3. On navigation/context switch, kill in-flight requests before starting new ones.
109
+ 4. Mobile layout changes must not wait on network completion.
110
+
111
+ ## Migration rule
112
+
113
+ For every new/changed page:
114
+
115
+ 1. No new one-off card styles unless justified.
116
+ 2. Use `app/components/ui/*` primitives first — `Button`, `Input`, `Select`, `Modal`, `Toast`, `Surface`, `Metric`, `StatusCard`, `Pill`, `Page`, `Tabs`, `ItemList`, `FilterBar`, `PropertyList`, `TemplateOption`, `LoadingSpinner`.
117
+ 3. Async data must render skeleton, not fake zero values.
118
+ 4. Business logic stays in `lib`/API routes; UI consumes typed payloads.
119
+ 5. If a pattern is introduced, name it and keep it in `lib/patterns` or `app/components/ui`.