@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.
@@ -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-B4RwGkGB.js"></script>
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: String(parsed.model ?? "").trim(),
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 added = await insertSeedAgentFromFile(seedDir, name, true);
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
- const server = Bun.serve({
16105
- port: PORT,
16106
- hostname: HOST,
16107
- idleTimeout: 120,
16108
- routes: wrapRouteHandlers(createRoutes(), dashboardSecret),
16109
- fetch(req, server2) {
16110
- const url = new URL(req.url);
16111
- if (req.method === "GET" && url.pathname === "/api/events") {
16112
- if (dashboardSecret) {
16113
- const token = url.searchParams.get("token")?.trim() ?? getTokenFromRequest(req);
16114
- if (token !== dashboardSecret) {
16115
- return authUnauthorizedResponse();
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 (server2.upgrade(req, { data: undefined }))
16119
- return;
16120
- return Response.json({ error: "Upgrade failed" }, { status: 500, headers: CORS_HEADERS });
16121
- }
16122
- if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
16123
- const dashboardResponse = serveDashboard(url.pathname);
16124
- if (dashboardResponse)
16125
- return dashboardResponse;
16126
- if (url.pathname === "/" || url.pathname === "") {
16127
- return Response.json({
16128
- error: "Dashboard not built",
16129
- path: DASHBOARD_DIST,
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
- close(ws) {
16146
- wsClients.delete(ws);
16239
+ error(error) {
16240
+ console.error(error);
16241
+ return Response.json({ error: "Internal Server Error" }, { status: 500, headers: CORS_HEADERS });
16147
16242
  },
16148
- message() {}
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 = process.env.AGENT_MEMORY_DB_PATH ?? join13(getAgentOsHome(), "database.db");
16197
- mkdirSync(dirname(MEMORY_DB_PATH), { recursive: true });
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 dirname2 } from "path";
16213
- import { readFile as readFile10, writeFile as writeFile6, mkdir as mkdir7, unlink as unlink2 } from "fs/promises";
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 mkdir7(getAgentOsHome(), { recursive: true });
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 mkdir7(getAgentOsHome(), { recursive: true });
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 mkdir7(home, { recursive: true });
16350
- await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
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();