@sulala/agent-os 0.1.19 → 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/index.html +1 -1
- package/dist/cli.js +155 -59
- package/dist/index.js +150 -51
- package/package.json +4 -2
package/dist/index.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
|
// node_modules/yaml/dist/nodes/identity.js
|
|
@@ -11388,7 +11420,7 @@ function getSeedAgentsDir() {
|
|
|
11388
11420
|
return fromSrc;
|
|
11389
11421
|
return join2(process.cwd(), "data", "agents");
|
|
11390
11422
|
}
|
|
11391
|
-
async function insertSeedAgentFromFile(seedDir, name, skipIfExists) {
|
|
11423
|
+
async function insertSeedAgentFromFile(seedDir, name, skipIfExists, modelOverride) {
|
|
11392
11424
|
if (!agentStore)
|
|
11393
11425
|
return false;
|
|
11394
11426
|
const path = join2(seedDir, name);
|
|
@@ -11399,10 +11431,12 @@ async function insertSeedAgentFromFile(seedDir, name, skipIfExists) {
|
|
|
11399
11431
|
return false;
|
|
11400
11432
|
if (skipIfExists && agentStore.getAgentById(id))
|
|
11401
11433
|
return false;
|
|
11434
|
+
const seedModel = String(parsed.model ?? "").trim();
|
|
11435
|
+
const model = modelOverride?.trim() || seedModel || "gpt-4o-mini";
|
|
11402
11436
|
const payload = {
|
|
11403
11437
|
id: String(parsed.id).trim(),
|
|
11404
11438
|
name: String(parsed.name ?? "").trim(),
|
|
11405
|
-
model
|
|
11439
|
+
model,
|
|
11406
11440
|
description: parsed.description != null ? String(parsed.description) : undefined,
|
|
11407
11441
|
personality: parsed.personality != null ? String(parsed.personality) : undefined,
|
|
11408
11442
|
skills: ensureMemoryInSkills(Array.isArray(parsed.skills) ? parsed.skills : undefined),
|
|
@@ -11435,11 +11469,12 @@ async function seedAgentsIfEmpty() {
|
|
|
11435
11469
|
} catch {
|
|
11436
11470
|
return;
|
|
11437
11471
|
}
|
|
11472
|
+
const modelOverride = await getDefaultModelForAvailableProvider();
|
|
11438
11473
|
for (const name of entries) {
|
|
11439
11474
|
if (!name.endsWith(".json"))
|
|
11440
11475
|
continue;
|
|
11441
11476
|
try {
|
|
11442
|
-
await insertSeedAgentFromFile(seedDir, name, false);
|
|
11477
|
+
await insertSeedAgentFromFile(seedDir, name, false, modelOverride);
|
|
11443
11478
|
} catch (err) {
|
|
11444
11479
|
console.error(`[agent-registry] Failed to seed ${name}:`, err);
|
|
11445
11480
|
}
|
|
@@ -11455,12 +11490,23 @@ async function installSystemAgents() {
|
|
|
11455
11490
|
} catch (err) {
|
|
11456
11491
|
throw new Error(`Seed directory not found: ${seedDir}`);
|
|
11457
11492
|
}
|
|
11493
|
+
const modelOverride = await getDefaultModelForAvailableProvider();
|
|
11458
11494
|
let installed = 0;
|
|
11459
11495
|
for (const name of entries) {
|
|
11460
11496
|
if (!name.endsWith(".json"))
|
|
11461
11497
|
continue;
|
|
11462
11498
|
try {
|
|
11463
|
-
const
|
|
11499
|
+
const path = join2(seedDir, name);
|
|
11500
|
+
const raw = await readFile2(path, "utf-8");
|
|
11501
|
+
const parsed = JSON.parse(raw);
|
|
11502
|
+
const id = typeof parsed?.id === "string" ? parsed.id.trim() : null;
|
|
11503
|
+
if (id && modelOverride) {
|
|
11504
|
+
const existing = agentStore.getAgentById(id);
|
|
11505
|
+
if (existing) {
|
|
11506
|
+
await updateAgent(id, { model: modelOverride });
|
|
11507
|
+
}
|
|
11508
|
+
}
|
|
11509
|
+
const added = await insertSeedAgentFromFile(seedDir, name, true, modelOverride);
|
|
11464
11510
|
if (added)
|
|
11465
11511
|
installed += 1;
|
|
11466
11512
|
} catch (err) {
|
|
@@ -15520,8 +15566,9 @@ async function loadPlugins() {
|
|
|
15520
15566
|
|
|
15521
15567
|
// src/server.ts
|
|
15522
15568
|
init_config();
|
|
15523
|
-
import { join as join13, dirname, resolve as resolve4 } from "path";
|
|
15569
|
+
import { join as join13, dirname as dirname2, resolve as resolve4 } from "path";
|
|
15524
15570
|
import { mkdirSync, existsSync as existsSync3 } from "fs";
|
|
15571
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
15525
15572
|
var PORT = parseInt(process.env.PORT ?? "3010", 10);
|
|
15526
15573
|
var DASHBOARD_DIST = (() => {
|
|
15527
15574
|
const root = join13(import.meta.dir, "..");
|
|
@@ -15537,6 +15584,12 @@ function isAuthExempt(pathname, method) {
|
|
|
15537
15584
|
return true;
|
|
15538
15585
|
if (method === "GET" && pathname === "/health")
|
|
15539
15586
|
return true;
|
|
15587
|
+
if (method === "GET" && pathname === "/api/bootstrap/dashboard-token")
|
|
15588
|
+
return true;
|
|
15589
|
+
if (method === "GET" && pathname === "/api/bootstrap/workspace-status")
|
|
15590
|
+
return true;
|
|
15591
|
+
if (method === "POST" && pathname === "/api/bootstrap/setup-workspace")
|
|
15592
|
+
return true;
|
|
15540
15593
|
if (method === "POST" && pathname === "/api/channels/telegram/webhook")
|
|
15541
15594
|
return true;
|
|
15542
15595
|
if (method === "POST" && pathname === "/api/channels/slack/webhook")
|
|
@@ -15600,8 +15653,8 @@ var EVENT_TYPES = [
|
|
|
15600
15653
|
"tool.completed"
|
|
15601
15654
|
];
|
|
15602
15655
|
var wsClients = new Set;
|
|
15603
|
-
var MEMORY_DB_PATH =
|
|
15604
|
-
mkdirSync(
|
|
15656
|
+
var MEMORY_DB_PATH = getMemoryDbPath();
|
|
15657
|
+
mkdirSync(dirname2(MEMORY_DB_PATH), { recursive: true });
|
|
15605
15658
|
var memoryStore = new MemoryStore(MEMORY_DB_PATH);
|
|
15606
15659
|
setAgentStore(memoryStore);
|
|
15607
15660
|
function broadcastEvent(event) {
|
|
@@ -15884,6 +15937,43 @@ function createRoutes() {
|
|
|
15884
15937
|
"/api/conversations/summarize": {
|
|
15885
15938
|
POST: (req) => handleConversationSummarize(req, memoryStore)
|
|
15886
15939
|
},
|
|
15940
|
+
"/api/bootstrap/dashboard-token": {
|
|
15941
|
+
GET: async () => {
|
|
15942
|
+
const config = await readConfig();
|
|
15943
|
+
if (config.onboarding_completed === true) {
|
|
15944
|
+
return Response.json({ error: "Onboarding already completed. Use the login page." }, { status: 403, headers: CORS_HEADERS });
|
|
15945
|
+
}
|
|
15946
|
+
const token = await getDashboardSecret();
|
|
15947
|
+
return jsonResponse({ token });
|
|
15948
|
+
}
|
|
15949
|
+
},
|
|
15950
|
+
"/api/bootstrap/workspace-status": {
|
|
15951
|
+
GET: async () => {
|
|
15952
|
+
try {
|
|
15953
|
+
await mkdir7(getAgentOsHome(), { recursive: true });
|
|
15954
|
+
await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
|
|
15955
|
+
await loadAgents();
|
|
15956
|
+
return jsonResponse({ ready: true });
|
|
15957
|
+
} catch (err) {
|
|
15958
|
+
const msg = errorMessage(err);
|
|
15959
|
+
return jsonResponse({ ready: false, error: msg }, 200);
|
|
15960
|
+
}
|
|
15961
|
+
}
|
|
15962
|
+
},
|
|
15963
|
+
"/api/bootstrap/setup-workspace": {
|
|
15964
|
+
POST: async () => {
|
|
15965
|
+
try {
|
|
15966
|
+
await mkdir7(getAgentOsHome(), { recursive: true });
|
|
15967
|
+
await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
|
|
15968
|
+
await seedAgentsIfEmpty();
|
|
15969
|
+
const { installed } = await installSystemAgents();
|
|
15970
|
+
return jsonResponse({ ok: true, installed });
|
|
15971
|
+
} catch (err) {
|
|
15972
|
+
const msg = errorMessage(err);
|
|
15973
|
+
return jsonResponse({ error: msg }, 400);
|
|
15974
|
+
}
|
|
15975
|
+
}
|
|
15976
|
+
},
|
|
15887
15977
|
"/api/settings": {
|
|
15888
15978
|
GET: (req) => handleSettings(req),
|
|
15889
15979
|
PUT: (req) => handleSettings(req)
|
|
@@ -16037,53 +16127,62 @@ async function startServer() {
|
|
|
16037
16127
|
if (dashboardMissing) {
|
|
16038
16128
|
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`);
|
|
16039
16129
|
}
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
if (
|
|
16051
|
-
|
|
16130
|
+
let server;
|
|
16131
|
+
try {
|
|
16132
|
+
server = Bun.serve({
|
|
16133
|
+
port: PORT,
|
|
16134
|
+
hostname: HOST,
|
|
16135
|
+
idleTimeout: 120,
|
|
16136
|
+
routes: wrapRouteHandlers(createRoutes(), dashboardSecret),
|
|
16137
|
+
fetch(req, server2) {
|
|
16138
|
+
const url = new URL(req.url);
|
|
16139
|
+
if (req.method === "GET" && url.pathname === "/api/events") {
|
|
16140
|
+
if (dashboardSecret) {
|
|
16141
|
+
const token = url.searchParams.get("token")?.trim() ?? getTokenFromRequest(req);
|
|
16142
|
+
if (token !== dashboardSecret) {
|
|
16143
|
+
return authUnauthorizedResponse();
|
|
16144
|
+
}
|
|
16145
|
+
}
|
|
16146
|
+
if (server2.upgrade(req, { data: undefined }))
|
|
16147
|
+
return;
|
|
16148
|
+
return Response.json({ error: "Upgrade failed" }, { status: 500, headers: CORS_HEADERS });
|
|
16149
|
+
}
|
|
16150
|
+
if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
|
|
16151
|
+
const dashboardResponse = serveDashboard(url.pathname);
|
|
16152
|
+
if (dashboardResponse)
|
|
16153
|
+
return dashboardResponse;
|
|
16154
|
+
if (url.pathname === "/" || url.pathname === "") {
|
|
16155
|
+
return Response.json({
|
|
16156
|
+
error: "Dashboard not built",
|
|
16157
|
+
path: DASHBOARD_DIST,
|
|
16158
|
+
hint: "From the sulala package root run: cd dashboard && npm run build",
|
|
16159
|
+
hint_global: "If you installed globally, reinstall to get the dashboard: bun install -g @sulala/agent-os@latest"
|
|
16160
|
+
}, { status: 404, headers: CORS_HEADERS });
|
|
16052
16161
|
}
|
|
16053
16162
|
}
|
|
16054
|
-
|
|
16055
|
-
return;
|
|
16056
|
-
return Response.json({ error: "Upgrade failed" }, { status: 500, headers: CORS_HEADERS });
|
|
16057
|
-
}
|
|
16058
|
-
if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
|
|
16059
|
-
const dashboardResponse = serveDashboard(url.pathname);
|
|
16060
|
-
if (dashboardResponse)
|
|
16061
|
-
return dashboardResponse;
|
|
16062
|
-
if (url.pathname === "/" || url.pathname === "") {
|
|
16063
|
-
return Response.json({
|
|
16064
|
-
error: "Dashboard not built",
|
|
16065
|
-
path: DASHBOARD_DIST,
|
|
16066
|
-
hint: "From the sulala package root run: cd dashboard && npm run build",
|
|
16067
|
-
hint_global: "If you installed globally, reinstall to get the dashboard: bun install -g @sulala/agent-os@latest"
|
|
16068
|
-
}, { status: 404, headers: CORS_HEADERS });
|
|
16069
|
-
}
|
|
16070
|
-
}
|
|
16071
|
-
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
16072
|
-
},
|
|
16073
|
-
error(error) {
|
|
16074
|
-
console.error(error);
|
|
16075
|
-
return Response.json({ error: "Internal Server Error" }, { status: 500, headers: CORS_HEADERS });
|
|
16076
|
-
},
|
|
16077
|
-
websocket: {
|
|
16078
|
-
open(ws) {
|
|
16079
|
-
wsClients.add(ws);
|
|
16163
|
+
return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
|
|
16080
16164
|
},
|
|
16081
|
-
|
|
16082
|
-
|
|
16165
|
+
error(error) {
|
|
16166
|
+
console.error(error);
|
|
16167
|
+
return Response.json({ error: "Internal Server Error" }, { status: 500, headers: CORS_HEADERS });
|
|
16083
16168
|
},
|
|
16084
|
-
|
|
16169
|
+
websocket: {
|
|
16170
|
+
open(ws) {
|
|
16171
|
+
wsClients.add(ws);
|
|
16172
|
+
},
|
|
16173
|
+
close(ws) {
|
|
16174
|
+
wsClients.delete(ws);
|
|
16175
|
+
},
|
|
16176
|
+
message() {}
|
|
16177
|
+
}
|
|
16178
|
+
});
|
|
16179
|
+
} catch (err) {
|
|
16180
|
+
const e = err;
|
|
16181
|
+
if (e?.code === "EADDRINUSE" || e?.errno === 48) {
|
|
16182
|
+
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`);
|
|
16085
16183
|
}
|
|
16086
|
-
|
|
16184
|
+
throw err;
|
|
16185
|
+
}
|
|
16087
16186
|
console.log(`Agent OS server running at ${server.url}`);
|
|
16088
16187
|
startTelegramPolling(memoryStore);
|
|
16089
16188
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sulala/agent-os",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
26
|
"dashboard-dist",
|
|
27
|
-
"data"
|
|
27
|
+
"data/agents",
|
|
28
|
+
"data/skills",
|
|
29
|
+
"data/templates"
|
|
28
30
|
],
|
|
29
31
|
"engines": {
|
|
30
32
|
"bun": ">=1.0.0"
|