@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,385 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export const OPENCLAW_CONFIG_PATH = '/data/.openclaw/openclaw.json';
|
|
5
|
+
export const SHARED_CONTEXT_DIR = '/data/.openclaw/shared-context';
|
|
6
|
+
export const DEFAULT_WORKSPACE_ROOT = '/data/.openclaw';
|
|
7
|
+
export const BOOTSTRAP_FILES = ['USER.md', 'MEMORY.md', 'AGENTS.md', 'SOUL.md', 'HEARTBEAT.md'] as const;
|
|
8
|
+
export const GLOBAL_CONTEXT_FILES = ['USER.md', 'MEMORY.md', 'AGENTS.md', 'SOUL.md', 'HEARTBEAT.md'] as const;
|
|
9
|
+
export const WORKSPACE_TOTAL_WARN_BYTES = 25 * 1024;
|
|
10
|
+
export const BOOTSTRAP_FILE_WARN_BYTES: Partial<Record<BootstrapFileName, number>> = {
|
|
11
|
+
'MEMORY.md': 10 * 1024,
|
|
12
|
+
'SOUL.md': 4 * 1024,
|
|
13
|
+
'USER.md': 8 * 1024,
|
|
14
|
+
'AGENTS.md': 10 * 1024,
|
|
15
|
+
'HEARTBEAT.md': 4 * 1024,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type BootstrapFileName = (typeof BOOTSTRAP_FILES)[number];
|
|
19
|
+
export type StrategyHealth = 'ok' | 'warning' | 'error';
|
|
20
|
+
|
|
21
|
+
export interface FileSummary {
|
|
22
|
+
key: string;
|
|
23
|
+
path: string;
|
|
24
|
+
exists: boolean;
|
|
25
|
+
isSymlink: boolean;
|
|
26
|
+
symlinkTarget: string | null;
|
|
27
|
+
size: number | null;
|
|
28
|
+
mtime: string | null;
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AgentMemorySummary {
|
|
33
|
+
agentId: string;
|
|
34
|
+
name: string;
|
|
35
|
+
displayName?: string;
|
|
36
|
+
workspace: string;
|
|
37
|
+
source: string[];
|
|
38
|
+
files: Record<BootstrapFileName, FileSummary>;
|
|
39
|
+
bootstrapBytes: number;
|
|
40
|
+
bootstrapBudgetBytes: number;
|
|
41
|
+
strategy: {
|
|
42
|
+
userProfile: 'shared' | 'local' | 'missing';
|
|
43
|
+
memory: 'local' | 'missing' | 'shared-warning';
|
|
44
|
+
health: StrategyHealth;
|
|
45
|
+
};
|
|
46
|
+
warnings: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MemoryContextPayload {
|
|
50
|
+
globalContext: FileSummary[];
|
|
51
|
+
agents: AgentMemorySummary[];
|
|
52
|
+
strategy: {
|
|
53
|
+
userProfile: 'shared' | 'local' | 'missing';
|
|
54
|
+
memory: 'local' | 'missing' | 'shared-warning';
|
|
55
|
+
health: StrategyHealth;
|
|
56
|
+
warnings: string[];
|
|
57
|
+
};
|
|
58
|
+
summary: {
|
|
59
|
+
totalAgents: number;
|
|
60
|
+
userLinked: number;
|
|
61
|
+
warnings: number;
|
|
62
|
+
globalFiles: number;
|
|
63
|
+
};
|
|
64
|
+
warnings: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ConfigAgent {
|
|
68
|
+
id?: unknown;
|
|
69
|
+
name?: unknown;
|
|
70
|
+
workspace?: unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface OpenClawConfig {
|
|
74
|
+
agents?: {
|
|
75
|
+
list?: ConfigAgent[];
|
|
76
|
+
defaults?: {
|
|
77
|
+
workspace?: unknown;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface AgentSeed {
|
|
83
|
+
agentId: string;
|
|
84
|
+
name: string;
|
|
85
|
+
displayName?: string;
|
|
86
|
+
workspace: string;
|
|
87
|
+
source: Set<string>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toIsoTime(value: Date | null): string | null {
|
|
91
|
+
return value ? value.toISOString() : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeAgentIdFromWorkspace(workspace: string): string {
|
|
95
|
+
const base = path.basename(workspace);
|
|
96
|
+
if (base === 'workspace') return 'default';
|
|
97
|
+
if (base.startsWith('workspace-')) return base.slice('workspace-'.length) || 'unknown';
|
|
98
|
+
return base || 'unknown';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function safeReadConfigWarnings(warnings: string[]): OpenClawConfig {
|
|
102
|
+
if (!fs.existsSync(OPENCLAW_CONFIG_PATH)) {
|
|
103
|
+
warnings.push(`OpenClaw config missing: ${OPENCLAW_CONFIG_PATH}`);
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(fs.readFileSync(OPENCLAW_CONFIG_PATH, 'utf8')) as OpenClawConfig;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
warnings.push(`Unable to parse openclaw.json: ${(error as Error).message}`);
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function uniqueStrings(values: string[]): string[] {
|
|
116
|
+
return [...new Set(values.filter(Boolean))];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function formatBytesForWarning(value: number): string {
|
|
120
|
+
if (value < 1024) return `${value} B`;
|
|
121
|
+
return `${(value / 1024).toFixed(1)} KB`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function applyBootstrapSizeWarnings(file: FileSummary): void {
|
|
125
|
+
const limit = BOOTSTRAP_FILE_WARN_BYTES[file.key as BootstrapFileName];
|
|
126
|
+
if (!limit || file.size === null || file.size <= limit) return;
|
|
127
|
+
file.warnings.push(`${file.key} sopra soglia: ${formatBytesForWarning(file.size)} / ${formatBytesForWarning(limit)}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function inspectFile(filePath: string, key: string): FileSummary {
|
|
131
|
+
const warnings: string[] = [];
|
|
132
|
+
let exists = false;
|
|
133
|
+
let isSymlink = false;
|
|
134
|
+
let symlinkTarget: string | null = null;
|
|
135
|
+
let size: number | null = null;
|
|
136
|
+
let mtime: string | null = null;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const lst = fs.lstatSync(filePath);
|
|
140
|
+
exists = true;
|
|
141
|
+
isSymlink = lst.isSymbolicLink();
|
|
142
|
+
|
|
143
|
+
if (isSymlink) {
|
|
144
|
+
try {
|
|
145
|
+
const linkValue = fs.readlinkSync(filePath);
|
|
146
|
+
symlinkTarget = path.resolve(path.dirname(filePath), linkValue);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
warnings.push(`Unable to resolve symlink: ${(error as Error).message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const stat = fs.statSync(filePath);
|
|
154
|
+
size = stat.size;
|
|
155
|
+
mtime = toIsoTime(stat.mtime);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
warnings.push(`Unable to stat target: ${(error as Error).message}`);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
161
|
+
if (code !== 'ENOENT') warnings.push(`Unable to inspect file: ${(error as Error).message}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const summary = {
|
|
165
|
+
key,
|
|
166
|
+
path: filePath,
|
|
167
|
+
exists,
|
|
168
|
+
isSymlink,
|
|
169
|
+
symlinkTarget,
|
|
170
|
+
size,
|
|
171
|
+
mtime,
|
|
172
|
+
warnings,
|
|
173
|
+
};
|
|
174
|
+
applyBootstrapSizeWarnings(summary);
|
|
175
|
+
return summary;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function inspectGlobalContext(warnings: string[]): FileSummary[] {
|
|
179
|
+
if (!fs.existsSync(SHARED_CONTEXT_DIR)) {
|
|
180
|
+
warnings.push(`Shared context directory missing: ${SHARED_CONTEXT_DIR}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return GLOBAL_CONTEXT_FILES.map((fileName) => {
|
|
184
|
+
const summary = inspectFile(path.join(SHARED_CONTEXT_DIR, fileName), fileName);
|
|
185
|
+
if (!fs.existsSync(SHARED_CONTEXT_DIR)) {
|
|
186
|
+
summary.warnings.push('Shared context directory not found');
|
|
187
|
+
}
|
|
188
|
+
return summary;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function discoverAgents(globalWarnings: string[]): AgentSeed[] {
|
|
193
|
+
const config = safeReadConfigWarnings(globalWarnings);
|
|
194
|
+
const defaultWorkspace = typeof config.agents?.defaults?.workspace === 'string' && config.agents.defaults.workspace.trim()
|
|
195
|
+
? config.agents.defaults.workspace.trim()
|
|
196
|
+
: path.join(DEFAULT_WORKSPACE_ROOT, 'workspace');
|
|
197
|
+
|
|
198
|
+
const seeds = new Map<string, AgentSeed>();
|
|
199
|
+
|
|
200
|
+
const upsert = (agentId: string, name: string, workspace: string, source: string, displayName?: string) => {
|
|
201
|
+
const cleanWorkspace = workspace.trim();
|
|
202
|
+
if (!cleanWorkspace) return;
|
|
203
|
+
const key = `${agentId}@@${cleanWorkspace}`;
|
|
204
|
+
const existing = seeds.get(key);
|
|
205
|
+
if (existing) {
|
|
206
|
+
existing.source.add(source);
|
|
207
|
+
if (existing.name === existing.agentId && name) existing.name = name;
|
|
208
|
+
if (displayName && !existing.displayName) existing.displayName = displayName;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
seeds.set(key, {
|
|
212
|
+
agentId,
|
|
213
|
+
name: name || agentId,
|
|
214
|
+
displayName,
|
|
215
|
+
workspace: cleanWorkspace,
|
|
216
|
+
source: new Set([source]),
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
for (const agent of config.agents?.list ?? []) {
|
|
221
|
+
const agentId = typeof agent.id === 'string' && agent.id.trim() ? agent.id.trim() : '';
|
|
222
|
+
if (!agentId) continue;
|
|
223
|
+
const agentName = typeof agent.name === 'string' && agent.name.trim() ? agent.name.trim() : agentId;
|
|
224
|
+
const workspace = typeof agent.workspace === 'string' && agent.workspace.trim() ? agent.workspace.trim() : defaultWorkspace;
|
|
225
|
+
upsert(agentId, agentName, workspace, 'config');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
for (const entry of fs.readdirSync(DEFAULT_WORKSPACE_ROOT, { withFileTypes: true })) {
|
|
230
|
+
if (!entry.isDirectory()) continue;
|
|
231
|
+
if (entry.name !== 'workspace' && !entry.name.startsWith('workspace-')) continue;
|
|
232
|
+
const workspace = path.join(DEFAULT_WORKSPACE_ROOT, entry.name);
|
|
233
|
+
const agentId = normalizeAgentIdFromWorkspace(workspace);
|
|
234
|
+
upsert(agentId, agentId, workspace, 'filesystem');
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
globalWarnings.push(`Unable to scan workspaces: ${(error as Error).message}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Discover Docker agents with AGENT_ID label (Rev4a-managed containers)
|
|
241
|
+
try {
|
|
242
|
+
const { execSync } = require('child_process');
|
|
243
|
+
const out = execSync(
|
|
244
|
+
`docker ps --filter "label=AGENT_ID" --format '{{.Names}}|{{.Label "AGENT_ID"}}'`,
|
|
245
|
+
{ timeout: 5000, encoding: 'utf-8' },
|
|
246
|
+
).trim();
|
|
247
|
+
if (out) {
|
|
248
|
+
for (const line of out.split('\n')) {
|
|
249
|
+
const [containerName, agentId] = line.split('|');
|
|
250
|
+
if (!containerName || !agentId) continue;
|
|
251
|
+
// Read AGENT_NAME from container env
|
|
252
|
+
let displayName = '';
|
|
253
|
+
try {
|
|
254
|
+
displayName = execSync(
|
|
255
|
+
`docker exec ${containerName} sh -c 'printenv AGENT_NAME'`,
|
|
256
|
+
{ timeout: 3000, encoding: 'utf-8' },
|
|
257
|
+
).trim();
|
|
258
|
+
} catch { /* env read failed — use fallback */ }
|
|
259
|
+
const workspace = '/root/.openclaw/workspace/';
|
|
260
|
+
upsert(agentId, displayName || containerName, workspace, 'docker', displayName || undefined);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
// Docker not available — not a fatal error for memory context
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!seeds.size) {
|
|
268
|
+
upsert('default', 'default', defaultWorkspace, 'defaults');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return [...seeds.values()].sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function classifyAgent(summary: AgentMemorySummary): AgentMemorySummary['strategy'] {
|
|
275
|
+
const user = summary.files['USER.md'];
|
|
276
|
+
const memory = summary.files['MEMORY.md'];
|
|
277
|
+
|
|
278
|
+
const userProfile = !user.exists
|
|
279
|
+
? 'missing'
|
|
280
|
+
: user.isSymlink && user.symlinkTarget?.startsWith(`${SHARED_CONTEXT_DIR}${path.sep}`)
|
|
281
|
+
? 'shared'
|
|
282
|
+
: 'local';
|
|
283
|
+
|
|
284
|
+
const memoryStatus = !memory.exists
|
|
285
|
+
? 'missing'
|
|
286
|
+
: memory.isSymlink && memory.symlinkTarget?.startsWith(`${SHARED_CONTEXT_DIR}${path.sep}`)
|
|
287
|
+
? 'shared-warning'
|
|
288
|
+
: 'local';
|
|
289
|
+
|
|
290
|
+
if (userProfile === 'missing') summary.warnings.push('USER.md missing');
|
|
291
|
+
if (memoryStatus === 'missing') summary.warnings.push('MEMORY.md missing');
|
|
292
|
+
if (memoryStatus === 'shared-warning') summary.warnings.push('MEMORY.md should stay local, not shared');
|
|
293
|
+
|
|
294
|
+
for (const fileName of ['AGENTS.md', 'SOUL.md'] as const) {
|
|
295
|
+
if (!summary.files[fileName].exists) summary.warnings.push(`${fileName} missing`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const allWarnings = uniqueStrings(summary.warnings.concat(...Object.values(summary.files).map((file) => file.warnings)));
|
|
299
|
+
const hasStructuralWarning = allWarnings.some((warning) => /should stay local|missing/.test(warning));
|
|
300
|
+
const health: StrategyHealth = hasStructuralWarning
|
|
301
|
+
? allWarnings.length >= 3 || userProfile === 'missing'
|
|
302
|
+
? 'error'
|
|
303
|
+
: 'warning'
|
|
304
|
+
: allWarnings.length > 0
|
|
305
|
+
? 'warning'
|
|
306
|
+
: 'ok';
|
|
307
|
+
|
|
308
|
+
return { userProfile, memory: memoryStatus, health };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function inspectAgent(seed: AgentSeed): AgentMemorySummary {
|
|
312
|
+
const files = Object.fromEntries(
|
|
313
|
+
BOOTSTRAP_FILES.map((fileName) => [fileName, inspectFile(path.join(seed.workspace, fileName), fileName)]),
|
|
314
|
+
) as Record<BootstrapFileName, FileSummary>;
|
|
315
|
+
|
|
316
|
+
const bootstrapBytes = Object.values(files).reduce((total, file) => total + (file.size ?? 0), 0);
|
|
317
|
+
|
|
318
|
+
const summary: AgentMemorySummary = {
|
|
319
|
+
agentId: seed.agentId,
|
|
320
|
+
name: seed.name,
|
|
321
|
+
displayName: seed.displayName,
|
|
322
|
+
workspace: seed.workspace,
|
|
323
|
+
source: [...seed.source].sort(),
|
|
324
|
+
files,
|
|
325
|
+
bootstrapBytes,
|
|
326
|
+
bootstrapBudgetBytes: WORKSPACE_TOTAL_WARN_BYTES,
|
|
327
|
+
strategy: {
|
|
328
|
+
userProfile: 'missing',
|
|
329
|
+
memory: 'missing',
|
|
330
|
+
health: 'error',
|
|
331
|
+
},
|
|
332
|
+
warnings: [],
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
summary.strategy = classifyAgent(summary);
|
|
336
|
+
if (bootstrapBytes > WORKSPACE_TOTAL_WARN_BYTES) {
|
|
337
|
+
summary.warnings.push(`Bootstrap totale sopra budget: ${formatBytesForWarning(bootstrapBytes)} / ${formatBytesForWarning(WORKSPACE_TOTAL_WARN_BYTES)}`);
|
|
338
|
+
}
|
|
339
|
+
summary.warnings = uniqueStrings(summary.warnings.concat(...Object.values(files).map((file) => file.warnings)));
|
|
340
|
+
return summary;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function getMemoryContextSnapshot(): MemoryContextPayload {
|
|
344
|
+
const warnings: string[] = [];
|
|
345
|
+
const globalContext = inspectGlobalContext(warnings);
|
|
346
|
+
const agents = discoverAgents(warnings).map(inspectAgent);
|
|
347
|
+
|
|
348
|
+
const allAgentWarnings = agents.flatMap((agent) => agent.warnings);
|
|
349
|
+
const healths = agents.map((agent) => agent.strategy.health);
|
|
350
|
+
const topHealth: StrategyHealth = healths.includes('error')
|
|
351
|
+
? 'error'
|
|
352
|
+
: healths.includes('warning') || warnings.length > 0 || globalContext.some((file) => file.warnings.length > 0)
|
|
353
|
+
? 'warning'
|
|
354
|
+
: 'ok';
|
|
355
|
+
|
|
356
|
+
const topUserProfile = agents.some((agent) => agent.strategy.userProfile === 'shared')
|
|
357
|
+
? 'shared'
|
|
358
|
+
: agents.some((agent) => agent.strategy.userProfile === 'local')
|
|
359
|
+
? 'local'
|
|
360
|
+
: 'missing';
|
|
361
|
+
|
|
362
|
+
const topMemory = agents.some((agent) => agent.strategy.memory === 'shared-warning')
|
|
363
|
+
? 'shared-warning'
|
|
364
|
+
: agents.some((agent) => agent.strategy.memory === 'local')
|
|
365
|
+
? 'local'
|
|
366
|
+
: 'missing';
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
globalContext,
|
|
370
|
+
agents,
|
|
371
|
+
strategy: {
|
|
372
|
+
userProfile: topUserProfile,
|
|
373
|
+
memory: topMemory,
|
|
374
|
+
health: topHealth,
|
|
375
|
+
warnings: uniqueStrings(warnings.concat(allAgentWarnings, ...globalContext.map((file) => file.warnings))),
|
|
376
|
+
},
|
|
377
|
+
summary: {
|
|
378
|
+
totalAgents: agents.length,
|
|
379
|
+
userLinked: agents.filter((agent) => agent.strategy.userProfile === 'shared').length,
|
|
380
|
+
warnings: uniqueStrings(warnings.concat(allAgentWarnings, ...globalContext.map((file) => file.warnings))).length,
|
|
381
|
+
globalFiles: globalContext.filter((file) => file.exists).length,
|
|
382
|
+
},
|
|
383
|
+
warnings: uniqueStrings(warnings),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model catalogue — bundle-first + user-overrides.
|
|
3
|
+
*
|
|
4
|
+
* Reads the bundled models.config.json from the npm package and applies
|
|
5
|
+
* user toggles from REV4A_DATA/model-overrides.json.
|
|
6
|
+
*
|
|
7
|
+
* Imported by both provider/route.ts and gateway/sync.ts — shared logic,
|
|
8
|
+
* avoids circular dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { REV4A_DATA } from '@/lib/rev4a-paths';
|
|
13
|
+
|
|
14
|
+
/* ------------------------------------------------------------------ */
|
|
15
|
+
/* Types */
|
|
16
|
+
/* ------------------------------------------------------------------ */
|
|
17
|
+
|
|
18
|
+
export interface ModelConfigEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
provider: string;
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
modality?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ------------------------------------------------------------------ */
|
|
27
|
+
/* Path helpers */
|
|
28
|
+
/* ------------------------------------------------------------------ */
|
|
29
|
+
|
|
30
|
+
/** Absolute path to model-overrides.json in REV4A_DATA. */
|
|
31
|
+
function overridesPath(): string {
|
|
32
|
+
return path.join(REV4A_DATA, 'model-overrides.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Resolve the bundled models.config.json (in the npm package). */
|
|
36
|
+
function resolveBundledPath(): string {
|
|
37
|
+
// cwd first (works with systemd WorkingDirectory pointed at the repo / project)
|
|
38
|
+
const cwdPath = path.resolve(process.cwd(), 'models.config.json');
|
|
39
|
+
if (fs.existsSync(cwdPath)) return cwdPath;
|
|
40
|
+
// Fallback: relative to this module (lib/ → project root)
|
|
41
|
+
return path.resolve(__dirname, '..', 'models.config.json');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ------------------------------------------------------------------ */
|
|
45
|
+
/* Loaders */
|
|
46
|
+
/* ------------------------------------------------------------------ */
|
|
47
|
+
|
|
48
|
+
function loadBundledModels(): ModelConfigEntry[] {
|
|
49
|
+
try {
|
|
50
|
+
const raw = fs.readFileSync(resolveBundledPath(), 'utf-8');
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
if (Array.isArray(parsed.models)) {
|
|
53
|
+
return parsed.models.map((m: Record<string, unknown>) => ({
|
|
54
|
+
id: String(m.id ?? ''),
|
|
55
|
+
name: String(m.name ?? ''),
|
|
56
|
+
provider: String(m.provider ?? ''),
|
|
57
|
+
enabled: Boolean(m.enabled),
|
|
58
|
+
modality: typeof m.modality === 'string' ? m.modality : undefined,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
} catch { /* fall through */ }
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function loadOverrides(): Record<string, boolean> {
|
|
66
|
+
try {
|
|
67
|
+
if (!fs.existsSync(overridesPath())) return {};
|
|
68
|
+
const raw = fs.readFileSync(overridesPath(), 'utf-8');
|
|
69
|
+
const parsed = JSON.parse(raw);
|
|
70
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
71
|
+
const out: Record<string, boolean> = {};
|
|
72
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
73
|
+
if (typeof v === 'boolean') out[k] = v;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
} catch { /* ignore */ }
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function writeOverrides(overrides: Record<string, boolean>): void {
|
|
82
|
+
if (!fs.existsSync(REV4A_DATA)) {
|
|
83
|
+
fs.mkdirSync(REV4A_DATA, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
fs.writeFileSync(overridesPath(), JSON.stringify(overrides, null, 2), 'utf-8');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Load the full model catalogue:
|
|
90
|
+
* 1. Read models.config.json from the npm package (bundle).
|
|
91
|
+
* 2. Apply user overrides (enabled/disabled toggles).
|
|
92
|
+
*
|
|
93
|
+
* The bundle is read-only at runtime. User choices live in model-overrides.json.
|
|
94
|
+
*/
|
|
95
|
+
export function loadModelsConfig(): ModelConfigEntry[] {
|
|
96
|
+
const bundled = loadBundledModels();
|
|
97
|
+
const overrides = loadOverrides();
|
|
98
|
+
|
|
99
|
+
// Clean up orphan overrides (models removed from bundle)
|
|
100
|
+
const bundledIds = new Set(bundled.map((m) => m.id));
|
|
101
|
+
let dirty = false;
|
|
102
|
+
for (const id of Object.keys(overrides)) {
|
|
103
|
+
if (!bundledIds.has(id)) {
|
|
104
|
+
delete overrides[id];
|
|
105
|
+
dirty = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (dirty) writeOverrides(overrides);
|
|
109
|
+
|
|
110
|
+
return bundled.map((m) => ({
|
|
111
|
+
...m,
|
|
112
|
+
enabled: Object.prototype.hasOwnProperty.call(overrides, m.id) ? overrides[m.id] : m.enabled,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Save a single model toggle (writes to model-overrides.json). */
|
|
117
|
+
export function toggleModelOverride(modelId: string, enabled: boolean): boolean {
|
|
118
|
+
const overrides = loadOverrides();
|
|
119
|
+
const bundled = loadBundledModels();
|
|
120
|
+
const bm = bundled.find((m) => m.id === modelId);
|
|
121
|
+
if (!bm) return false; // unknown model
|
|
122
|
+
|
|
123
|
+
if (enabled === bm.enabled) {
|
|
124
|
+
// Reset to default → remove key (keeps file clean)
|
|
125
|
+
delete overrides[modelId];
|
|
126
|
+
} else {
|
|
127
|
+
overrides[modelId] = enabled;
|
|
128
|
+
}
|
|
129
|
+
writeOverrides(overrides);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model pricing data — static, from model-pricing.json in the project root.
|
|
3
|
+
*
|
|
4
|
+
* OpenRouter models are NOT included here; their pricing is fetched live
|
|
5
|
+
* via the OpenRouter API and merged client-side.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
export interface ModelPricing {
|
|
11
|
+
input: number;
|
|
12
|
+
output: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let _cache: Record<string, ModelPricing> | null = null;
|
|
16
|
+
let _cacheMtime = 0;
|
|
17
|
+
|
|
18
|
+
function resolvePricingPath(): string {
|
|
19
|
+
// Try cwd first (works with systemd WorkingDirectory)
|
|
20
|
+
const cwdPath = path.resolve(process.cwd(), 'model-pricing.json');
|
|
21
|
+
if (fs.existsSync(cwdPath)) return cwdPath;
|
|
22
|
+
// Fallback: relative to this module (works for npm global installs)
|
|
23
|
+
return path.resolve(__dirname, '..', 'model-pricing.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PRICING_PATH = resolvePricingPath();
|
|
27
|
+
|
|
28
|
+
function load(): Record<string, ModelPricing> {
|
|
29
|
+
try {
|
|
30
|
+
const stat = fs.statSync(PRICING_PATH);
|
|
31
|
+
if (_cache && stat.mtimeMs <= _cacheMtime) return _cache;
|
|
32
|
+
const raw = fs.readFileSync(PRICING_PATH, 'utf-8');
|
|
33
|
+
_cache = JSON.parse(raw) as Record<string, ModelPricing>;
|
|
34
|
+
_cacheMtime = stat.mtimeMs;
|
|
35
|
+
return _cache!;
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Get pricing for a single model, or null if not found. */
|
|
42
|
+
export function getPricing(modelId: string): ModelPricing | null {
|
|
43
|
+
const all = load();
|
|
44
|
+
return all[modelId] ?? null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Get all static pricing entries. */
|
|
48
|
+
export function getAllPricing(): Record<string, ModelPricing> {
|
|
49
|
+
return { ...load() };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Format pricing for display: "$0.44 / $0.87" or "Free" or "Dynamic" or "—" */
|
|
53
|
+
export function formatPricing(p: ModelPricing | null | undefined): string {
|
|
54
|
+
if (!p) return '—';
|
|
55
|
+
if (p.input === -1 && p.output === -1) return 'Dynamic';
|
|
56
|
+
if (p.input === 0 && p.output === 0) return 'Free';
|
|
57
|
+
const fmt = (n: number) => {
|
|
58
|
+
if (n < 0.01) return `$${n.toFixed(3)}`;
|
|
59
|
+
if (n < 1) return `$${n.toFixed(2)}`;
|
|
60
|
+
return `$${n.toFixed(2)}`;
|
|
61
|
+
};
|
|
62
|
+
return `${fmt(p.input)} / ${fmt(p.output)}`;
|
|
63
|
+
}
|