@sulala/agent-os 0.1.18 → 0.1.20
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 +111 -37
- package/dashboard-dist/assets/index-B3C3mWft.js +72 -0
- package/dashboard-dist/assets/index-CRHWtry2.js +72 -0
- package/dashboard-dist/index.html +1 -1
- package/dist/cli.js +165 -59
- package/dist/index.js +160 -51
- package/package.json +4 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/png" href="/logo_dark.png" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Sulala Agent Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-B3C3mWft.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-Cdtyuhuu.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,8 @@ __export(exports_config, {
|
|
|
43
43
|
getSkillsDir: () => getSkillsDir,
|
|
44
44
|
getSkillConfigPath: () => getSkillConfigPath,
|
|
45
45
|
getSeedSkillsDir: () => getSeedSkillsDir,
|
|
46
|
+
getMemoryDbPath: () => getMemoryDbPath,
|
|
47
|
+
getDefaultModelForAvailableProvider: () => getDefaultModelForAvailableProvider,
|
|
46
48
|
getDashboardSecretFromConfig: () => getDashboardSecretFromConfig,
|
|
47
49
|
getDashboardSecret: () => getDashboardSecret,
|
|
48
50
|
getConfigsDir: () => getConfigsDir,
|
|
@@ -53,7 +55,7 @@ __export(exports_config, {
|
|
|
53
55
|
});
|
|
54
56
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
55
57
|
import { existsSync } from "fs";
|
|
56
|
-
import { join, resolve, relative } from "path";
|
|
58
|
+
import { join, resolve, relative, dirname } from "path";
|
|
57
59
|
import { randomBytes } from "crypto";
|
|
58
60
|
function getAgentOsHome() {
|
|
59
61
|
return process.env.AGENT_OS_HOME || DEFAULT_HOME;
|
|
@@ -61,6 +63,14 @@ function getAgentOsHome() {
|
|
|
61
63
|
function getConfigPath() {
|
|
62
64
|
return join(getAgentOsHome(), "config.json");
|
|
63
65
|
}
|
|
66
|
+
function getMemoryDbPath() {
|
|
67
|
+
if (process.env.AGENT_MEMORY_DB_PATH)
|
|
68
|
+
return process.env.AGENT_MEMORY_DB_PATH;
|
|
69
|
+
const agentsDir = process.env.AGENT_OS_AGENTS_DIR;
|
|
70
|
+
if (agentsDir)
|
|
71
|
+
return join(dirname(agentsDir), "database.db");
|
|
72
|
+
return join(getAgentOsHome(), "database.db");
|
|
73
|
+
}
|
|
64
74
|
async function readConfig() {
|
|
65
75
|
const path = getConfigPath();
|
|
66
76
|
try {
|
|
@@ -120,6 +130,22 @@ async function readConfig() {
|
|
|
120
130
|
}
|
|
121
131
|
return {};
|
|
122
132
|
}
|
|
133
|
+
async function getDefaultModelForAvailableProvider() {
|
|
134
|
+
const config = await readConfig();
|
|
135
|
+
const googleKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY?.trim() || config.google_api_key?.trim();
|
|
136
|
+
if (googleKey)
|
|
137
|
+
return DEFAULT_MODEL_BY_PROVIDER.google;
|
|
138
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY?.trim() || config.anthropic_api_key?.trim();
|
|
139
|
+
if (anthropicKey)
|
|
140
|
+
return DEFAULT_MODEL_BY_PROVIDER.anthropic;
|
|
141
|
+
const openrouterKey = process.env.OPENROUTER_API_KEY?.trim() || config.openrouter_api_key?.trim() || (config.provider === "openrouter" ? config.api_key?.trim() : undefined);
|
|
142
|
+
if (openrouterKey)
|
|
143
|
+
return DEFAULT_MODEL_BY_PROVIDER.openrouter;
|
|
144
|
+
const openaiKey = process.env.OPENAI_API_KEY?.trim() || config.openai_api_key?.trim() || (config.provider === "openai" ? config.api_key?.trim() : undefined);
|
|
145
|
+
if (openaiKey)
|
|
146
|
+
return DEFAULT_MODEL_BY_PROVIDER.openai;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
123
149
|
async function writeConfig(updates) {
|
|
124
150
|
const current = await readConfig();
|
|
125
151
|
const merged = {
|
|
@@ -269,9 +295,15 @@ function resolveInWorkspace(workspaceDir, relativePath) {
|
|
|
269
295
|
}
|
|
270
296
|
return resolved;
|
|
271
297
|
}
|
|
272
|
-
var DEFAULT_HOME, DEFAULT_SKILLS_REGISTRY_URL = "https://hub.sulala.ai/api/sulalahub/registry";
|
|
298
|
+
var DEFAULT_HOME, DEFAULT_MODEL_BY_PROVIDER, DEFAULT_SKILLS_REGISTRY_URL = "https://hub.sulala.ai/api/sulalahub/registry";
|
|
273
299
|
var init_config = __esm(() => {
|
|
274
300
|
DEFAULT_HOME = join(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os");
|
|
301
|
+
DEFAULT_MODEL_BY_PROVIDER = {
|
|
302
|
+
google: "gemini-2.0-flash",
|
|
303
|
+
anthropic: "claude-sonnet-4-6",
|
|
304
|
+
openrouter: "openai/gpt-4o-mini",
|
|
305
|
+
openai: "gpt-4o-mini"
|
|
306
|
+
};
|
|
275
307
|
});
|
|
276
308
|
|
|
277
309
|
// src/db/memory-store.ts
|
|
@@ -629,7 +661,7 @@ function getSeedAgentsDir() {
|
|
|
629
661
|
return fromSrc;
|
|
630
662
|
return join2(process.cwd(), "data", "agents");
|
|
631
663
|
}
|
|
632
|
-
async function insertSeedAgentFromFile(seedDir, name, skipIfExists) {
|
|
664
|
+
async function insertSeedAgentFromFile(seedDir, name, skipIfExists, modelOverride) {
|
|
633
665
|
if (!agentStore)
|
|
634
666
|
return false;
|
|
635
667
|
const path = join2(seedDir, name);
|
|
@@ -640,10 +672,12 @@ async function insertSeedAgentFromFile(seedDir, name, skipIfExists) {
|
|
|
640
672
|
return false;
|
|
641
673
|
if (skipIfExists && agentStore.getAgentById(id))
|
|
642
674
|
return false;
|
|
675
|
+
const seedModel = String(parsed.model ?? "").trim();
|
|
676
|
+
const model = modelOverride?.trim() || seedModel || "gpt-4o-mini";
|
|
643
677
|
const payload = {
|
|
644
678
|
id: String(parsed.id).trim(),
|
|
645
679
|
name: String(parsed.name ?? "").trim(),
|
|
646
|
-
model
|
|
680
|
+
model,
|
|
647
681
|
description: parsed.description != null ? String(parsed.description) : undefined,
|
|
648
682
|
personality: parsed.personality != null ? String(parsed.personality) : undefined,
|
|
649
683
|
skills: ensureMemoryInSkills(Array.isArray(parsed.skills) ? parsed.skills : undefined),
|
|
@@ -676,11 +710,12 @@ async function seedAgentsIfEmpty() {
|
|
|
676
710
|
} catch {
|
|
677
711
|
return;
|
|
678
712
|
}
|
|
713
|
+
const modelOverride = await getDefaultModelForAvailableProvider();
|
|
679
714
|
for (const name of entries) {
|
|
680
715
|
if (!name.endsWith(".json"))
|
|
681
716
|
continue;
|
|
682
717
|
try {
|
|
683
|
-
await insertSeedAgentFromFile(seedDir, name, false);
|
|
718
|
+
await insertSeedAgentFromFile(seedDir, name, false, modelOverride);
|
|
684
719
|
} catch (err) {
|
|
685
720
|
console.error(`[agent-registry] Failed to seed ${name}:`, err);
|
|
686
721
|
}
|
|
@@ -696,12 +731,23 @@ async function installSystemAgents() {
|
|
|
696
731
|
} catch (err) {
|
|
697
732
|
throw new Error(`Seed directory not found: ${seedDir}`);
|
|
698
733
|
}
|
|
734
|
+
const modelOverride = await getDefaultModelForAvailableProvider();
|
|
699
735
|
let installed = 0;
|
|
700
736
|
for (const name of entries) {
|
|
701
737
|
if (!name.endsWith(".json"))
|
|
702
738
|
continue;
|
|
703
739
|
try {
|
|
704
|
-
const
|
|
740
|
+
const path = join2(seedDir, name);
|
|
741
|
+
const raw = await readFile2(path, "utf-8");
|
|
742
|
+
const parsed = JSON.parse(raw);
|
|
743
|
+
const id = typeof parsed?.id === "string" ? parsed.id.trim() : null;
|
|
744
|
+
if (id && modelOverride) {
|
|
745
|
+
const existing = agentStore.getAgentById(id);
|
|
746
|
+
if (existing) {
|
|
747
|
+
await updateAgent(id, { model: modelOverride });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const added = await insertSeedAgentFromFile(seedDir, name, true, modelOverride);
|
|
705
751
|
if (added)
|
|
706
752
|
installed += 1;
|
|
707
753
|
} catch (err) {
|
|
@@ -15622,13 +15668,20 @@ var exports_server = {};
|
|
|
15622
15668
|
__export(exports_server, {
|
|
15623
15669
|
startServer: () => startServer
|
|
15624
15670
|
});
|
|
15625
|
-
import { join as join13, dirname, resolve as resolve4 } from "path";
|
|
15671
|
+
import { join as join13, dirname as dirname2, resolve as resolve4 } from "path";
|
|
15626
15672
|
import { mkdirSync, existsSync as existsSync3 } from "fs";
|
|
15673
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
15627
15674
|
function isAuthExempt(pathname, method) {
|
|
15628
15675
|
if (method === "OPTIONS")
|
|
15629
15676
|
return true;
|
|
15630
15677
|
if (method === "GET" && pathname === "/health")
|
|
15631
15678
|
return true;
|
|
15679
|
+
if (method === "GET" && pathname === "/api/bootstrap/dashboard-token")
|
|
15680
|
+
return true;
|
|
15681
|
+
if (method === "GET" && pathname === "/api/bootstrap/workspace-status")
|
|
15682
|
+
return true;
|
|
15683
|
+
if (method === "POST" && pathname === "/api/bootstrap/setup-workspace")
|
|
15684
|
+
return true;
|
|
15632
15685
|
if (method === "POST" && pathname === "/api/channels/telegram/webhook")
|
|
15633
15686
|
return true;
|
|
15634
15687
|
if (method === "POST" && pathname === "/api/channels/slack/webhook")
|
|
@@ -15958,10 +16011,57 @@ function createRoutes() {
|
|
|
15958
16011
|
"/api/conversations/summarize": {
|
|
15959
16012
|
POST: (req) => handleConversationSummarize(req, memoryStore)
|
|
15960
16013
|
},
|
|
16014
|
+
"/api/bootstrap/dashboard-token": {
|
|
16015
|
+
GET: async () => {
|
|
16016
|
+
const config = await readConfig();
|
|
16017
|
+
if (config.onboarding_completed === true) {
|
|
16018
|
+
return Response.json({ error: "Onboarding already completed. Use the login page." }, { status: 403, headers: CORS_HEADERS });
|
|
16019
|
+
}
|
|
16020
|
+
const token = await getDashboardSecret();
|
|
16021
|
+
return jsonResponse({ token });
|
|
16022
|
+
}
|
|
16023
|
+
},
|
|
16024
|
+
"/api/bootstrap/workspace-status": {
|
|
16025
|
+
GET: async () => {
|
|
16026
|
+
try {
|
|
16027
|
+
await mkdir7(getAgentOsHome(), { recursive: true });
|
|
16028
|
+
await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
|
|
16029
|
+
await loadAgents();
|
|
16030
|
+
return jsonResponse({ ready: true });
|
|
16031
|
+
} catch (err) {
|
|
16032
|
+
const msg = errorMessage(err);
|
|
16033
|
+
return jsonResponse({ ready: false, error: msg }, 200);
|
|
16034
|
+
}
|
|
16035
|
+
}
|
|
16036
|
+
},
|
|
16037
|
+
"/api/bootstrap/setup-workspace": {
|
|
16038
|
+
POST: async () => {
|
|
16039
|
+
try {
|
|
16040
|
+
await mkdir7(getAgentOsHome(), { recursive: true });
|
|
16041
|
+
await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
|
|
16042
|
+
await seedAgentsIfEmpty();
|
|
16043
|
+
const { installed } = await installSystemAgents();
|
|
16044
|
+
return jsonResponse({ ok: true, installed });
|
|
16045
|
+
} catch (err) {
|
|
16046
|
+
const msg = errorMessage(err);
|
|
16047
|
+
return jsonResponse({ error: msg }, 400);
|
|
16048
|
+
}
|
|
16049
|
+
}
|
|
16050
|
+
},
|
|
15961
16051
|
"/api/settings": {
|
|
15962
16052
|
GET: (req) => handleSettings(req),
|
|
15963
16053
|
PUT: (req) => handleSettings(req)
|
|
15964
16054
|
},
|
|
16055
|
+
"/api/settings/dashboard-token/regenerate": {
|
|
16056
|
+
POST: async () => {
|
|
16057
|
+
const token = generateDashboardSecret();
|
|
16058
|
+
await writeConfig({ dashboard_secret: token });
|
|
16059
|
+
return jsonResponse({
|
|
16060
|
+
token,
|
|
16061
|
+
message: "Restart the server for the new token to take effect."
|
|
16062
|
+
});
|
|
16063
|
+
}
|
|
16064
|
+
},
|
|
15965
16065
|
"/api/channels/telegram/webhook": {
|
|
15966
16066
|
POST: (req) => handleTelegramWebhook(req, memoryStore)
|
|
15967
16067
|
},
|
|
@@ -16101,53 +16201,62 @@ async function startServer() {
|
|
|
16101
16201
|
if (dashboardMissing) {
|
|
16102
16202
|
console.warn(`[sulala] Dashboard not found at ${DASHBOARD_DIST}. From package root run: cd dashboard && npm run build. If using a global install, reinstall: bun install -g @sulala/agent-os@latest`);
|
|
16103
16203
|
}
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
if (
|
|
16115
|
-
|
|
16204
|
+
let server;
|
|
16205
|
+
try {
|
|
16206
|
+
server = Bun.serve({
|
|
16207
|
+
port: PORT,
|
|
16208
|
+
hostname: HOST,
|
|
16209
|
+
idleTimeout: 120,
|
|
16210
|
+
routes: wrapRouteHandlers(createRoutes(), dashboardSecret),
|
|
16211
|
+
fetch(req, server2) {
|
|
16212
|
+
const url = new URL(req.url);
|
|
16213
|
+
if (req.method === "GET" && url.pathname === "/api/events") {
|
|
16214
|
+
if (dashboardSecret) {
|
|
16215
|
+
const token = url.searchParams.get("token")?.trim() ?? getTokenFromRequest(req);
|
|
16216
|
+
if (token !== dashboardSecret) {
|
|
16217
|
+
return authUnauthorizedResponse();
|
|
16218
|
+
}
|
|
16116
16219
|
}
|
|
16220
|
+
if (server2.upgrade(req, { data: undefined }))
|
|
16221
|
+
return;
|
|
16222
|
+
return Response.json({ error: "Upgrade failed" }, { status: 500, headers: CORS_HEADERS });
|
|
16117
16223
|
}
|
|
16118
|
-
if (
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
hint: "From the sulala package root run: cd dashboard && npm run build",
|
|
16131
|
-
hint_global: "If you installed globally, reinstall to get the dashboard: bun install -g @sulala/agent-os@latest"
|
|
16132
|
-
}, { status: 404, headers: CORS_HEADERS });
|
|
16224
|
+
if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
|
|
16225
|
+
const dashboardResponse = serveDashboard(url.pathname);
|
|
16226
|
+
if (dashboardResponse)
|
|
16227
|
+
return dashboardResponse;
|
|
16228
|
+
if (url.pathname === "/" || url.pathname === "") {
|
|
16229
|
+
return Response.json({
|
|
16230
|
+
error: "Dashboard not built",
|
|
16231
|
+
path: DASHBOARD_DIST,
|
|
16232
|
+
hint: "From the sulala package root run: cd dashboard && npm run build",
|
|
16233
|
+
hint_global: "If you installed globally, reinstall to get the dashboard: bun install -g @sulala/agent-os@latest"
|
|
16234
|
+
}, { status: 404, headers: CORS_HEADERS });
|
|
16235
|
+
}
|
|
16133
16236
|
}
|
|
16134
|
-
|
|
16135
|
-
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
16136
|
-
},
|
|
16137
|
-
error(error) {
|
|
16138
|
-
console.error(error);
|
|
16139
|
-
return Response.json({ error: "Internal Server Error" }, { status: 500, headers: CORS_HEADERS });
|
|
16140
|
-
},
|
|
16141
|
-
websocket: {
|
|
16142
|
-
open(ws) {
|
|
16143
|
-
wsClients.add(ws);
|
|
16237
|
+
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
16144
16238
|
},
|
|
16145
|
-
|
|
16146
|
-
|
|
16239
|
+
error(error) {
|
|
16240
|
+
console.error(error);
|
|
16241
|
+
return Response.json({ error: "Internal Server Error" }, { status: 500, headers: CORS_HEADERS });
|
|
16147
16242
|
},
|
|
16148
|
-
|
|
16243
|
+
websocket: {
|
|
16244
|
+
open(ws) {
|
|
16245
|
+
wsClients.add(ws);
|
|
16246
|
+
},
|
|
16247
|
+
close(ws) {
|
|
16248
|
+
wsClients.delete(ws);
|
|
16249
|
+
},
|
|
16250
|
+
message() {}
|
|
16251
|
+
}
|
|
16252
|
+
});
|
|
16253
|
+
} catch (err) {
|
|
16254
|
+
const e = err;
|
|
16255
|
+
if (e?.code === "EADDRINUSE" || e?.errno === 48) {
|
|
16256
|
+
throw new Error(`Port ${PORT} is already in use. Stop the other process (e.g. sulala stop) or use a different port: PORT=3011 sulala start`);
|
|
16149
16257
|
}
|
|
16150
|
-
|
|
16258
|
+
throw err;
|
|
16259
|
+
}
|
|
16151
16260
|
console.log(`Agent OS server running at ${server.url}`);
|
|
16152
16261
|
startTelegramPolling(memoryStore);
|
|
16153
16262
|
}
|
|
@@ -16193,8 +16302,8 @@ var init_server = __esm(() => {
|
|
|
16193
16302
|
"tool.completed"
|
|
16194
16303
|
];
|
|
16195
16304
|
wsClients = new Set;
|
|
16196
|
-
MEMORY_DB_PATH =
|
|
16197
|
-
mkdirSync(
|
|
16305
|
+
MEMORY_DB_PATH = getMemoryDbPath();
|
|
16306
|
+
mkdirSync(dirname2(MEMORY_DB_PATH), { recursive: true });
|
|
16198
16307
|
memoryStore = new MemoryStore(MEMORY_DB_PATH);
|
|
16199
16308
|
setAgentStore(memoryStore);
|
|
16200
16309
|
for (const type of EVENT_TYPES) {
|
|
@@ -16209,8 +16318,8 @@ init_agent_registry();
|
|
|
16209
16318
|
init_loader();
|
|
16210
16319
|
init_agent_registry();
|
|
16211
16320
|
init_runtime();
|
|
16212
|
-
import { join as join14, dirname as
|
|
16213
|
-
import { readFile as readFile10, writeFile as writeFile6, mkdir as
|
|
16321
|
+
import { join as join14, dirname as dirname3 } from "path";
|
|
16322
|
+
import { readFile as readFile10, writeFile as writeFile6, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
|
|
16214
16323
|
import { existsSync as existsSync4, readFileSync } from "fs";
|
|
16215
16324
|
var PID_FILE = join14(getAgentOsHome(), "sulala.pid");
|
|
16216
16325
|
var DEFAULT_PORT = 3010;
|
|
@@ -16271,7 +16380,7 @@ async function cmdVersion() {
|
|
|
16271
16380
|
async function cmdStart(args) {
|
|
16272
16381
|
const daemon = args.includes("--daemon");
|
|
16273
16382
|
if (daemon) {
|
|
16274
|
-
await
|
|
16383
|
+
await mkdir8(getAgentOsHome(), { recursive: true });
|
|
16275
16384
|
const projectRoot = join14(import.meta.dir, "..");
|
|
16276
16385
|
const distEntry = join14(projectRoot, "dist", "index.js");
|
|
16277
16386
|
const serverEntry = existsSync4(distEntry) ? "dist/index.js" : "src/index.ts";
|
|
@@ -16315,9 +16424,6 @@ async function cmdStop() {
|
|
|
16315
16424
|
await unlink2(PID_FILE).catch(() => {});
|
|
16316
16425
|
console.log("Sulala server stopped.");
|
|
16317
16426
|
}
|
|
16318
|
-
function getMemoryDbPath() {
|
|
16319
|
-
return process.env.AGENT_MEMORY_DB_PATH ?? join14(getAgentOsHome(), "database.db");
|
|
16320
|
-
}
|
|
16321
16427
|
async function startServerDaemonIfNeeded() {
|
|
16322
16428
|
if (existsSync4(PID_FILE)) {
|
|
16323
16429
|
try {
|
|
@@ -16329,7 +16435,7 @@ async function startServerDaemonIfNeeded() {
|
|
|
16329
16435
|
}
|
|
16330
16436
|
} catch {}
|
|
16331
16437
|
}
|
|
16332
|
-
await
|
|
16438
|
+
await mkdir8(getAgentOsHome(), { recursive: true });
|
|
16333
16439
|
const projectRoot = join14(import.meta.dir, "..");
|
|
16334
16440
|
const distEntry = join14(projectRoot, "dist", "index.js");
|
|
16335
16441
|
const serverEntry = existsSync4(distEntry) ? "dist/index.js" : "src/index.ts";
|
|
@@ -16346,8 +16452,8 @@ async function startServerDaemonIfNeeded() {
|
|
|
16346
16452
|
}
|
|
16347
16453
|
async function cmdOnboard() {
|
|
16348
16454
|
const home = getAgentOsHome();
|
|
16349
|
-
await
|
|
16350
|
-
await
|
|
16455
|
+
await mkdir8(home, { recursive: true });
|
|
16456
|
+
await mkdir8(dirname3(getMemoryDbPath()), { recursive: true });
|
|
16351
16457
|
const configPath = join14(home, "config.json");
|
|
16352
16458
|
if (!existsSync4(configPath)) {
|
|
16353
16459
|
const dashboardSecret = generateDashboardSecret();
|