@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.
- package/README.md +150 -0
- package/agent-templates/README.md +37 -0
- package/agent-templates/argus/AGENTS.md +52 -0
- package/agent-templates/argus/IDENTITY.md +7 -0
- package/agent-templates/argus/MEMORY.md +9 -0
- package/agent-templates/argus/SOUL.md +24 -0
- package/agent-templates/argus/TOOLS.md +8 -0
- package/agent-templates/atlas/AGENTS.md +47 -0
- package/agent-templates/atlas/HEARTBEAT.md +10 -0
- package/agent-templates/atlas/IDENTITY.md +6 -0
- package/agent-templates/atlas/MEMORY.md +9 -0
- package/agent-templates/atlas/SOUL.md +21 -0
- package/agent-templates/atlas/TOOLS.md +4 -0
- package/agent-templates/base-image/.trigger +0 -0
- package/agent-templates/base-image/Dockerfile +90 -0
- package/agent-templates/base-image/entrypoint.sh +37 -0
- package/agent-templates/custom/AGENTS.md +29 -0
- package/agent-templates/custom/BOOTSTRAP.md +56 -0
- package/agent-templates/custom/MEMORY.md +8 -0
- package/agent-templates/prometheus/AGENTS.md +37 -0
- package/agent-templates/prometheus/IDENTITY.md +6 -0
- package/agent-templates/prometheus/MEMORY.md +9 -0
- package/agent-templates/prometheus/SOUL.md +21 -0
- package/agent-templates/prometheus/TOOLS.md +4 -0
- package/app/agents/ChannelManager.tsx +465 -0
- package/app/agents/ImageDownloadBanner.tsx +159 -0
- package/app/agents/PageClient.tsx +1285 -0
- package/app/agents/create/PageClient.tsx +437 -0
- package/app/agents/create/page.tsx +126 -0
- package/app/agents/loading.tsx +14 -0
- package/app/agents/page.tsx +5 -0
- package/app/api/agents/[id]/backup/route.ts +169 -0
- package/app/api/agents/[id]/channels/pairing/route.ts +69 -0
- package/app/api/agents/[id]/channels/route.ts +14 -0
- package/app/api/agents/[id]/channels/telegram/route.ts +50 -0
- package/app/api/agents/[id]/lifecycle/route.ts +94 -0
- package/app/api/agents/[id]/recreate/route.ts +199 -0
- package/app/api/agents/[id]/restart/route.ts +33 -0
- package/app/api/agents/[id]/restore/route.ts +81 -0
- package/app/api/agents/[id]/route.ts +280 -0
- package/app/api/agents/channels-summary/route.ts +39 -0
- package/app/api/agents/config-json/route.ts +58 -0
- package/app/api/agents/create/route.ts +488 -0
- package/app/api/agents/download-image/route.ts +32 -0
- package/app/api/agents/image-status/route.ts +126 -0
- package/app/api/agents/route.ts +213 -0
- package/app/api/agents/token/route.ts +94 -0
- package/app/api/agents-active/route.ts +190 -0
- package/app/api/agents-config/route.ts +629 -0
- package/app/api/alerts/smoke/route.ts +26 -0
- package/app/api/aliases/add/route.ts +28 -0
- package/app/api/aliases/remove/route.ts +27 -0
- package/app/api/assistant/route.ts +204 -0
- package/app/api/auth/check/route.ts +38 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/logout/route.ts +7 -0
- package/app/api/auth/status/route.ts +33 -0
- package/app/api/config/env/route.ts +132 -0
- package/app/api/config/restart/route.ts +26 -0
- package/app/api/containers/route.ts +75 -0
- package/app/api/cost-override/route.ts +46 -0
- package/app/api/costs/route.ts +84 -0
- package/app/api/credentials/[id]/reveal/route.ts +21 -0
- package/app/api/credentials/[id]/route.ts +52 -0
- package/app/api/credentials/[id]/sync/route.ts +58 -0
- package/app/api/credentials/detect/route.ts +246 -0
- package/app/api/credentials/route.ts +33 -0
- package/app/api/crons/[id]/route.ts +69 -0
- package/app/api/crons/route.ts +12 -0
- package/app/api/crons/runs/route.ts +31 -0
- package/app/api/debug/route.ts +11 -0
- package/app/api/envcheck/route.ts +18 -0
- package/app/api/events/route.ts +24 -0
- package/app/api/gateway/agent/route.ts +154 -0
- package/app/api/gateway/provider/balance/route.ts +38 -0
- package/app/api/gateway/provider/keys.ts +38 -0
- package/app/api/gateway/provider/oauth/route.ts +304 -0
- package/app/api/gateway/provider/route.ts +209 -0
- package/app/api/gateway/route.ts +223 -0
- package/app/api/gateway/sync.ts +189 -0
- package/app/api/lineage/route.ts +54 -0
- package/app/api/memory-context/route.ts +15 -0
- package/app/api/metrics/route.ts +35 -0
- package/app/api/models/route.ts +77 -0
- package/app/api/plugins/route.js +37 -0
- package/app/api/provider/upstream.ts +142 -0
- package/app/api/provider/v1/chat/completions/route.ts +146 -0
- package/app/api/provider/v1/models/route.ts +167 -0
- package/app/api/provider-usage/route.ts +629 -0
- package/app/api/session/route.ts +39 -0
- package/app/api/sessions/[id]/route.ts +38 -0
- package/app/api/sessions/route.ts +23 -0
- package/app/api/setup/agent-image/route.ts +102 -0
- package/app/api/setup/password/route.ts +111 -0
- package/app/api/setup/restart/route.ts +76 -0
- package/app/api/skills/delete/route.js +105 -0
- package/app/api/skills/promote/route.js +129 -0
- package/app/api/skills/route.js +203 -0
- package/app/api/skills/save/route.js +142 -0
- package/app/api/stats/route.ts +34 -0
- package/app/api/stats-since/route.ts +20 -0
- package/app/api/stream/route.ts +70 -0
- package/app/api/system-health/route.ts +233 -0
- package/app/api/tool-calls/route.ts +24 -0
- package/app/api/tools-config/route.ts +163 -0
- package/app/api/update-check/route.ts +127 -0
- package/app/api/version/route.ts +25 -0
- package/app/api/wizard/complete/route.ts +32 -0
- package/app/api/wizard/reset/route.ts +28 -0
- package/app/api/wizard/status/route.ts +28 -0
- package/app/api/workspace/route.ts +392 -0
- package/app/api/workspace/stream/route.ts +136 -0
- package/app/components/AppShell.tsx +33 -0
- package/app/components/AuthGuard.tsx +91 -0
- package/app/components/CostBreakdown.tsx +37 -0
- package/app/components/DashboardHeader.tsx +73 -0
- package/app/components/DashboardLayout.tsx +76 -0
- package/app/components/DashboardToolbar.tsx +61 -0
- package/app/components/FloatingActions.tsx +33 -0
- package/app/components/Icons.tsx +78 -0
- package/app/components/LineageGraphPage.tsx +142 -0
- package/app/components/LiveFeed.tsx +48 -0
- package/app/components/MobileBottomNav.tsx +84 -0
- package/app/components/ModelPickerModal.tsx +94 -0
- package/app/components/PasswordInput.tsx +86 -0
- package/app/components/PulseChat.tsx +362 -0
- package/app/components/Rev4aLoader.tsx +48 -0
- package/app/components/SessionDrawer.tsx +263 -0
- package/app/components/SessionTopology.tsx +289 -0
- package/app/components/Sidebar.tsx +294 -0
- package/app/components/Skeleton.tsx +133 -0
- package/app/components/SystemCockpit.tsx +169 -0
- package/app/components/VersionBanner.tsx +105 -0
- package/app/components/WizardButton.tsx +30 -0
- package/app/components/ui/Badge.tsx +30 -0
- package/app/components/ui/Button.tsx +59 -0
- package/app/components/ui/ConfirmModal.tsx +46 -0
- package/app/components/ui/FilterBar.tsx +33 -0
- package/app/components/ui/Input.tsx +25 -0
- package/app/components/ui/ItemList.tsx +83 -0
- package/app/components/ui/LoadingSpinner.tsx +30 -0
- package/app/components/ui/Metric.tsx +22 -0
- package/app/components/ui/Modal.tsx +42 -0
- package/app/components/ui/Page.tsx +18 -0
- package/app/components/ui/Pill.tsx +6 -0
- package/app/components/ui/PropertyList.tsx +24 -0
- package/app/components/ui/Select.tsx +26 -0
- package/app/components/ui/StatusCard.tsx +32 -0
- package/app/components/ui/Surface.tsx +14 -0
- package/app/components/ui/Tabs.tsx +55 -0
- package/app/components/ui/TemplateOption.tsx +59 -0
- package/app/components/ui/Toast.tsx +44 -0
- package/app/components/ui/index.ts +19 -0
- package/app/components/ui/tokens.ts +18 -0
- package/app/config/PageClient.tsx +293 -0
- package/app/config/loading.tsx +10 -0
- package/app/config/page.tsx +7 -0
- package/app/containers/ContainersClient.tsx +129 -0
- package/app/containers/loading.tsx +21 -0
- package/app/containers/page.tsx +5 -0
- package/app/containers/terminal/[id]/TerminalClient.tsx +313 -0
- package/app/containers/terminal/[id]/page.tsx +6 -0
- package/app/credentials/PageClient.tsx +598 -0
- package/app/credentials/loading.tsx +10 -0
- package/app/credentials/page.tsx +7 -0
- package/app/crons/PageClient.tsx +538 -0
- package/app/crons/loading.tsx +10 -0
- package/app/crons/page.tsx +5 -0
- package/app/design-system.ts +57 -0
- package/app/gateway/PageClient.tsx +1048 -0
- package/app/gateway/loading.tsx +24 -0
- package/app/gateway/page.tsx +5 -0
- package/app/globals.css +1474 -0
- package/app/layout.tsx +45 -0
- package/app/lib/models-context.tsx +41 -0
- package/app/lineage/loading.tsx +10 -0
- package/app/lineage/page.tsx +13 -0
- package/app/loading.tsx +16 -0
- package/app/login/page.tsx +160 -0
- package/app/memory/MemoryContextPageClient.tsx +196 -0
- package/app/memory/loading.tsx +10 -0
- package/app/memory/page.tsx +8 -0
- package/app/page.tsx +31 -0
- package/app/plugins/PageClient.tsx +296 -0
- package/app/plugins/loading.tsx +10 -0
- package/app/plugins/page.tsx +5 -0
- package/app/setup/PageClient.tsx +227 -0
- package/app/setup/loading.tsx +21 -0
- package/app/setup/page.tsx +7 -0
- package/app/skills/PageClient.tsx +499 -0
- package/app/skills/loading.tsx +10 -0
- package/app/skills/page.tsx +5 -0
- package/app/tools/ToolsPageClient.tsx +308 -0
- package/app/tools/loading.tsx +10 -0
- package/app/tools/page.tsx +62 -0
- package/app/tools/tools-catalog.ts +141 -0
- package/app/wizard/PageClient.tsx +485 -0
- package/app/wizard/icons.tsx +89 -0
- package/app/wizard/loading.tsx +24 -0
- package/app/wizard/page.tsx +7 -0
- package/app/wizard/storage.ts +33 -0
- package/app/wizard/useWizard.ts +101 -0
- package/app/workspace/WorkspaceClient.tsx +633 -0
- package/app/workspace/loading.tsx +36 -0
- package/app/workspace/page.tsx +8 -0
- package/bin/docker-entrypoint.sh +21 -0
- package/bin/postinstall.js +20 -0
- package/bin/rev4a.js +696 -0
- package/daemon.js +783 -0
- package/docs/ARCHITECTURE.md +557 -0
- package/docs/CONTAINER-TERMINAL.md +280 -0
- package/docs/DESIGN-SYSTEM.md +99 -0
- package/docs/FRONTEND-ARCHITECTURE.md +119 -0
- package/docs/REV4A.md +504 -0
- package/docs/dev/API-REFERENCE.md +1835 -0
- package/docs/dev/DATABASE.md +224 -0
- package/docs/dev/GATEWAY.md +300 -0
- package/docs/dev/PROVIDERS.md +229 -0
- package/docs/dev/SESSION-MAINTENANCE-PLAN.md +607 -0
- package/docs/dev/WORKSPACE.md +199 -0
- package/docs/rag/DATA-FRESHNESS.md +109 -0
- package/docs/rag/GLOSSARY.md +125 -0
- package/docs/rag/REV4A-OVERVIEW.md +135 -0
- package/docs/rag/WHAT-I-CAN-ANSWER.md +124 -0
- package/lib/agent-setup.ts +130 -0
- package/lib/alerts.ts +115 -0
- package/lib/apiFetch.ts +7 -0
- package/lib/auth.ts +28 -0
- package/lib/billing.ts +100 -0
- package/lib/buildAgentImage.ts +346 -0
- package/lib/channelManager.ts +386 -0
- package/lib/container.ts +60 -0
- package/lib/credentials/db.ts +86 -0
- package/lib/credentials/delivery.ts +448 -0
- package/lib/credentials/detect.ts +149 -0
- package/lib/credentials/hash.ts +9 -0
- package/lib/credentials/providers.ts +110 -0
- package/lib/credentials/vault.ts +195 -0
- package/lib/db-bootstrap.d.ts +3 -0
- package/lib/db-bootstrap.mjs +144 -0
- package/lib/db.ts +25 -0
- package/lib/docker-utils.ts +60 -0
- package/lib/hooks/useDashboard.ts +264 -0
- package/lib/hooks/useRev4aTimezone.ts +28 -0
- package/lib/hooks/useScreenshot.ts +30 -0
- package/lib/memory-context.ts +385 -0
- package/lib/model-catalogue.ts +131 -0
- package/lib/model-pricing.ts +63 -0
- package/lib/openclaw-cron.ts +350 -0
- package/lib/patterns/ApiAdapter.ts +73 -0
- package/lib/patterns/ApiClient.ts +65 -0
- package/lib/patterns/EventBus.ts +113 -0
- package/lib/patterns/FilterStrategy.ts +79 -0
- package/lib/patterns/SessionFactory.ts +117 -0
- package/lib/patterns/sessionPresentation.ts +117 -0
- package/lib/provider-balance.ts +163 -0
- package/lib/requireAuth.tsx +38 -0
- package/lib/rev4a-auth.d.ts +3 -0
- package/lib/rev4a-auth.js +36 -0
- package/lib/rev4a-paths.ts +145 -0
- package/lib/timezone.ts +46 -0
- package/lib/types/index.ts +96 -0
- package/lib/utils/format.ts +82 -0
- package/lineage.js +56 -0
- package/model-pricing.json +482 -0
- package/models.config.json +844 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +7 -0
- package/package.json +82 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-64.png +0 -0
- package/public/favicon.svg +58 -0
- package/public/icon-192-maskable.png +0 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512-maskable.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.json +38 -0
- package/public/rev4a-logo.png +0 -0
- package/public/rev4a-logo.svg +58 -0
- package/rev4a-rules/AGENTS.md +37 -0
- package/scripts/add-auth.cjs +185 -0
- package/scripts/backup.sh +66 -0
- package/scripts/check-providers.mjs +143 -0
- package/scripts/docker/traefik/docker-compose.yml +32 -0
- package/scripts/docker/traefik/dynamic/.gitkeep +18 -0
- package/scripts/docker/traefik/traefik.yml +37 -0
- package/scripts/remove-auth-profile.js +56 -0
- package/scripts/restore.sh +82 -0
- package/terminal-ws-server.js +139 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# What PULSE Can Answer
|
|
2
|
+
|
|
3
|
+
> **Last updated:** 2026-07-18
|
|
4
|
+
|
|
5
|
+
PULSE is the in-dashboard AI concierge for Rev4a. This document defines what she can and cannot answer.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Navigation Questions
|
|
10
|
+
|
|
11
|
+
- "Where do I find the agents page?"
|
|
12
|
+
- "How do I get to the container list?"
|
|
13
|
+
- "Where can I see the onboarding wizard?"
|
|
14
|
+
- "Is there a page for cron jobs?"
|
|
15
|
+
- "Where can I see my AI providers?" — The Gateway page (`/gateway`) shows providers, models, agent assignments.
|
|
16
|
+
- "How do I create a new agent?"
|
|
17
|
+
- "What templates are available?"
|
|
18
|
+
- "What is the Custom template?"
|
|
19
|
+
- "How does the custom agent bootstrap work?"
|
|
20
|
+
- "Where is the lineage graph?"
|
|
21
|
+
- "How do I open the file explorer?"
|
|
22
|
+
- "Which page shows costs?"
|
|
23
|
+
- "Where do I manage credentials (GitHub, Trello, etc.)?"
|
|
24
|
+
- "How do I connect an agent to Telegram?"
|
|
25
|
+
- "How do I approve a Telegram pairing?"
|
|
26
|
+
- "What does the TG badge mean?"
|
|
27
|
+
- "How do I get to the gateway?"
|
|
28
|
+
- "Where is the terminal for a container?"
|
|
29
|
+
- "Where can I edit memory files?"
|
|
30
|
+
- "How do I see the plugins?"
|
|
31
|
+
- "Where can I check system health?"
|
|
32
|
+
|
|
33
|
+
## How-To Questions
|
|
34
|
+
|
|
35
|
+
- "How do I create an agent?"
|
|
36
|
+
- "How do I set up Rev4a for the first time?"
|
|
37
|
+
- "How do I complete the onboarding wizard?"
|
|
38
|
+
- "How do I add an API key for a provider?"
|
|
39
|
+
- "How do I filter agents by status?"
|
|
40
|
+
- "How do I open a terminal for a container?"
|
|
41
|
+
- "How do I see the tool calls for a session?"
|
|
42
|
+
- "How do I configure a provider?"
|
|
43
|
+
- "How do I set up the gateway?"
|
|
44
|
+
- "How do I enable/disable models on the gateway?"
|
|
45
|
+
- "How do I check if my API key is working?"
|
|
46
|
+
- "How do I take a screenshot of the dashboard?"
|
|
47
|
+
- "How do I view memory files?"
|
|
48
|
+
- "How do I sync credentials to an agent?"
|
|
49
|
+
- "How do I back up an agent?"
|
|
50
|
+
- "How do I restore an agent from a backup?"
|
|
51
|
+
- "How do I recreate an agent with the latest image?"
|
|
52
|
+
- "How do I delete an agent?"
|
|
53
|
+
- "How do I restart the Rev4a server?"
|
|
54
|
+
- "How do I change environment variables?"
|
|
55
|
+
- "How do I add a cost override?"
|
|
56
|
+
- "How do I see the session lineage?"
|
|
57
|
+
- "How do I log in to an agent's control UI?"
|
|
58
|
+
- "How do I register a new provider via OAuth?"
|
|
59
|
+
- "How do I manage agent model fallbacks?"
|
|
60
|
+
- "How do I connect an agent to Telegram?"
|
|
61
|
+
- "How do I approve a pairing code?"
|
|
62
|
+
|
|
63
|
+
## Explanation Questions
|
|
64
|
+
|
|
65
|
+
- "What does the lineage graph show?"
|
|
66
|
+
- "What do the different statuses mean (idle, working, completed, error)?"
|
|
67
|
+
- "What is a gateway?"
|
|
68
|
+
- "What is the Provider Gateway?"
|
|
69
|
+
- "What is the difference between a plugin and a skill?"
|
|
70
|
+
- "What are MCP tools?"
|
|
71
|
+
- "What does 'cost override' mean?"
|
|
72
|
+
- "What is the vault?"
|
|
73
|
+
- "What are credentials in Rev4a?"
|
|
74
|
+
- "What is an agent template?"
|
|
75
|
+
- "What happens when I recreate an agent?"
|
|
76
|
+
- "How do agent backups work?"
|
|
77
|
+
- "What is an agent persistent volume?"
|
|
78
|
+
- "What is the onboarding wizard?"
|
|
79
|
+
- "What is the difference between Gateway and Providers pages?"
|
|
80
|
+
- "What is the Provider Gateway proxy?"
|
|
81
|
+
- "How does the SSE stream work?"
|
|
82
|
+
- "What is the shared gateway token?"
|
|
83
|
+
- "What are model fallbacks?"
|
|
84
|
+
- "What is the Channel Manager?"
|
|
85
|
+
- "What is pairing?"
|
|
86
|
+
- "What is Telegram pairing?"
|
|
87
|
+
- "What is the TG badge on the agent list?"
|
|
88
|
+
|
|
89
|
+
## Troubleshooting Questions
|
|
90
|
+
|
|
91
|
+
- "Why is my agent not showing up?"
|
|
92
|
+
- "Why are costs missing for some sessions?"
|
|
93
|
+
- "Why can't I connect to the container terminal?"
|
|
94
|
+
- "Why is the gateway sync not working?"
|
|
95
|
+
- "Why am I seeing an onboarding banner?"
|
|
96
|
+
- "How do I check if the daemon is running?"
|
|
97
|
+
- "Why are credentials not showing in my agent?"
|
|
98
|
+
- "Why is the agent base image banner showing?"
|
|
99
|
+
- "How do I fix 'image is outdated'?"
|
|
100
|
+
- "What happens if I abort a build?"
|
|
101
|
+
- "Can I close the build modal while it's running?"
|
|
102
|
+
- "How do agent image rebuilds work?"
|
|
103
|
+
- "Why is my Telegram bot not connecting?"
|
|
104
|
+
- "Why can't I approve a pairing code?"
|
|
105
|
+
- "Why is the TG badge missing from my agent?"
|
|
106
|
+
|
|
107
|
+
## What PULSE CANNOT do
|
|
108
|
+
|
|
109
|
+
- Access real-time data (session costs, live statuses, actual CPU/RAM). She only knows features and navigation.
|
|
110
|
+
- Execute actions (create agents, sync gateway, change config). She guides you to the right page.
|
|
111
|
+
- Read the contents of specific files on the server.
|
|
112
|
+
- See your API keys or credentials.
|
|
113
|
+
- Predict costs for future sessions.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## How PULSE uses context
|
|
118
|
+
|
|
119
|
+
When you ask a question, PULSE knows which page you're currently on (e.g. `/agents`, `/gateway`). She tailors her answer to the page you're looking at. If you're on the Agents page and ask "how do I create one?", she knows you mean an agent, not a cron job.
|
|
120
|
+
|
|
121
|
+
### Best results
|
|
122
|
+
1. **Open the relevant page first.** PULSE gives better guidance when you're on the Agents page asking about agents, or on the Gateway page asking about model routing.
|
|
123
|
+
2. **Be specific.** "How do I add a fallback model?" works better than "help me with models."
|
|
124
|
+
3. **Ask follow-ups.** PULSE remembers the conversation within the same chat session.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rev4a Agent Setup — Centralized volumes & config guarantees.
|
|
3
|
+
*
|
|
4
|
+
* Every Rev4a-managed agent gets a fixed set of bind mounts + a
|
|
5
|
+
* guaranteed set of OpenClaw config keys. This module is the single
|
|
6
|
+
* source of truth; both the "create" and "recreate" API routes import
|
|
7
|
+
* from here so that a future volume or mandatory config key is a
|
|
8
|
+
* one-file change.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
|
|
14
|
+
// ── Volumes ──────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export interface Rev4aVolume {
|
|
17
|
+
/** Absolute host path (e.g. /docker/shared-skills). */
|
|
18
|
+
source: string;
|
|
19
|
+
/** Absolute path inside the container. */
|
|
20
|
+
target: string;
|
|
21
|
+
/** Mount mode — "ro" for read-only, "rw" for read-write. */
|
|
22
|
+
mode: 'ro' | 'rw';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fixed volumes mounted into every Rev4a agent container.
|
|
27
|
+
* Append entries here to add a new shared volume across all agents.
|
|
28
|
+
*/
|
|
29
|
+
export const REV4A_VOLUMES: Rev4aVolume[] = [
|
|
30
|
+
{
|
|
31
|
+
source: '/docker/shared-skills',
|
|
32
|
+
target: '/data/.openclaw/shared-skills',
|
|
33
|
+
mode: 'ro',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
source: '/docker/rev4a-rules',
|
|
37
|
+
target: '/root/.openclaw/workspace/.rev4a',
|
|
38
|
+
mode: 'ro',
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// ── Mount flags ──────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Return an array of `-v source:target:mode` flags ready for `docker run`.
|
|
46
|
+
*/
|
|
47
|
+
export function getMountFlags(): string[] {
|
|
48
|
+
const flags: string[] = [];
|
|
49
|
+
for (const vol of REV4A_VOLUMES) {
|
|
50
|
+
flags.push('-v', `${vol.source}:${vol.target}:${vol.mode}`);
|
|
51
|
+
}
|
|
52
|
+
return flags;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Host directory bootstrap ─────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create every host-side source directory if it doesn't exist.
|
|
59
|
+
* Called once during `rev4a serve` preflight; safe to call
|
|
60
|
+
* multiple times.
|
|
61
|
+
*/
|
|
62
|
+
export function ensureHostDirs(): void {
|
|
63
|
+
for (const vol of REV4A_VOLUMES) {
|
|
64
|
+
if (!fs.existsSync(vol.source)) {
|
|
65
|
+
try {
|
|
66
|
+
execSync(`sudo mkdir -p ${escapeShell(vol.source)}`, { stdio: 'pipe' });
|
|
67
|
+
} catch {
|
|
68
|
+
execSync(`mkdir -p ${escapeShell(vol.source)}`, { stdio: 'pipe' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Runtime config guarantees ────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* JSON patch payloads that MUST be present in every agent's openclaw.json.
|
|
78
|
+
* Each entry is a deep-merge patch applied via `openclaw config patch --stdin`.
|
|
79
|
+
*
|
|
80
|
+
* Ordered intentionally: provider config (which can vary per host) goes last
|
|
81
|
+
* so it can reference earlier-merged keys if needed.
|
|
82
|
+
*/
|
|
83
|
+
const GUARANTEED_CONFIG_PATCHES: object[] = [
|
|
84
|
+
// Rev4a system rules hook — loads .rev4a/*.md into every session
|
|
85
|
+
{
|
|
86
|
+
hooks: {
|
|
87
|
+
internal: {
|
|
88
|
+
entries: {
|
|
89
|
+
'bootstrap-extra-files': {
|
|
90
|
+
enabled: true,
|
|
91
|
+
paths: ['.rev4a/*.md'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
// Extra skill directories (shared across all agents)
|
|
98
|
+
{
|
|
99
|
+
skills: {
|
|
100
|
+
load: {
|
|
101
|
+
extraDirs: ['/data/.openclaw/shared-skills'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Apply all guaranteed config patches to a running container.
|
|
109
|
+
* Uses `openclaw config patch --stdin` (deep-merge) so existing
|
|
110
|
+
* user customizations are never overwritten.
|
|
111
|
+
*/
|
|
112
|
+
export function applyRuntimeConfig(containerName: string): void {
|
|
113
|
+
for (const patch of GUARANTEED_CONFIG_PATCHES) {
|
|
114
|
+
const json = JSON.stringify(patch);
|
|
115
|
+
try {
|
|
116
|
+
execSync(
|
|
117
|
+
`docker exec -i ${escapeShell(containerName)} sh -c 'openclaw config patch --stdin'`,
|
|
118
|
+
{ timeout: 15_000, stdio: 'pipe', input: json },
|
|
119
|
+
);
|
|
120
|
+
} catch {
|
|
121
|
+
// config patch is best-effort — the image may already have it
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
export function escapeShell(value: string): string {
|
|
129
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
130
|
+
}
|
package/lib/alerts.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { DB_PATH, ensureDbReady } from './db';
|
|
3
|
+
|
|
4
|
+
export type AlertKind = 'db-freshness';
|
|
5
|
+
|
|
6
|
+
export interface AlertContext {
|
|
7
|
+
key: string;
|
|
8
|
+
kind: AlertKind;
|
|
9
|
+
title: string;
|
|
10
|
+
message: string;
|
|
11
|
+
resolvedMessage?: string;
|
|
12
|
+
stale: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ALERTS_TABLE = `
|
|
16
|
+
CREATE TABLE IF NOT EXISTS alert_state (
|
|
17
|
+
alert_key TEXT PRIMARY KEY,
|
|
18
|
+
kind TEXT NOT NULL,
|
|
19
|
+
state TEXT NOT NULL,
|
|
20
|
+
last_sent_at INTEGER,
|
|
21
|
+
last_resolved_at INTEGER,
|
|
22
|
+
updated_at INTEGER NOT NULL,
|
|
23
|
+
payload TEXT
|
|
24
|
+
);
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
function getConfig() {
|
|
28
|
+
return {
|
|
29
|
+
enabled: process.env.REV4A_ALERTS_ENABLED === 'true',
|
|
30
|
+
token: process.env.REV4A_TELEGRAM_BOT_TOKEN?.trim() ?? '',
|
|
31
|
+
chatId: process.env.REV4A_TELEGRAM_CHAT_ID?.trim() ?? '',
|
|
32
|
+
cooldownMs: Math.max(0, Number(process.env.REV4A_ALERT_COOLDOWN_MS ?? 10 * 60 * 1000)),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function openWritableDb() {
|
|
37
|
+
ensureDbReady();
|
|
38
|
+
const db = new Database(DB_PATH);
|
|
39
|
+
db.exec(ALERTS_TABLE);
|
|
40
|
+
return db;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function sendTelegramMessage(text: string): Promise<void> {
|
|
44
|
+
const { token, chatId } = getConfig();
|
|
45
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'content-type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ chat_id: chatId, text, disable_web_page_preview: true }),
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`Telegram send failed (${response.status})`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function maybeSendAlert(context: AlertContext): Promise<{ action: 'noop' | 'sent' | 'dry-run'; reason: string }> {
|
|
56
|
+
const config = getConfig();
|
|
57
|
+
if (!config.enabled) return { action: 'noop', reason: 'alerts disabled' };
|
|
58
|
+
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const db = openWritableDb();
|
|
61
|
+
try {
|
|
62
|
+
const existing = db.prepare('SELECT * FROM alert_state WHERE alert_key = ?').get(context.key) as { state?: string; last_sent_at?: number | null } | undefined;
|
|
63
|
+
const message = context.stale ? `⚠️ ${context.title}\n${context.message}` : `✅ ${context.title}\n${context.resolvedMessage ?? context.message}`;
|
|
64
|
+
|
|
65
|
+
if (!config.token || !config.chatId) {
|
|
66
|
+
console.info('[alerts] dry-run', { key: context.key, stale: context.stale, hasToken: Boolean(config.token), hasChatId: Boolean(config.chatId) });
|
|
67
|
+
if (context.stale) {
|
|
68
|
+
db.prepare(
|
|
69
|
+
`INSERT INTO alert_state (alert_key, kind, state, last_sent_at, updated_at, payload)
|
|
70
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
71
|
+
ON CONFLICT(alert_key) DO UPDATE SET state=excluded.state, last_sent_at=excluded.last_sent_at, updated_at=excluded.updated_at, payload=excluded.payload`,
|
|
72
|
+
).run(context.key, context.kind, 'alerted', now, now, JSON.stringify(context));
|
|
73
|
+
} else if (existing?.state === 'alerted') {
|
|
74
|
+
db.prepare(
|
|
75
|
+
`INSERT INTO alert_state (alert_key, kind, state, last_resolved_at, updated_at, payload)
|
|
76
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
77
|
+
ON CONFLICT(alert_key) DO UPDATE SET state=excluded.state, last_resolved_at=excluded.last_resolved_at, updated_at=excluded.updated_at, payload=excluded.payload`,
|
|
78
|
+
).run(context.key, context.kind, 'resolved', now, now, JSON.stringify(context));
|
|
79
|
+
}
|
|
80
|
+
return { action: 'dry-run', reason: 'missing telegram credentials' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (context.stale) {
|
|
84
|
+
const lastSentAt = Number(existing?.last_sent_at ?? 0);
|
|
85
|
+
if (lastSentAt && now - lastSentAt < config.cooldownMs) return { action: 'noop', reason: 'cooldown active' };
|
|
86
|
+
await sendTelegramMessage(message);
|
|
87
|
+
db.prepare(
|
|
88
|
+
`INSERT INTO alert_state (alert_key, kind, state, last_sent_at, updated_at, payload)
|
|
89
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
90
|
+
ON CONFLICT(alert_key) DO UPDATE SET state=excluded.state, last_sent_at=excluded.last_sent_at, updated_at=excluded.updated_at, payload=excluded.payload`,
|
|
91
|
+
).run(context.key, context.kind, 'alerted', now, now, JSON.stringify(context));
|
|
92
|
+
return { action: 'sent', reason: 'stale alert sent' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (existing?.state === 'alerted') {
|
|
96
|
+
await sendTelegramMessage(message);
|
|
97
|
+
db.prepare(
|
|
98
|
+
`INSERT INTO alert_state (alert_key, kind, state, last_resolved_at, updated_at, payload)
|
|
99
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
100
|
+
ON CONFLICT(alert_key) DO UPDATE SET state=excluded.state, last_resolved_at=excluded.last_resolved_at, updated_at=excluded.updated_at, payload=excluded.payload`,
|
|
101
|
+
).run(context.key, context.kind, 'resolved', now, now, JSON.stringify(context));
|
|
102
|
+
return { action: 'sent', reason: 'resolved alert sent' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
db.prepare(
|
|
106
|
+
`INSERT INTO alert_state (alert_key, kind, state, updated_at, payload)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?)
|
|
108
|
+
ON CONFLICT(alert_key) DO UPDATE SET state=excluded.state, updated_at=excluded.updated_at, payload=excluded.payload`,
|
|
109
|
+
).run(context.key, context.kind, 'ok', now, JSON.stringify(context));
|
|
110
|
+
return { action: 'noop', reason: 'already ok' };
|
|
111
|
+
} finally {
|
|
112
|
+
db.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
package/lib/apiFetch.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export function apiFetch(path: string, init?: RequestInit): Promise<Response> {
|
|
4
|
+
const url = typeof window !== 'undefined' ? new URL(path, window.location.origin) : new URL(path, 'http://localhost:3740');
|
|
5
|
+
// Auth via JWT cookie (set by /api/auth/login) — no query param needed
|
|
6
|
+
return fetch(url.toString(), { cache: 'no-store', credentials: 'same-origin', ...init });
|
|
7
|
+
}
|
package/lib/auth.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
2
|
+
import { jwtVerify } from 'jose';
|
|
3
|
+
import { TOKEN } from '@/lib/db';
|
|
4
|
+
|
|
5
|
+
const JWT_SECRET = new TextEncoder().encode(
|
|
6
|
+
process.env.REV4A_JWT_SECRET ?? 'rev4a-jwt-secret-change-in-prod',
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export async function requireBrowserAuth(request: NextRequest): Promise<NextResponse | null> {
|
|
10
|
+
const token = new URL(request.url).searchParams.get('token');
|
|
11
|
+
const auth = request.headers.get('authorization');
|
|
12
|
+
|
|
13
|
+
if (token === TOKEN || auth === `Bearer ${TOKEN}`) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cookieToken = request.cookies.get('rev4a_token')?.value;
|
|
18
|
+
if (cookieToken) {
|
|
19
|
+
try {
|
|
20
|
+
await jwtVerify(cookieToken, JWT_SECRET);
|
|
21
|
+
return null;
|
|
22
|
+
} catch {
|
|
23
|
+
// Fall through to unauthorized.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
28
|
+
}
|
package/lib/billing.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { ModelCost } from '@/lib/types';
|
|
2
|
+
|
|
3
|
+
export type BillingMode = 'usage' | 'fixed' | 'credits' | 'unknown';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Economic dollar costs should only include providers we actually bill per usage.
|
|
7
|
+
* Today that is primarily OpenRouter, plus a small set of explicit pay-per-use aliases
|
|
8
|
+
* stored without provider prefixes in older rows.
|
|
9
|
+
*/
|
|
10
|
+
const BILLABLE_MODEL_PREFIXES = ['openrouter/'] as const;
|
|
11
|
+
const BILLABLE_ALIAS_PREFIXES = ['deepseek', 'deepseek4', 'or-', 'openrouter', 'qwen', 'kimi', 'glm'] as const;
|
|
12
|
+
const NON_BILLABLE_ALIAS_PATTERNS = [
|
|
13
|
+
/^gpt-5(\.|-|$)/,
|
|
14
|
+
/^codex(\.|-|$)/,
|
|
15
|
+
/^claude(\.|-|$)/,
|
|
16
|
+
/^claude-cli(\/|$)/,
|
|
17
|
+
/^anthropic(\/|$)/,
|
|
18
|
+
/^openai-codex(\/|$)/,
|
|
19
|
+
/^github-copilot(\/|$)/,
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export interface BillingBucket {
|
|
23
|
+
mode: BillingMode;
|
|
24
|
+
cost_usd: number;
|
|
25
|
+
sessions: number;
|
|
26
|
+
tokens_in: number;
|
|
27
|
+
tokens_out: number;
|
|
28
|
+
models: ModelCost[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BillingSummary {
|
|
32
|
+
usageBasedCost: number;
|
|
33
|
+
dbEstimatedCost: number;
|
|
34
|
+
buckets: Record<BillingMode, BillingBucket>;
|
|
35
|
+
usageModels: ModelCost[];
|
|
36
|
+
fixedModels: ModelCost[];
|
|
37
|
+
creditModels: ModelCost[];
|
|
38
|
+
unknownModels: ModelCost[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isBillableDollarModel(model?: string | null): boolean {
|
|
42
|
+
const m = (model ?? '').trim().toLowerCase();
|
|
43
|
+
if (!m || m === 'unknown') return false;
|
|
44
|
+
if (BILLABLE_MODEL_PREFIXES.some((prefix) => m.startsWith(prefix))) return true;
|
|
45
|
+
if (NON_BILLABLE_ALIAS_PATTERNS.some((pattern) => pattern.test(m))) return false;
|
|
46
|
+
if (m.includes('/')) return false;
|
|
47
|
+
return BILLABLE_ALIAS_PREFIXES.some((prefix) => m.startsWith(prefix));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function classifyBillingModel(model?: string | null): BillingMode {
|
|
51
|
+
const m = (model ?? '').toLowerCase();
|
|
52
|
+
if (!m || m === 'unknown') return 'unknown';
|
|
53
|
+
if (isBillableDollarModel(m)) return 'usage';
|
|
54
|
+
if (m.startsWith('openai-codex/')) return 'fixed';
|
|
55
|
+
if (m.startsWith('anthropic/') || m.includes('claude-cli')) return 'fixed';
|
|
56
|
+
if (m.startsWith('github-copilot/')) return 'credits';
|
|
57
|
+
|
|
58
|
+
// Rev4a DB often stores model aliases without provider prefix.
|
|
59
|
+
if (/^gpt-5\.(2|3|4|5)(-codex|-mini|-pro)?$/.test(m)) return 'fixed';
|
|
60
|
+
if (/^claude-(haiku|sonnet|opus)-/.test(m)) return 'fixed';
|
|
61
|
+
if (/^gemini-.*(flash|pro)/.test(m)) return 'credits';
|
|
62
|
+
|
|
63
|
+
// Known OpenRouter/free/usage-style aliases seen in this workspace.
|
|
64
|
+
if (/^(deepseek|deepseek4|or-|openrouter|qwen|kimi|glm)/.test(m)) return 'usage';
|
|
65
|
+
return 'unknown';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function emptyBucket(mode: BillingMode): BillingBucket {
|
|
69
|
+
return { mode, cost_usd: 0, sessions: 0, tokens_in: 0, tokens_out: 0, models: [] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function summarizeBilling(models: ModelCost[]): BillingSummary {
|
|
73
|
+
const buckets: Record<BillingMode, BillingBucket> = {
|
|
74
|
+
usage: emptyBucket('usage'),
|
|
75
|
+
fixed: emptyBucket('fixed'),
|
|
76
|
+
credits: emptyBucket('credits'),
|
|
77
|
+
unknown: emptyBucket('unknown'),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const model of models) {
|
|
81
|
+
const mode = classifyBillingModel(model.model);
|
|
82
|
+
const bucket = buckets[mode];
|
|
83
|
+
const cost = Number(model.cost_usd ?? 0);
|
|
84
|
+
bucket.cost_usd += cost;
|
|
85
|
+
bucket.sessions += Number(model.sessions ?? 0);
|
|
86
|
+
bucket.tokens_in += Number(model.tokens_in ?? 0);
|
|
87
|
+
bucket.tokens_out += Number(model.tokens_out ?? 0);
|
|
88
|
+
bucket.models.push(model);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
usageBasedCost: buckets.usage.cost_usd,
|
|
93
|
+
dbEstimatedCost: models.reduce((sum, model) => sum + Number(model.cost_usd ?? 0), 0),
|
|
94
|
+
buckets,
|
|
95
|
+
usageModels: buckets.usage.models,
|
|
96
|
+
fixedModels: buckets.fixed.models,
|
|
97
|
+
creditModels: buckets.credits.models,
|
|
98
|
+
unknownModels: buckets.unknown.models,
|
|
99
|
+
};
|
|
100
|
+
}
|