@sulala/agent-os 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/dist/cli.js +439 -456
  2. package/dist/index.js +335 -365
  3. package/package.json +10 -2
package/dist/index.js CHANGED
@@ -80,6 +80,7 @@ async function readConfig() {
80
80
  const signal_default_agent_id = o.signal_default_agent_id;
81
81
  const viber_auth_token = o.viber_auth_token;
82
82
  const viber_default_agent_id = o.viber_default_agent_id;
83
+ const onboarding_completed = o.onboarding_completed;
83
84
  return {
84
85
  provider: provider === "openrouter" || provider === "openai" ? provider : undefined,
85
86
  api_key: typeof api_key === "string" ? api_key : undefined,
@@ -98,7 +99,8 @@ async function readConfig() {
98
99
  signal_bridge_url: typeof signal_bridge_url === "string" ? signal_bridge_url.trim() || undefined : undefined,
99
100
  signal_default_agent_id: typeof signal_default_agent_id === "string" ? signal_default_agent_id : undefined,
100
101
  viber_auth_token: typeof viber_auth_token === "string" ? viber_auth_token : undefined,
101
- viber_default_agent_id: typeof viber_default_agent_id === "string" ? viber_default_agent_id : undefined
102
+ viber_default_agent_id: typeof viber_default_agent_id === "string" ? viber_default_agent_id : undefined,
103
+ onboarding_completed: onboarding_completed === true ? true : undefined
102
104
  };
103
105
  }
104
106
  } catch (err) {
@@ -128,7 +130,8 @@ async function writeConfig(updates) {
128
130
  signal_bridge_url: updates.signal_bridge_url !== undefined ? updates.signal_bridge_url : current.signal_bridge_url,
129
131
  signal_default_agent_id: updates.signal_default_agent_id !== undefined ? updates.signal_default_agent_id : current.signal_default_agent_id,
130
132
  viber_auth_token: updates.viber_auth_token !== undefined ? updates.viber_auth_token : current.viber_auth_token,
131
- viber_default_agent_id: updates.viber_default_agent_id !== undefined ? updates.viber_default_agent_id : current.viber_default_agent_id
133
+ viber_default_agent_id: updates.viber_default_agent_id !== undefined ? updates.viber_default_agent_id : current.viber_default_agent_id,
134
+ onboarding_completed: updates.onboarding_completed !== undefined ? updates.onboarding_completed : current.onboarding_completed
132
135
  };
133
136
  const home = getAgentOsHome();
134
137
  const path = getConfigPath();
@@ -151,7 +154,8 @@ async function writeConfig(updates) {
151
154
  signal_bridge_url: merged.signal_bridge_url ?? null,
152
155
  signal_default_agent_id: merged.signal_default_agent_id ?? null,
153
156
  viber_auth_token: merged.viber_auth_token ?? null,
154
- viber_default_agent_id: merged.viber_default_agent_id ?? null
157
+ viber_default_agent_id: merged.viber_default_agent_id ?? null,
158
+ onboarding_completed: merged.onboarding_completed ?? null
155
159
  }, null, 2), "utf-8");
156
160
  }
157
161
  function getConfigsDir() {
@@ -11049,6 +11053,14 @@ function jsonResponse(data, status = 200) {
11049
11053
  headers: CORS_HEADERS
11050
11054
  });
11051
11055
  }
11056
+ async function parseJsonBody(req) {
11057
+ try {
11058
+ const body = await req.json();
11059
+ return { ok: true, body };
11060
+ } catch {
11061
+ return { ok: false, response: jsonResponse({ error: "Invalid JSON body" }, 400) };
11062
+ }
11063
+ }
11052
11064
  function getConversationHistoryForRun(memoryStore, conversationId, limit = 40) {
11053
11065
  const rows = memoryStore.getHistoryForConversation(conversationId, limit);
11054
11066
  return rows.map((row) => {
@@ -11132,12 +11144,10 @@ async function handleMemoryWrite(req, store) {
11132
11144
  if (req.method !== "POST") {
11133
11145
  return jsonResponse({ error: "Method not allowed" }, 405);
11134
11146
  }
11135
- let body;
11136
- try {
11137
- body = await req.json();
11138
- } catch {
11139
- return jsonResponse({ error: "Invalid JSON body" }, 400);
11140
- }
11147
+ const parsed = await parseJsonBody(req);
11148
+ if (!parsed.ok)
11149
+ return parsed.response;
11150
+ const body = parsed.body;
11141
11151
  const agent_id = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
11142
11152
  const text = typeof body.text === "string" ? body.text.trim() : "";
11143
11153
  const user_id = typeof body.user_id === "string" ? body.user_id.trim() : null;
@@ -11598,8 +11608,8 @@ async function deleteAgent(id) {
11598
11608
  }
11599
11609
 
11600
11610
  // src/core/runtime.ts
11601
- import { readFile as readFile4 } from "fs/promises";
11602
- import { join as join6 } from "path";
11611
+ import { readFile as readFile6 } from "fs/promises";
11612
+ import { join as join8 } from "path";
11603
11613
 
11604
11614
  // src/core/llm.ts
11605
11615
  init_config();
@@ -12091,11 +12101,14 @@ registerTool({
12091
12101
 
12092
12102
  // src/skills/loader.ts
12093
12103
  init_config();
12094
- var import_yaml = __toESM(require_dist(), 1);
12095
- import { readFile as readFile3, readdir as readdir2, cp, mkdir as mkdir3, writeFile as writeFile3, rm } from "fs/promises";
12096
- import { join as join4, resolve as resolve3, basename as basename2 } from "path";
12104
+ import { readFile as readFile5, readdir as readdir3, cp, mkdir as mkdir3, writeFile as writeFile3, rm } from "fs/promises";
12105
+ import { join as join6, resolve as resolve3, basename as basename2 } from "path";
12097
12106
  import { tmpdir } from "os";
12098
- init_config();
12107
+
12108
+ // src/skills/skill-doc.ts
12109
+ var import_yaml = __toESM(require_dist(), 1);
12110
+ import { readFile as readFile3 } from "fs/promises";
12111
+ import { join as join4 } from "path";
12099
12112
  function getRequiredEnvFromDoc(doc) {
12100
12113
  const creds = Array.isArray(doc.credentials) ? doc.credentials : typeof doc.credentials === "string" ? [doc.credentials] : [];
12101
12114
  const baseEnv = doc.base_url_env?.trim() ? [doc.base_url_env.trim()] : [];
@@ -12116,18 +12129,6 @@ async function getRequiredEnvForSkill(skillDir, subEntries, doc) {
12116
12129
  }
12117
12130
  return env;
12118
12131
  }
12119
- async function loadSkillYamlFromFile(path) {
12120
- try {
12121
- const raw = await readFile3(path, "utf-8");
12122
- const parsed = import_yaml.default.parse(raw);
12123
- return parsed;
12124
- } catch (err) {
12125
- if (err.code === "ENOENT")
12126
- return null;
12127
- console.error(`[skills] Failed to read YAML from ${path}:`, err);
12128
- return null;
12129
- }
12130
- }
12131
12132
  function extractYamlBlockFromMarkdown(md) {
12132
12133
  const lines = md.split(`
12133
12134
  `);
@@ -12141,15 +12142,12 @@ function extractYamlBlockFromMarkdown(md) {
12141
12142
  }
12142
12143
  continue;
12143
12144
  }
12144
- if (trimmed.startsWith("```")) {
12145
+ if (trimmed.startsWith("```"))
12145
12146
  break;
12146
- }
12147
12147
  buf.push(line);
12148
12148
  }
12149
- if (buf.length === 0)
12150
- return null;
12151
- return buf.join(`
12152
- `);
12149
+ return buf.length > 0 ? buf.join(`
12150
+ `) : null;
12153
12151
  }
12154
12152
  function extractFrontmatterFromMarkdown(md) {
12155
12153
  const lines = md.split(`
@@ -12162,16 +12160,51 @@ function extractFrontmatterFromMarkdown(md) {
12162
12160
  break;
12163
12161
  buf.push(lines[i]);
12164
12162
  }
12165
- if (buf.length === 0)
12163
+ return buf.length > 0 ? buf.join(`
12164
+ `) : null;
12165
+ }
12166
+ function extractMarkdownBody(md) {
12167
+ const trimmed = md.replace(/\r\n/g, `
12168
+ `).trim();
12169
+ if (!trimmed.startsWith("---"))
12170
+ return trimmed;
12171
+ const afterFirst = trimmed.slice(3);
12172
+ const closeIdx = afterFirst.indexOf(`
12173
+ ---`);
12174
+ if (closeIdx === -1)
12175
+ return trimmed;
12176
+ return afterFirst.slice(closeIdx + 4).trim();
12177
+ }
12178
+ async function loadSkillDocument(dir, entries) {
12179
+ if (!entries.includes("SKILL.md"))
12166
12180
  return null;
12167
- return buf.join(`
12168
- `);
12181
+ try {
12182
+ const raw = await readFile3(join4(dir, "SKILL.md"), "utf-8");
12183
+ const yamlBlock = extractFrontmatterFromMarkdown(raw) || extractYamlBlockFromMarkdown(raw);
12184
+ if (!yamlBlock) {
12185
+ console.warn(`[skills] No YAML frontmatter or block in SKILL.md for ${dir}, skipping.`);
12186
+ return null;
12187
+ }
12188
+ return import_yaml.default.parse(yamlBlock);
12189
+ } catch (err) {
12190
+ console.error(`[skills] Failed to read SKILL.md in ${dir}:`, err);
12191
+ return null;
12192
+ }
12193
+ }
12194
+
12195
+ // src/skills/skill-extract.ts
12196
+ var import_yaml2 = __toESM(require_dist(), 1);
12197
+ import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
12198
+ import { join as join5 } from "path";
12199
+ var JUNK_DIR_NAMES = new Set(["__macosx"]);
12200
+ function isJunkDir(name) {
12201
+ return JUNK_DIR_NAMES.has(name.toLowerCase()) || name.startsWith(".");
12169
12202
  }
12170
12203
  async function deriveSkillIdFromFlatRoot(extractDir, archiveFilename) {
12171
12204
  const entries = await readdir2(extractDir);
12172
12205
  if (entries.includes("_meta.json")) {
12173
12206
  try {
12174
- const raw = await readFile3(join4(extractDir, "_meta.json"), "utf-8");
12207
+ const raw = await readFile4(join5(extractDir, "_meta.json"), "utf-8");
12175
12208
  const meta = JSON.parse(raw);
12176
12209
  if (typeof meta.slug === "string" && meta.slug.length > 0) {
12177
12210
  return meta.slug.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "skill";
@@ -12180,10 +12213,10 @@ async function deriveSkillIdFromFlatRoot(extractDir, archiveFilename) {
12180
12213
  }
12181
12214
  if (entries.includes("SKILL.md")) {
12182
12215
  try {
12183
- const raw = await readFile3(join4(extractDir, "SKILL.md"), "utf-8");
12216
+ const raw = await readFile4(join5(extractDir, "SKILL.md"), "utf-8");
12184
12217
  const yamlBlock = extractFrontmatterFromMarkdown(raw) || extractYamlBlockFromMarkdown(raw);
12185
12218
  if (yamlBlock) {
12186
- const parsed = import_yaml.default.parse(yamlBlock);
12219
+ const parsed = import_yaml2.default.parse(yamlBlock);
12187
12220
  if (typeof parsed.name === "string" && parsed.name.length > 0) {
12188
12221
  return parsed.name.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "skill";
12189
12222
  }
@@ -12193,10 +12226,6 @@ async function deriveSkillIdFromFlatRoot(extractDir, archiveFilename) {
12193
12226
  const stem = archiveFilename.replace(/\.tar\.gz$/i, "").replace(/\.tgz$/i, "").replace(/\.zip$/i, "").replace(/\.tar$/i, "");
12194
12227
  return stem.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "skill";
12195
12228
  }
12196
- var JUNK_DIR_NAMES = new Set(["__macosx"]);
12197
- function isJunkDir(name) {
12198
- return JUNK_DIR_NAMES.has(name.toLowerCase()) || name.startsWith(".");
12199
- }
12200
12229
  async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
12201
12230
  const entries = await readdir2(tmpDir, { withFileTypes: true });
12202
12231
  const rootNames = new Set(entries.map((e) => e.name));
@@ -12204,7 +12233,7 @@ async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
12204
12233
  const dirs = entries.filter((e) => e.isDirectory() && !isJunkDir(e.name));
12205
12234
  const dirsWithSkillDoc = [];
12206
12235
  for (const d of dirs) {
12207
- const sub = await readdir2(join4(tmpDir, d.name)).catch(() => []);
12236
+ const sub = await readdir2(join5(tmpDir, d.name)).catch(() => []);
12208
12237
  const hasSkillMd = sub.includes("SKILL.md");
12209
12238
  if (hasSkillMd || sub.includes("README.md"))
12210
12239
  dirsWithSkillDoc.push({ name: d.name, hasSkillMd });
@@ -12215,12 +12244,11 @@ async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
12215
12244
  }
12216
12245
  if (dirsWithSkillDoc.length >= 1) {
12217
12246
  const preferred = dirsWithSkillDoc.find((d) => d.hasSkillMd) ?? dirsWithSkillDoc[0];
12218
- const dirName = preferred.name;
12219
- return { id: dirName, sourcePath: join4(tmpDir, dirName) };
12247
+ return { id: preferred.name, sourcePath: join5(tmpDir, preferred.name) };
12220
12248
  }
12221
12249
  if (dirs.length === 1) {
12222
12250
  const dirName = dirs[0].name;
12223
- return { id: dirName, sourcePath: join4(tmpDir, dirName) };
12251
+ return { id: dirName, sourcePath: join5(tmpDir, dirName) };
12224
12252
  }
12225
12253
  if (hasSkillDocAtRoot) {
12226
12254
  const id2 = await deriveSkillIdFromFlatRoot(tmpDir, archiveFilename);
@@ -12228,33 +12256,32 @@ async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
12228
12256
  }
12229
12257
  const firstDir = dirs[0];
12230
12258
  if (firstDir) {
12231
- return { id: firstDir.name, sourcePath: join4(tmpDir, firstDir.name) };
12259
+ return { id: firstDir.name, sourcePath: join5(tmpDir, firstDir.name) };
12232
12260
  }
12233
12261
  const id = await deriveSkillIdFromFlatRoot(tmpDir, archiveFilename);
12234
12262
  return { id, sourcePath: tmpDir };
12235
12263
  }
12236
- async function loadSkillDocument(dir, entries) {
12237
- const mdName = "SKILL.md";
12238
- const yamlName = "skill.yaml";
12239
- if (entries.includes(mdName)) {
12264
+ function slugToSkillId(slug) {
12265
+ return slug.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "skill";
12266
+ }
12267
+ function deriveSkillIdFromSkillMdContent(md, filename) {
12268
+ const yamlBlock = extractFrontmatterFromMarkdown(md) || extractYamlBlockFromMarkdown(md);
12269
+ if (yamlBlock) {
12240
12270
  try {
12241
- const raw = await readFile3(join4(dir, mdName), "utf-8");
12242
- const yamlBlock = extractYamlBlockFromMarkdown(raw) || extractFrontmatterFromMarkdown(raw);
12243
- if (!yamlBlock) {
12244
- console.warn(`[skills] No YAML block or frontmatter in ${mdName} for ${dir}, skipping.`);
12245
- return null;
12271
+ const parsed = import_yaml2.default.parse(yamlBlock);
12272
+ if (typeof parsed.name === "string" && parsed.name.trim()) {
12273
+ return parsed.name.trim().replace(/[^a-z0-9_-]/gi, "_").toLowerCase().replace(/^_+|_+$/g, "") || "skill";
12246
12274
  }
12247
- const parsed = import_yaml.default.parse(yamlBlock);
12248
- return parsed;
12249
- } catch (err) {
12250
- console.error(`[skills] Failed to read SKILL.md in ${dir}:`, err);
12251
- }
12275
+ } catch {}
12252
12276
  }
12253
- if (entries.includes(yamlName)) {
12254
- return loadSkillYamlFromFile(join4(dir, yamlName));
12277
+ const stem = filename.replace(/\.(md|markdown)$/i, "").trim();
12278
+ if (stem && stem !== "SKILL") {
12279
+ return stem.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "uploaded_skill";
12255
12280
  }
12256
- return null;
12281
+ return "uploaded_skill";
12257
12282
  }
12283
+
12284
+ // src/skills/skill-tools.ts
12258
12285
  function getSkillBaseUrl() {
12259
12286
  const env = process.env.AGENT_OS_SKILL_BASE_URL?.trim();
12260
12287
  if (env)
@@ -12263,10 +12290,23 @@ function getSkillBaseUrl() {
12263
12290
  const port = process.env.PORT || "3010";
12264
12291
  return `http://${host}:${port}`;
12265
12292
  }
12266
- function httpToolFromDescriptor(skill, dir, desc) {
12293
+ async function getAuthToken(skillId, credentials) {
12294
+ if (!credentials?.length)
12295
+ return null;
12296
+ const { readSkillConfig: readSkillConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
12297
+ const stored = await readSkillConfig2(skillId);
12298
+ for (const envVar of credentials) {
12299
+ const v = stored[envVar]?.trim() ?? process.env[envVar]?.trim();
12300
+ if (v)
12301
+ return v;
12302
+ }
12303
+ return null;
12304
+ }
12305
+ function httpToolFromDescriptor(skill, _dir, desc) {
12267
12306
  const id = desc.id;
12268
12307
  const method = (desc.method || "GET").toUpperCase();
12269
12308
  const path = desc.path || "/";
12309
+ const base = getSkillBaseUrl();
12270
12310
  return {
12271
12311
  id,
12272
12312
  name: `${skill}:${id}`,
@@ -12279,7 +12319,6 @@ function httpToolFromDescriptor(skill, dir, desc) {
12279
12319
  }
12280
12320
  },
12281
12321
  async execute(input) {
12282
- const base = getSkillBaseUrl();
12283
12322
  const url = `${base}${path}`;
12284
12323
  const query = input.query ?? {};
12285
12324
  let body = input.body;
@@ -12297,9 +12336,7 @@ function httpToolFromDescriptor(skill, dir, desc) {
12297
12336
  }
12298
12337
  const res = await fetch(urlObj.toString(), {
12299
12338
  method,
12300
- headers: {
12301
- "Content-Type": "application/json"
12302
- },
12339
+ headers: { "Content-Type": "application/json" },
12303
12340
  body: method === "GET" || method === "HEAD" ? undefined : JSON.stringify(body ?? {})
12304
12341
  });
12305
12342
  const text = await res.text();
@@ -12309,26 +12346,10 @@ function httpToolFromDescriptor(skill, dir, desc) {
12309
12346
  } catch {
12310
12347
  json = text;
12311
12348
  }
12312
- return {
12313
- status: res.status,
12314
- ok: res.ok,
12315
- data: json
12316
- };
12349
+ return { status: res.status, ok: res.ok, data: json };
12317
12350
  }
12318
12351
  };
12319
12352
  }
12320
- async function getAuthToken(skillId, credentials) {
12321
- if (!credentials?.length)
12322
- return null;
12323
- const { readSkillConfig: readSkillConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
12324
- const stored = await readSkillConfig2(skillId);
12325
- for (const envVar of credentials) {
12326
- const v = stored[envVar]?.trim() ?? process.env[envVar]?.trim();
12327
- if (v)
12328
- return v;
12329
- }
12330
- return null;
12331
- }
12332
12353
  function createDocOnlyRequestTool(skillId, apiBase, credentials, authScheme, authLocation, authParam, skillDescription) {
12333
12354
  const base = apiBase.replace(/\/$/, "");
12334
12355
  const desc = skillDescription?.trim() ? `${skillDescription.trim()} Use this tool with method, path, and optional query and body. See skill doc for paths and examples.` : `Call the API for the "${skillId}" skill. Provide method (GET, POST, etc.), path, and optional query (object) and body (object). Use the skill documentation in your context to know which paths and params to use.`;
@@ -12453,11 +12474,16 @@ function createTokenRequestTool(skillId, toolId, baseUrl, description) {
12453
12474
  }
12454
12475
  };
12455
12476
  }
12477
+
12478
+ // src/skills/loader.ts
12479
+ init_config();
12480
+ var SYSTEM_SKILL_IDS = new Set(["memory"]);
12481
+ var DEFAULT_SYSTEM_SKILL_IDS = ["memory", "date", "fetch", "jq", "file-search"];
12456
12482
  async function loadSkill(name) {
12457
- const dir = join4(getSkillsDir(), name);
12483
+ const dir = join6(getSkillsDir(), name);
12458
12484
  let entries;
12459
12485
  try {
12460
- entries = await readdir2(dir);
12486
+ entries = await readdir3(dir);
12461
12487
  } catch (err) {
12462
12488
  if (err.code === "ENOENT")
12463
12489
  return;
@@ -12471,8 +12497,7 @@ async function loadSkill(name) {
12471
12497
  for (const t of skill.tools) {
12472
12498
  if (!t.id)
12473
12499
  continue;
12474
- const tool = httpToolFromDescriptor(name, dir, t);
12475
- registerTool(tool);
12500
+ registerTool(httpToolFromDescriptor(name, dir, t));
12476
12501
  }
12477
12502
  return;
12478
12503
  }
@@ -12491,7 +12516,7 @@ async function loadSkill(name) {
12491
12516
  }
12492
12517
  if (!apiBase && entries.includes("skill.json")) {
12493
12518
  try {
12494
- const raw = await readFile3(join4(dir, "skill.json"), "utf-8");
12519
+ const raw = await readFile5(join6(dir, "skill.json"), "utf-8");
12495
12520
  const meta = JSON.parse(raw);
12496
12521
  if (meta.base_url_env && process.env[meta.base_url_env]?.trim()) {
12497
12522
  apiBase = process.env[meta.base_url_env].trim().replace(/\/$/, "");
@@ -12504,7 +12529,7 @@ async function loadSkill(name) {
12504
12529
  }
12505
12530
  if (!apiBase && entries.includes("SKILL.md")) {
12506
12531
  try {
12507
- const raw = await readFile3(join4(dir, "SKILL.md"), "utf-8");
12532
+ const raw = await readFile5(join6(dir, "SKILL.md"), "utf-8");
12508
12533
  const match = raw.match(/Base\s+URL:\s*[`"]?(\s*https?:\/\/[^\s`"]+)/i) || raw.match(/api_base:\s*["']?([^"'\s]+)/i);
12509
12534
  if (match?.[1])
12510
12535
  apiBase = match[1].trim().replace(/[`"]/g, "");
@@ -12512,8 +12537,7 @@ async function loadSkill(name) {
12512
12537
  }
12513
12538
  if (apiBase) {
12514
12539
  const authScheme = skill.auth_scheme === "Apikey" ? "Apikey" : "Bearer";
12515
- const tool = createDocOnlyRequestTool(name, apiBase, credentials, authScheme, skill.auth_location, skill.auth_param, skill.description);
12516
- registerTool(tool);
12540
+ registerTool(createDocOnlyRequestTool(name, apiBase, credentials, authScheme, skill.auth_location, skill.auth_param, skill.description));
12517
12541
  } else if (baseUrlEnv) {
12518
12542
  console.warn(`[skills] ${name} skill loaded but request tool not registered: set ${baseUrlEnv} so the agent can call the API.`);
12519
12543
  }
@@ -12532,28 +12556,16 @@ async function loadSkillsForAgent(agent) {
12532
12556
  }
12533
12557
  await Promise.all(skills.map((name) => loadSkill(name)));
12534
12558
  }
12535
- function extractMarkdownBody(md) {
12536
- const trimmed = md.replace(/\r\n/g, `
12537
- `).trim();
12538
- if (!trimmed.startsWith("---"))
12539
- return trimmed;
12540
- const afterFirst = trimmed.slice(3);
12541
- const closeIdx = afterFirst.indexOf(`
12542
- ---`);
12543
- if (closeIdx === -1)
12544
- return trimmed;
12545
- return afterFirst.slice(closeIdx + 4).trim();
12546
- }
12547
12559
  async function getSkillDocContext(skillNames) {
12548
12560
  const dir = getSkillsDir();
12549
12561
  const parts = [];
12550
12562
  for (const name of skillNames) {
12551
12563
  if (!name?.trim())
12552
12564
  continue;
12553
- const skillDir = join4(dir, name);
12554
- const mdPath = join4(skillDir, "SKILL.md");
12565
+ const skillDir = join6(dir, name);
12566
+ const mdPath = join6(skillDir, "SKILL.md");
12555
12567
  try {
12556
- const raw = await readFile3(mdPath, "utf-8");
12568
+ const raw = await readFile5(mdPath, "utf-8");
12557
12569
  const body = extractMarkdownBody(raw);
12558
12570
  if (body)
12559
12571
  parts.push(`## Skill: ${name}
@@ -12567,13 +12579,10 @@ ${body}`);
12567
12579
 
12568
12580
  `);
12569
12581
  }
12570
- var SYSTEM_SKILL_IDS = new Set(["memory"]);
12571
- var DEFAULT_SYSTEM_SKILL_IDS = ["memory", "date", "fetch", "jq", "file-search"];
12572
12582
  async function getSkillConfigSchema(skillId) {
12573
- const dir = join4(getSkillsDir(), skillId);
12574
- const path = join4(dir, "config.schema.json");
12583
+ const path = join6(getSkillsDir(), skillId, "config.schema.json");
12575
12584
  try {
12576
- const raw = await readFile3(path, "utf-8");
12585
+ const raw = await readFile5(path, "utf-8");
12577
12586
  const parsed = JSON.parse(raw);
12578
12587
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12579
12588
  return parsed;
@@ -12586,10 +12595,9 @@ async function getSkillConfigSchema(skillId) {
12586
12595
  return null;
12587
12596
  }
12588
12597
  async function getSkillSetupMarkdown(skillId) {
12589
- const dir = join4(getSkillsDir(), skillId);
12590
- const mdPath = join4(dir, "SKILL.md");
12598
+ const mdPath = join6(getSkillsDir(), skillId, "SKILL.md");
12591
12599
  try {
12592
- const raw = await readFile3(mdPath, "utf-8");
12600
+ const raw = await readFile5(mdPath, "utf-8");
12593
12601
  const lines = raw.split(`
12594
12602
  `);
12595
12603
  let inSetup = false;
@@ -12620,22 +12628,19 @@ async function getSkillSetupMarkdown(skillId) {
12620
12628
  }
12621
12629
  async function getStoreRegistry() {
12622
12630
  const registryUrl = process.env.SKILLS_REGISTRY_URL?.trim() ?? null;
12623
- if (!registryUrl) {
12631
+ if (!registryUrl)
12624
12632
  return { skills: [], storeBase: null, registryUrl: null };
12625
- }
12626
12633
  let storeBase = null;
12627
12634
  try {
12628
12635
  storeBase = new URL(registryUrl).origin;
12629
12636
  } catch {}
12630
12637
  try {
12631
12638
  const res = await fetch(registryUrl, { redirect: "follow" });
12632
- if (!res.ok) {
12639
+ if (!res.ok)
12633
12640
  return { skills: [], storeBase, registryUrl };
12634
- }
12635
12641
  const contentType = (res.headers.get("content-type") ?? "").toLowerCase();
12636
- if (!contentType.includes("application/json")) {
12642
+ if (!contentType.includes("application/json"))
12637
12643
  return { skills: [], storeBase, registryUrl };
12638
- }
12639
12644
  const data = await res.json();
12640
12645
  const raw = Array.isArray(data) ? data : Array.isArray(data?.skills) ? data.skills : [];
12641
12646
  const skills = raw.filter((e) => e != null && typeof e === "object").filter((e) => typeof e.slug === "string").map((e) => {
@@ -12662,13 +12667,10 @@ async function getStoreRegistry() {
12662
12667
  }
12663
12668
  async function getSkillMarketplace() {
12664
12669
  const home = getAgentOsHome();
12665
- const paths = [
12666
- join4(home, "skill-marketplace.json"),
12667
- join4(process.cwd(), "data", "skill-marketplace.json")
12668
- ];
12670
+ const paths = [join6(home, "skill-marketplace.json"), join6(process.cwd(), "data", "skill-marketplace.json")];
12669
12671
  for (const path of paths) {
12670
12672
  try {
12671
- const raw = await readFile3(path, "utf-8");
12673
+ const raw = await readFile5(path, "utf-8");
12672
12674
  const parsed = JSON.parse(raw);
12673
12675
  if (Array.isArray(parsed)) {
12674
12676
  return parsed.filter((e) => e && typeof e === "object" && typeof e.id === "string" && typeof e.name === "string");
@@ -12688,15 +12690,13 @@ async function uninstallSkill(skillId) {
12688
12690
  if (SYSTEM_SKILL_IDS.has(skillId)) {
12689
12691
  throw new Error("Cannot uninstall system skill");
12690
12692
  }
12691
- const dir = getSkillsDir();
12692
- const skillDir = join4(dir, skillId);
12693
- await rm(skillDir, { recursive: true, force: true });
12693
+ await rm(join6(getSkillsDir(), skillId), { recursive: true, force: true });
12694
12694
  }
12695
12695
  async function listSkills() {
12696
12696
  const dir = getSkillsDir();
12697
12697
  let entries;
12698
12698
  try {
12699
- entries = await readdir2(dir, { withFileTypes: true });
12699
+ entries = await readdir3(dir, { withFileTypes: true });
12700
12700
  } catch (err) {
12701
12701
  if (err.code === "ENOENT")
12702
12702
  return [];
@@ -12709,10 +12709,10 @@ async function listSkills() {
12709
12709
  const name = e.name;
12710
12710
  if (!name || name.startsWith("."))
12711
12711
  continue;
12712
- const skillDir = join4(dir, name);
12712
+ const skillDir = join6(dir, name);
12713
12713
  let subEntries;
12714
12714
  try {
12715
- subEntries = await readdir2(skillDir);
12715
+ subEntries = await readdir3(skillDir);
12716
12716
  } catch {
12717
12717
  continue;
12718
12718
  }
@@ -12741,16 +12741,13 @@ function getAllowedPathBase() {
12741
12741
  async function installSkillFromPath(sourcePath) {
12742
12742
  const base = getAllowedPathBase();
12743
12743
  const resolved = resolve3(sourcePath);
12744
- if (!resolved.startsWith(base)) {
12744
+ if (!resolved.startsWith(base))
12745
12745
  throw new Error(`Path must be under ${base}`);
12746
- }
12747
12746
  const id = basename2(resolved);
12748
- if (!id || id.startsWith(".")) {
12747
+ if (!id || id.startsWith("."))
12749
12748
  throw new Error("Invalid skill folder name");
12750
- }
12751
- const destDir = join4(getSkillsDir(), id);
12752
12749
  await mkdir3(getSkillsDir(), { recursive: true });
12753
- await cp(resolved, destDir, { recursive: true });
12750
+ await cp(resolved, join6(getSkillsDir(), id), { recursive: true });
12754
12751
  return { id };
12755
12752
  }
12756
12753
  async function installSystemSkills() {
@@ -12764,16 +12761,15 @@ async function installSystemSkills() {
12764
12761
  } catch {
12765
12762
  seedExists = false;
12766
12763
  }
12767
- if (!seedExists) {
12764
+ if (!seedExists)
12768
12765
  throw new Error(`Seed skills directory not found: ${seedDir}`);
12769
- }
12770
12766
  let installed = 0;
12771
12767
  for (const id of DEFAULT_SYSTEM_SKILL_IDS) {
12772
- const sourcePath = join4(seedDir, id);
12773
- const destPath = join4(skillsDir, id);
12768
+ const sourcePath = join6(seedDir, id);
12769
+ const destPath = join6(skillsDir, id);
12774
12770
  let hasSkillMd;
12775
12771
  try {
12776
- await access(join4(sourcePath, "SKILL.md"));
12772
+ await access(join6(sourcePath, "SKILL.md"));
12777
12773
  hasSkillMd = true;
12778
12774
  } catch {
12779
12775
  hasSkillMd = false;
@@ -12782,8 +12778,7 @@ async function installSystemSkills() {
12782
12778
  continue;
12783
12779
  let alreadyInstalled;
12784
12780
  try {
12785
- const st = await stat(destPath);
12786
- alreadyInstalled = st.isDirectory();
12781
+ alreadyInstalled = (await stat(destPath)).isDirectory();
12787
12782
  } catch {
12788
12783
  alreadyInstalled = false;
12789
12784
  }
@@ -12798,35 +12793,25 @@ async function installSystemSkills() {
12798
12793
  }
12799
12794
  return { installed };
12800
12795
  }
12801
- function slugToSkillId(slug) {
12802
- return slug.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "skill";
12803
- }
12804
12796
  async function installSkillFromUrl(url, explicitId) {
12805
12797
  const urlLower = url.toLowerCase();
12806
12798
  const isStoreSkillContentUrl = urlLower.includes("/api/sulalahub/skills/") && !urlLower.includes("/download") && !urlLower.endsWith(".zip");
12807
12799
  const headers = isStoreSkillContentUrl ? { Accept: "application/zip" } : {};
12808
12800
  const res = await fetch(url, { redirect: "follow", headers });
12809
- if (!res.ok) {
12801
+ if (!res.ok)
12810
12802
  throw new Error(`Fetch failed: ${res.status}`);
12811
- }
12812
12803
  const buf = await res.arrayBuffer();
12813
12804
  const contentType = (res.headers.get("content-type") ?? "").toLowerCase();
12814
- const isZipByType = contentType.includes("application/zip");
12815
- const isZipByUrl = urlLower.endsWith(".zip") || urlLower.includes("/download");
12816
- const isZip = isZipByType || isZipByUrl;
12805
+ const isZip = contentType.includes("application/zip") || urlLower.endsWith(".zip") || urlLower.includes("/download");
12817
12806
  const skillsDir = getSkillsDir();
12818
12807
  await mkdir3(skillsDir, { recursive: true });
12819
- const tmpDir = join4(tmpdir(), `agent-os-skill-${Date.now()}`);
12808
+ const tmpDir = join6(tmpdir(), `agent-os-skill-${Date.now()}`);
12820
12809
  await mkdir3(tmpDir, { recursive: true });
12821
12810
  try {
12822
12811
  if (isZip) {
12823
- const zipPath = join4(tmpDir, "archive.zip");
12812
+ const zipPath = join6(tmpDir, "archive.zip");
12824
12813
  await writeFile3(zipPath, new Uint8Array(buf));
12825
- const proc2 = Bun.spawn({
12826
- cmd: ["unzip", "-q", "-o", zipPath, "-d", tmpDir],
12827
- stdout: "ignore",
12828
- stderr: "pipe"
12829
- });
12814
+ const proc2 = Bun.spawn({ cmd: ["unzip", "-q", "-o", zipPath, "-d", tmpDir], stdout: "ignore", stderr: "pipe" });
12830
12815
  const exit2 = await proc2.exited;
12831
12816
  if (exit2 !== 0) {
12832
12817
  const err = await new Response(proc2.stderr).text();
@@ -12834,17 +12819,12 @@ async function installSkillFromUrl(url, explicitId) {
12834
12819
  }
12835
12820
  const { id: id2, sourcePath: sourcePath2 } = await chooseSkillRootFromExtract(tmpDir, "archive.zip");
12836
12821
  const destId2 = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id2;
12837
- const destDir2 = join4(skillsDir, destId2);
12838
- await cp(sourcePath2, destDir2, { recursive: true });
12822
+ await cp(sourcePath2, join6(skillsDir, destId2), { recursive: true });
12839
12823
  return { id: destId2 };
12840
12824
  }
12841
- const tarPath = join4(tmpDir, "archive.tar.gz");
12825
+ const tarPath = join6(tmpDir, "archive.tar.gz");
12842
12826
  await writeFile3(tarPath, new Uint8Array(buf));
12843
- const proc = Bun.spawn({
12844
- cmd: ["tar", "-xzf", tarPath, "-C", tmpDir],
12845
- stdout: "ignore",
12846
- stderr: "pipe"
12847
- });
12827
+ const proc = Bun.spawn({ cmd: ["tar", "-xzf", tarPath, "-C", tmpDir], stdout: "ignore", stderr: "pipe" });
12848
12828
  const exit = await proc.exited;
12849
12829
  if (exit !== 0) {
12850
12830
  const err = await new Response(proc.stderr).text();
@@ -12852,8 +12832,7 @@ async function installSkillFromUrl(url, explicitId) {
12852
12832
  }
12853
12833
  const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, "archive.tar.gz");
12854
12834
  const destId = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id;
12855
- const destDir = join4(skillsDir, destId);
12856
- await cp(sourcePath, destDir, { recursive: true });
12835
+ await cp(sourcePath, join6(skillsDir, destId), { recursive: true });
12857
12836
  return { id: destId };
12858
12837
  } finally {
12859
12838
  await rm(tmpDir, { recursive: true, force: true });
@@ -12862,25 +12841,20 @@ async function installSkillFromUrl(url, explicitId) {
12862
12841
  async function installSkillFromUpload(buffer, filename) {
12863
12842
  const skillsDir = getSkillsDir();
12864
12843
  await mkdir3(skillsDir, { recursive: true });
12865
- const tmpDir = join4(tmpdir(), `agent-os-skill-upload-${Date.now()}`);
12844
+ const tmpDir = join6(tmpdir(), `agent-os-skill-upload-${Date.now()}`);
12866
12845
  await mkdir3(tmpDir, { recursive: true });
12867
12846
  const lower = filename.toLowerCase();
12868
12847
  const isZip = lower.endsWith(".zip");
12869
12848
  const isTarGz = lower.endsWith(".tar.gz") || lower.endsWith(".tgz");
12870
12849
  const isTar = lower.endsWith(".tar");
12871
- if (!isZip && !isTarGz && !isTar) {
12850
+ if (!isZip && !isTarGz && !isTar)
12872
12851
  throw new Error("Upload must be a .tar.gz, .tar, or .zip archive");
12873
- }
12874
12852
  try {
12875
12853
  const ext = isZip ? "archive.zip" : isTar && !isTarGz ? "archive.tar" : "archive.tar.gz";
12876
- const archivePath = join4(tmpDir, ext);
12854
+ const archivePath = join6(tmpDir, ext);
12877
12855
  await writeFile3(archivePath, new Uint8Array(buffer));
12878
12856
  if (isZip) {
12879
- const proc = Bun.spawn({
12880
- cmd: ["unzip", "-q", "-o", archivePath, "-d", tmpDir],
12881
- stdout: "ignore",
12882
- stderr: "pipe"
12883
- });
12857
+ const proc = Bun.spawn({ cmd: ["unzip", "-q", "-o", archivePath, "-d", tmpDir], stdout: "ignore", stderr: "pipe" });
12884
12858
  const exit = await proc.exited;
12885
12859
  if (exit !== 0) {
12886
12860
  const err = await new Response(proc.stderr).text();
@@ -12899,29 +12873,12 @@ async function installSkillFromUpload(buffer, filename) {
12899
12873
  }
12900
12874
  }
12901
12875
  const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, filename);
12902
- const destDir = join4(skillsDir, id);
12903
- await cp(sourcePath, destDir, { recursive: true });
12876
+ await cp(sourcePath, join6(skillsDir, id), { recursive: true });
12904
12877
  return { id };
12905
12878
  } finally {
12906
12879
  await rm(tmpDir, { recursive: true, force: true });
12907
12880
  }
12908
12881
  }
12909
- function deriveSkillIdFromSkillMdContent(md, filename) {
12910
- const yamlBlock = extractFrontmatterFromMarkdown(md) || extractYamlBlockFromMarkdown(md);
12911
- if (yamlBlock) {
12912
- try {
12913
- const parsed = import_yaml.default.parse(yamlBlock);
12914
- if (typeof parsed.name === "string" && parsed.name.trim()) {
12915
- return parsed.name.trim().replace(/[^a-z0-9_-]/gi, "_").toLowerCase().replace(/^_+|_+$/g, "") || "skill";
12916
- }
12917
- } catch {}
12918
- }
12919
- const stem = filename.replace(/\.(md|markdown)$/i, "").trim();
12920
- if (stem && stem !== "SKILL") {
12921
- return stem.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "uploaded_skill";
12922
- }
12923
- return "uploaded_skill";
12924
- }
12925
12882
  async function installSkillFromSkillMd(buffer, filename, explicitId) {
12926
12883
  const skillsDir = getSkillsDir();
12927
12884
  await mkdir3(skillsDir, { recursive: true });
@@ -12929,15 +12886,15 @@ async function installSkillFromSkillMd(buffer, filename, explicitId) {
12929
12886
  const id = explicitId?.trim() ? explicitId.replace(/[^a-z0-9_-]/gi, "_").toLowerCase().replace(/^_+|_+$/g, "") || "uploaded_skill" : deriveSkillIdFromSkillMdContent(text, filename);
12930
12887
  if (!id)
12931
12888
  throw new Error("Invalid skill id");
12932
- const destDir = join4(skillsDir, id);
12889
+ const destDir = join6(skillsDir, id);
12933
12890
  await mkdir3(destDir, { recursive: true });
12934
- await writeFile3(join4(destDir, "SKILL.md"), text, "utf-8");
12891
+ await writeFile3(join6(destDir, "SKILL.md"), text, "utf-8");
12935
12892
  return { id };
12936
12893
  }
12937
12894
 
12938
12895
  // src/core/events.ts
12939
12896
  import { mkdir as mkdir4, appendFile } from "fs/promises";
12940
- import { join as join5 } from "path";
12897
+ import { join as join7 } from "path";
12941
12898
  var subscribers = new Map;
12942
12899
  function registerEventHooks(hooks) {
12943
12900
  for (const [type, handler] of Object.entries(hooks)) {
@@ -12988,8 +12945,8 @@ function publishWithBuffer(type, data) {
12988
12945
  function getRecentEvents() {
12989
12946
  return [...recent];
12990
12947
  }
12991
- var LOG_DIR = process.env.AGENT_OS_LOGS_DIR || join5(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "logs");
12992
- var LOG_FILE = join5(LOG_DIR, "agent-os.log");
12948
+ var LOG_DIR = process.env.AGENT_OS_LOGS_DIR || join7(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "logs");
12949
+ var LOG_FILE = join7(LOG_DIR, "agent-os.log");
12993
12950
  var logInitPromise = null;
12994
12951
  async function ensureLogDir() {
12995
12952
  if (!logInitPromise) {
@@ -13454,7 +13411,7 @@ async function readWorkspacePromptContext(agentId) {
13454
13411
  const chunks = [];
13455
13412
  for (const name of WORKSPACE_PROMPT_FILES) {
13456
13413
  try {
13457
- const content = await readFile4(join6(workspaceDir, name), "utf-8");
13414
+ const content = await readFile6(join8(workspaceDir, name), "utf-8");
13458
13415
  const trimmed = content.trim();
13459
13416
  if (trimmed)
13460
13417
  chunks.push(trimmed);
@@ -13521,12 +13478,10 @@ Use the following documentation to know how to call skill APIs. When you have a
13521
13478
  // src/http/conversations.ts
13522
13479
  async function handleConversations(req, url, store) {
13523
13480
  if (req.method === "POST") {
13524
- let body;
13525
- try {
13526
- body = await req.json();
13527
- } catch {
13528
- return jsonResponse({ error: "Invalid JSON body" }, 400);
13529
- }
13481
+ const parsed = await parseJsonBody(req);
13482
+ if (!parsed.ok)
13483
+ return parsed.response;
13484
+ const body = parsed.body;
13530
13485
  const graph_id = typeof body.graph_id === "string" ? body.graph_id.trim() : null;
13531
13486
  const agent_id = typeof body.agent_id === "string" ? body.agent_id.trim() : graph_id ?? "";
13532
13487
  const role = body.role;
@@ -13620,13 +13575,10 @@ async function handleConversations(req, url, store) {
13620
13575
  async function handleConversationUpdate(req, conversationId, store) {
13621
13576
  if (req.method !== "PUT")
13622
13577
  return jsonResponse({ error: "Method not allowed" }, 405);
13623
- let body;
13624
- try {
13625
- body = await req.json();
13626
- } catch {
13627
- return jsonResponse({ error: "Invalid JSON body" }, 400);
13628
- }
13629
- const title = typeof body.title === "string" ? body.title.trim().slice(0, 200) : "";
13578
+ const parsed = await parseJsonBody(req);
13579
+ if (!parsed.ok)
13580
+ return parsed.response;
13581
+ const title = typeof parsed.body.title === "string" ? parsed.body.title.trim().slice(0, 200) : "";
13630
13582
  try {
13631
13583
  store.updateConversationTitle(conversationId, title || null);
13632
13584
  return jsonResponse({ ok: true });
@@ -13638,13 +13590,10 @@ async function handleConversationUpdate(req, conversationId, store) {
13638
13590
  async function handleConversationSummarize(req, store) {
13639
13591
  if (req.method !== "POST")
13640
13592
  return jsonResponse({ error: "Method not allowed" }, 405);
13641
- let body;
13642
- try {
13643
- body = await req.json();
13644
- } catch {
13645
- return jsonResponse({ error: "Invalid JSON body" }, 400);
13646
- }
13647
- const conversation_id = body.conversation_id?.trim();
13593
+ const parsed = await parseJsonBody(req);
13594
+ if (!parsed.ok)
13595
+ return parsed.response;
13596
+ const conversation_id = parsed.body.conversation_id?.trim();
13648
13597
  if (!conversation_id) {
13649
13598
  return jsonResponse({ error: "conversation_id is required" }, 400);
13650
13599
  }
@@ -13662,9 +13611,9 @@ async function handleConversationSummarize(req, store) {
13662
13611
  let text = row.content;
13663
13612
  if (typeof text === "string" && text.startsWith("{")) {
13664
13613
  try {
13665
- const parsed = JSON.parse(text);
13666
- if (typeof parsed.text === "string")
13667
- text = parsed.text;
13614
+ const parsed2 = JSON.parse(text);
13615
+ if (typeof parsed2.text === "string")
13616
+ text = parsed2.text;
13668
13617
  } catch {}
13669
13618
  }
13670
13619
  const prefix = row.role === "user" ? "User" : "Assistant";
@@ -13701,27 +13650,27 @@ ${transcript}`;
13701
13650
  }
13702
13651
 
13703
13652
  // src/http/handlers.ts
13704
- import { readFile as readFile7, writeFile as writeFile5, stat } from "fs/promises";
13705
- import { join as join9 } from "path";
13653
+ import { readFile as readFile9, writeFile as writeFile5, stat } from "fs/promises";
13654
+ import { join as join11 } from "path";
13706
13655
 
13707
13656
  // src/core/tasks.ts
13708
13657
  var import_node_cron = __toESM(require_node_cron(), 1);
13709
13658
  import { randomUUID } from "crypto";
13710
13659
  import os from "os";
13711
- import { readFile as readFile6, appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
13712
- import { join as join8 } from "path";
13660
+ import { readFile as readFile8, appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
13661
+ import { join as join10 } from "path";
13713
13662
 
13714
13663
  // src/core/graphs.ts
13715
- import { readFile as readFile5, readdir as readdir3, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
13716
- import { join as join7 } from "path";
13717
- var DEFAULT_GRAPHS_DIR = join7(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "graphs");
13664
+ import { readFile as readFile7, readdir as readdir4, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
13665
+ import { join as join9 } from "path";
13666
+ var DEFAULT_GRAPHS_DIR = join9(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "graphs");
13718
13667
  function getGraphsDir() {
13719
13668
  return process.env.AGENT_OS_GRAPHS_DIR || DEFAULT_GRAPHS_DIR;
13720
13669
  }
13721
13670
  async function listGraphs() {
13722
13671
  const dir = getGraphsDir();
13723
13672
  try {
13724
- const entries = await readdir3(dir);
13673
+ const entries = await readdir4(dir);
13725
13674
  const summaries = [];
13726
13675
  for (const name of entries) {
13727
13676
  if (name.endsWith(".json")) {
@@ -13740,11 +13689,11 @@ async function listGraphs() {
13740
13689
  async function loadGraph(id) {
13741
13690
  const dir = getGraphsDir();
13742
13691
  try {
13743
- const entries = await readdir3(dir);
13692
+ const entries = await readdir4(dir);
13744
13693
  const file = entries.find((name) => name === `${id}.json` || name === `${id}.graph.json`);
13745
13694
  if (!file)
13746
13695
  return null;
13747
- const raw = await readFile5(join7(dir, file), "utf-8");
13696
+ const raw = await readFile7(join9(dir, file), "utf-8");
13748
13697
  const parsed = JSON.parse(raw);
13749
13698
  validateGraph(parsed);
13750
13699
  return parsed;
@@ -13759,7 +13708,7 @@ async function saveGraph(graph) {
13759
13708
  validateGraph(graph);
13760
13709
  const dir = getGraphsDir();
13761
13710
  await mkdir5(dir, { recursive: true });
13762
- const path = join7(dir, `${graph.id}.json`);
13711
+ const path = join9(dir, `${graph.id}.json`);
13763
13712
  await writeFile4(path, JSON.stringify(graph, null, 2), "utf-8");
13764
13713
  }
13765
13714
  function validateGraph(graph) {
@@ -13935,8 +13884,8 @@ async function runGraphStream(options, onEvent) {
13935
13884
  function nowIso() {
13936
13885
  return new Date().toISOString();
13937
13886
  }
13938
- var TASKS_DIR = process.env.AGENT_OS_TASKS_DIR || join8(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "tasks");
13939
- var TASKS_FILE = join8(TASKS_DIR, "tasks.log");
13887
+ var TASKS_DIR = process.env.AGENT_OS_TASKS_DIR || join10(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "tasks");
13888
+ var TASKS_FILE = join10(TASKS_DIR, "tasks.log");
13940
13889
  var tasksInitPromise = null;
13941
13890
  async function ensureTasksDir() {
13942
13891
  if (!tasksInitPromise) {
@@ -13996,7 +13945,7 @@ class MemoryTaskStore {
13996
13945
  }
13997
13946
  async loadFromDisk() {
13998
13947
  try {
13999
- const raw = await readFile6(TASKS_FILE, "utf-8");
13948
+ const raw = await readFile8(TASKS_FILE, "utf-8");
14000
13949
  const lines = raw.split(`
14001
13950
  `);
14002
13951
  for (const line of lines) {
@@ -14179,13 +14128,10 @@ function startScheduler(store = memoryTaskStore) {
14179
14128
  // src/http/handlers.ts
14180
14129
  init_config();
14181
14130
  async function handleRun(req, memoryStore) {
14182
- let body;
14183
- try {
14184
- body = await req.json();
14185
- } catch {
14186
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14187
- }
14188
- const { agent_id, task, conversation_id } = body;
14131
+ const parsed = await parseJsonBody(req);
14132
+ if (!parsed.ok)
14133
+ return parsed.response;
14134
+ const { agent_id, task, conversation_id } = parsed.body;
14189
14135
  if (!agent_id || typeof task !== "string") {
14190
14136
  return jsonResponse({ error: "Missing required fields: agent_id, task" }, 400);
14191
14137
  }
@@ -14215,13 +14161,10 @@ var SSE_HEADERS = {
14215
14161
  Connection: "keep-alive"
14216
14162
  };
14217
14163
  async function handleRunStream(req, memoryStore) {
14218
- let body;
14219
- try {
14220
- body = await req.json();
14221
- } catch {
14222
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14223
- }
14224
- const { agent_id, task, conversation_id, attachment_paths } = body;
14164
+ const parsed = await parseJsonBody(req);
14165
+ if (!parsed.ok)
14166
+ return parsed.response;
14167
+ const { agent_id, task, conversation_id, attachment_paths } = parsed.body;
14225
14168
  if (!agent_id || typeof task !== "string") {
14226
14169
  return jsonResponse({ error: "Missing required fields: agent_id, task" }, 400);
14227
14170
  }
@@ -14308,13 +14251,10 @@ async function handleTasks(req, url) {
14308
14251
  return jsonResponse({ tasks });
14309
14252
  }
14310
14253
  if (req.method === "POST") {
14311
- let body;
14312
- try {
14313
- body = await req.json();
14314
- } catch {
14315
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14316
- }
14317
- const { agent_id, graph_id, task } = body;
14254
+ const parsed = await parseJsonBody(req);
14255
+ if (!parsed.ok)
14256
+ return parsed.response;
14257
+ const { agent_id, graph_id, task } = parsed.body;
14318
14258
  if (typeof task !== "string") {
14319
14259
  return jsonResponse({ error: "Missing required field: task" }, 400);
14320
14260
  }
@@ -14338,12 +14278,10 @@ async function handleLogs(req) {
14338
14278
  return jsonResponse({ events });
14339
14279
  }
14340
14280
  async function handleSkillInstall(req) {
14341
- let body;
14342
- try {
14343
- body = await req.json();
14344
- } catch {
14345
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14346
- }
14281
+ const parsed = await parseJsonBody(req);
14282
+ if (!parsed.ok)
14283
+ return parsed.response;
14284
+ const body = parsed.body;
14347
14285
  const hasPath = typeof body.path === "string" && body.path.trim().length > 0;
14348
14286
  const hasUrl = typeof body.url === "string" && body.url.trim().length > 0;
14349
14287
  if (hasPath && hasUrl) {
@@ -14435,16 +14373,16 @@ async function handleSettings(req) {
14435
14373
  signal_default_agent_id: config.signal_default_agent_id ?? null,
14436
14374
  signal_bridge_url: config.signal_bridge_url ?? null,
14437
14375
  viber_configured: Boolean(config.viber_auth_token?.trim()),
14438
- viber_default_agent_id: config.viber_default_agent_id ?? null
14376
+ viber_default_agent_id: config.viber_default_agent_id ?? null,
14377
+ onboarding_completed: config.onboarding_completed === true
14439
14378
  });
14440
14379
  }
14441
14380
  if (req.method === "PUT") {
14442
14381
  let body;
14443
- try {
14444
- body = await req.json();
14445
- } catch {
14446
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14447
- }
14382
+ const parsed = await parseJsonBody(req);
14383
+ if (!parsed.ok)
14384
+ return parsed.response;
14385
+ body = parsed.body;
14448
14386
  const current = await readConfig();
14449
14387
  const provider = body.provider === "openrouter" || body.provider === "openai" ? body.provider : undefined;
14450
14388
  const rawKey = typeof body.api_key === "string" ? body.api_key.trim() : undefined;
@@ -14465,6 +14403,7 @@ async function handleSettings(req) {
14465
14403
  const signal_default_agent_id = body.signal_default_agent_id !== undefined ? typeof body.signal_default_agent_id === "string" ? body.signal_default_agent_id.trim() || undefined : undefined : current.signal_default_agent_id;
14466
14404
  const viber_auth_token = body.viber_auth_token !== undefined ? typeof body.viber_auth_token === "string" ? body.viber_auth_token.trim() || undefined : undefined : current.viber_auth_token;
14467
14405
  const viber_default_agent_id = body.viber_default_agent_id !== undefined ? typeof body.viber_default_agent_id === "string" ? body.viber_default_agent_id.trim() || undefined : undefined : current.viber_default_agent_id;
14406
+ const onboarding_completed = body.onboarding_completed !== undefined ? body.onboarding_completed === true ? true : undefined : current.onboarding_completed;
14468
14407
  await writeConfig({
14469
14408
  provider: provider ?? current.provider,
14470
14409
  api_key: api_key || undefined,
@@ -14483,7 +14422,8 @@ async function handleSettings(req) {
14483
14422
  signal_bridge_url: signal_bridge_url || undefined,
14484
14423
  signal_default_agent_id: signal_default_agent_id || undefined,
14485
14424
  viber_auth_token: viber_auth_token || undefined,
14486
- viber_default_agent_id: viber_default_agent_id || undefined
14425
+ viber_default_agent_id: viber_default_agent_id || undefined,
14426
+ onboarding_completed: onboarding_completed ?? undefined
14487
14427
  });
14488
14428
  return jsonResponse({ ok: true });
14489
14429
  }
@@ -14493,13 +14433,10 @@ async function handleGraphRun(req) {
14493
14433
  if (req.method !== "POST") {
14494
14434
  return jsonResponse({ error: "Method not allowed" }, 405);
14495
14435
  }
14496
- let body;
14497
- try {
14498
- body = await req.json();
14499
- } catch {
14500
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14501
- }
14502
- const { graph_id, input } = body;
14436
+ const parsed = await parseJsonBody(req);
14437
+ if (!parsed.ok)
14438
+ return parsed.response;
14439
+ const { graph_id, input } = parsed.body;
14503
14440
  if (!graph_id || typeof input !== "string") {
14504
14441
  return jsonResponse({ error: "Missing required fields: graph_id, input" }, 400);
14505
14442
  }
@@ -14516,13 +14453,10 @@ async function handleGraphRun(req) {
14516
14453
  }
14517
14454
  }
14518
14455
  async function handleGraphRunStream(req) {
14519
- let body;
14520
- try {
14521
- body = await req.json();
14522
- } catch {
14523
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14524
- }
14525
- const { graph_id, input } = body;
14456
+ const parsed = await parseJsonBody(req);
14457
+ if (!parsed.ok)
14458
+ return parsed.response;
14459
+ const { graph_id, input } = parsed.body;
14526
14460
  if (!graph_id || typeof input !== "string") {
14527
14461
  return jsonResponse({ error: "Missing required fields: graph_id, input" }, 400);
14528
14462
  }
@@ -14577,13 +14511,10 @@ function extractJsonFromContent(content) {
14577
14511
  return trimmed;
14578
14512
  }
14579
14513
  async function handleAgentSuggest(req) {
14580
- let body;
14581
- try {
14582
- body = await req.json();
14583
- } catch {
14584
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14585
- }
14586
- const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
14514
+ const parsed = await parseJsonBody(req);
14515
+ if (!parsed.ok)
14516
+ return parsed.response;
14517
+ const prompt = typeof parsed.body.prompt === "string" ? parsed.body.prompt.trim() : "";
14587
14518
  if (!prompt)
14588
14519
  return jsonResponse({ error: "Missing prompt" }, 400);
14589
14520
  const skills = await listSkills();
@@ -14624,13 +14555,13 @@ Rules:
14624
14555
  if (!content)
14625
14556
  return jsonResponse({ error: "No suggestion from model" }, 502);
14626
14557
  const raw = extractJsonFromContent(content);
14627
- const parsed = JSON.parse(raw);
14628
- const name = typeof parsed.name === "string" ? parsed.name.trim() : "Agent";
14629
- const id = typeof parsed.id === "string" ? parsed.id.trim().toLowerCase().replace(/[^a-z0-9_.-]/g, "_").replace(/\s+/g, "_") || "agent" : "agent";
14630
- const description = typeof parsed.description === "string" ? parsed.description.trim() : "";
14631
- const skillsOut = Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && skillIds.includes(s)) : [];
14632
- const schedule = typeof parsed.schedule === "string" ? parsed.schedule.trim() : "";
14633
- const schedule_input = typeof parsed.schedule_input === "string" ? parsed.schedule_input.trim() : "";
14558
+ const parsed2 = JSON.parse(raw);
14559
+ const name = typeof parsed2.name === "string" ? parsed2.name.trim() : "Agent";
14560
+ const id = typeof parsed2.id === "string" ? parsed2.id.trim().toLowerCase().replace(/[^a-z0-9_.-]/g, "_").replace(/\s+/g, "_") || "agent" : "agent";
14561
+ const description = typeof parsed2.description === "string" ? parsed2.description.trim() : "";
14562
+ const skillsOut = Array.isArray(parsed2.skills) ? parsed2.skills.filter((s) => typeof s === "string" && skillIds.includes(s)) : [];
14563
+ const schedule = typeof parsed2.schedule === "string" ? parsed2.schedule.trim() : "";
14564
+ const schedule_input = typeof parsed2.schedule_input === "string" ? parsed2.schedule_input.trim() : "";
14634
14565
  const suggestion = {
14635
14566
  name,
14636
14567
  id,
@@ -14665,10 +14596,10 @@ async function handleAgentUpload(req, agentId) {
14665
14596
  const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
14666
14597
  const filename = `${unique}_${basename3}`;
14667
14598
  await ensureWorkspace(agentId);
14668
- const uploadsDir = join9(getWorkspaceDir(agentId), "uploads");
14599
+ const uploadsDir = join11(getWorkspaceDir(agentId), "uploads");
14669
14600
  const { mkdir: mkdir7 } = await import("fs/promises");
14670
14601
  await mkdir7(uploadsDir, { recursive: true });
14671
- const absolutePath = join9(uploadsDir, filename);
14602
+ const absolutePath = join11(uploadsDir, filename);
14672
14603
  const buffer = Buffer.from(await file.arrayBuffer());
14673
14604
  await writeFile5(absolutePath, buffer);
14674
14605
  return jsonResponse({ path: absolutePath });
@@ -14709,7 +14640,7 @@ async function handleGetAgentWorkspaceFile(agentId, filename) {
14709
14640
  const msg = errorMessage(err);
14710
14641
  return jsonResponse({ error: msg }, 500);
14711
14642
  }
14712
- const buf = await readFile7(absolutePath);
14643
+ const buf = await readFile9(absolutePath);
14713
14644
  const ext = basename3.replace(/^.*\./, "").toLowerCase();
14714
14645
  const mime = {
14715
14646
  png: "image/png",
@@ -14786,8 +14717,8 @@ async function handleGetDoc(name) {
14786
14717
  return jsonResponse({ error: "Unknown doc" }, 404);
14787
14718
  }
14788
14719
  try {
14789
- const path = join9(process.cwd(), "doc", filename);
14790
- const content = await readFile7(path, "utf-8");
14720
+ const path = join11(process.cwd(), "doc", filename);
14721
+ const content = await readFile9(path, "utf-8");
14791
14722
  return jsonResponse({ content });
14792
14723
  } catch (err) {
14793
14724
  const code = err?.code;
@@ -14802,6 +14733,15 @@ async function handleGetDoc(name) {
14802
14733
  init_config();
14803
14734
 
14804
14735
  // src/http/channel-run.ts
14736
+ async function getDefaultAgent(config, defaultAgentIdKey) {
14737
+ const agents = await loadAgents();
14738
+ const id = config[defaultAgentIdKey];
14739
+ const defaultAgentId = typeof id === "string" ? id.trim() : "";
14740
+ if (defaultAgentId) {
14741
+ return agents.find((a) => a.id === defaultAgentId) ?? null;
14742
+ }
14743
+ return agents[0] ?? null;
14744
+ }
14805
14745
  async function runAgentWithConversation(memoryStore, agent, conversationId, userId, task, sendReply) {
14806
14746
  memoryStore.ensureConversation(conversationId, agent.id, userId);
14807
14747
  const conversationHistory = getConversationHistoryForRun(memoryStore, conversationId);
@@ -14829,13 +14769,10 @@ async function handleTelegramSetWebhook(req) {
14829
14769
  if (req.method !== "POST") {
14830
14770
  return jsonResponse({ error: "Method not allowed" }, 405);
14831
14771
  }
14832
- let body;
14833
- try {
14834
- body = await req.json();
14835
- } catch {
14836
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14837
- }
14838
- const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : "";
14772
+ const parsed = await parseJsonBody(req);
14773
+ if (!parsed.ok)
14774
+ return parsed.response;
14775
+ const baseUrl = typeof parsed.body.base_url === "string" ? parsed.body.base_url.trim() : "";
14839
14776
  if (!baseUrl) {
14840
14777
  return jsonResponse({ error: "base_url is required" }, 400);
14841
14778
  }
@@ -14911,11 +14848,9 @@ async function processTelegramUpdate(memoryStore, body) {
14911
14848
  const botToken = config.telegram_bot_token?.trim();
14912
14849
  if (!botToken)
14913
14850
  return;
14914
- const agents = await loadAgents();
14915
- const defaultAgentId = config.telegram_default_agent_id?.trim();
14916
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
14851
+ const agent = await getDefaultAgent(config, "telegram_default_agent_id");
14917
14852
  if (!agent) {
14918
- await sendTelegramMessage(botToken, chatId, "No agent configured. Add an agent and set Telegram default agent in Settings.");
14853
+ await sendTelegramMessage(botToken, chatId, "No agent configured. Set default agent in Settings.");
14919
14854
  return;
14920
14855
  }
14921
14856
  if (!text) {
@@ -14929,12 +14864,10 @@ async function handleTelegramWebhook(req, memoryStore) {
14929
14864
  if (req.method !== "POST") {
14930
14865
  return jsonResponse({ error: "Method not allowed" }, 405);
14931
14866
  }
14932
- let body;
14933
- try {
14934
- body = await req.json();
14935
- } catch {
14936
- return jsonResponse({ error: "Invalid JSON body" }, 400);
14937
- }
14867
+ const parsed = await parseJsonBody(req);
14868
+ if (!parsed.ok)
14869
+ return parsed.response;
14870
+ const body = parsed.body;
14938
14871
  const message = body.message;
14939
14872
  if (!message?.chat?.id) {
14940
14873
  return jsonResponse({ ok: true });
@@ -15057,11 +14990,9 @@ async function processSlackEvent(memoryStore, payload) {
15057
14990
  const botToken = config.slack_bot_token?.trim();
15058
14991
  if (!botToken)
15059
14992
  return;
15060
- const agents = await loadAgents();
15061
- const defaultAgentId = config.slack_default_agent_id?.trim();
15062
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
14993
+ const agent = await getDefaultAgent(config, "slack_default_agent_id");
15063
14994
  if (!agent) {
15064
- await sendSlackMessage(botToken, event.channel, "No agent configured. Set Slack default agent in Settings.");
14995
+ await sendSlackMessage(botToken, event.channel, "No agent configured. Set default agent in Settings.");
15065
14996
  return;
15066
14997
  }
15067
14998
  const userId = event.user ?? "unknown";
@@ -15172,11 +15103,9 @@ async function processDiscordInteraction(memoryStore, interaction) {
15172
15103
  const botToken = config.discord_bot_token?.trim();
15173
15104
  if (!botToken)
15174
15105
  return;
15175
- const agents = await loadAgents();
15176
- const defaultAgentId = config.discord_default_agent_id?.trim();
15177
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
15106
+ const agent = await getDefaultAgent(config, "discord_default_agent_id");
15178
15107
  if (!agent) {
15179
- await sendDiscordFollowup(appId, token, botToken, "No agent configured. Set Discord default agent in Settings.");
15108
+ await sendDiscordFollowup(appId, token, botToken, "No agent configured. Set default agent in Settings.");
15180
15109
  return;
15181
15110
  }
15182
15111
  const conversationId = `discord:${channelId}:${userId}`;
@@ -15258,11 +15187,9 @@ async function processSignalWebhook(memoryStore, body) {
15258
15187
  const bridgeUrl = config.signal_bridge_url?.trim();
15259
15188
  if (!bridgeUrl)
15260
15189
  return;
15261
- const agents = await loadAgents();
15262
- const defaultAgentId = config.signal_default_agent_id?.trim();
15263
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
15190
+ const agent = await getDefaultAgent(config, "signal_default_agent_id");
15264
15191
  if (!agent) {
15265
- await sendSignalMessage(bridgeUrl, from, "No agent configured. Set Signal default agent in Settings.");
15192
+ await sendSignalMessage(bridgeUrl, from, "No agent configured. Set default agent in Settings.");
15266
15193
  return;
15267
15194
  }
15268
15195
  if (!text) {
@@ -15281,12 +15208,10 @@ async function handleSignalWebhook(req, memoryStore) {
15281
15208
  if (req.method !== "POST") {
15282
15209
  return jsonResponse({ error: "Method not allowed" }, 405);
15283
15210
  }
15284
- let body;
15285
- try {
15286
- body = await req.json();
15287
- } catch {
15288
- return jsonResponse({ error: "Invalid JSON body" }, 400);
15289
- }
15211
+ const parsed = await parseJsonBody(req);
15212
+ if (!parsed.ok)
15213
+ return parsed.response;
15214
+ const body = parsed.body;
15290
15215
  const config = await readConfig();
15291
15216
  if (!config.signal_bridge_url?.trim()) {
15292
15217
  console.warn("[signal] Webhook received but signal_bridge_url not configured");
@@ -15324,13 +15249,10 @@ async function handleViberSetWebhook(req) {
15324
15249
  if (req.method !== "POST") {
15325
15250
  return jsonResponse({ error: "Method not allowed" }, 405);
15326
15251
  }
15327
- let body;
15328
- try {
15329
- body = await req.json();
15330
- } catch {
15331
- return jsonResponse({ error: "Invalid JSON body" }, 400);
15332
- }
15333
- const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : "";
15252
+ const parsed = await parseJsonBody(req);
15253
+ if (!parsed.ok)
15254
+ return parsed.response;
15255
+ const baseUrl = typeof parsed.body.base_url === "string" ? parsed.body.base_url.trim() : "";
15334
15256
  if (!baseUrl) {
15335
15257
  return jsonResponse({ error: "base_url is required" }, 400);
15336
15258
  }
@@ -15431,11 +15353,9 @@ async function processViberCallback(memoryStore, payload) {
15431
15353
  const authToken = config.viber_auth_token?.trim();
15432
15354
  if (!authToken)
15433
15355
  return;
15434
- const agents = await loadAgents();
15435
- const defaultAgentId = config.viber_default_agent_id?.trim();
15436
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
15356
+ const agent = await getDefaultAgent(config, "viber_default_agent_id");
15437
15357
  if (!agent) {
15438
- await sendViberMessage(authToken, senderId, "No agent configured. Set Viber default agent in Settings.");
15358
+ await sendViberMessage(authToken, senderId, "No agent configured. Set default agent in Settings.");
15439
15359
  return;
15440
15360
  }
15441
15361
  const conversationId = `viber:${senderId}`;
@@ -15484,10 +15404,10 @@ async function handleViberWebhook(req, memoryStore) {
15484
15404
  init_config();
15485
15405
 
15486
15406
  // src/core/plugins.ts
15487
- import { readdir as readdir4, stat as stat2 } from "fs/promises";
15488
- import { join as join10 } from "path";
15407
+ import { readdir as readdir5, stat as stat2 } from "fs/promises";
15408
+ import { join as join12 } from "path";
15489
15409
  init_config();
15490
- var DEFAULT_PLUGINS_DIR = join10(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "plugins");
15410
+ var DEFAULT_PLUGINS_DIR = join12(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "plugins");
15491
15411
  function getPluginsDir() {
15492
15412
  return process.env.AGENT_OS_PLUGINS_DIR || DEFAULT_PLUGINS_DIR;
15493
15413
  }
@@ -15501,7 +15421,7 @@ async function loadPlugins() {
15501
15421
  const dir = getPluginsDir();
15502
15422
  let entries = [];
15503
15423
  try {
15504
- entries = await readdir4(dir);
15424
+ entries = await readdir5(dir);
15505
15425
  } catch (err) {
15506
15426
  if (err?.code === "ENOENT")
15507
15427
  return;
@@ -15512,12 +15432,12 @@ async function loadPlugins() {
15512
15432
  for (const name of entries) {
15513
15433
  if (name.startsWith("."))
15514
15434
  continue;
15515
- const pluginPath = join10(dir, name);
15435
+ const pluginPath = join12(dir, name);
15516
15436
  let modulePath;
15517
15437
  try {
15518
15438
  const st = await stat2(pluginPath);
15519
15439
  if (st.isDirectory()) {
15520
- modulePath = join10(pluginPath, "index.js");
15440
+ modulePath = join12(pluginPath, "index.js");
15521
15441
  } else if (name.endsWith(".js") || name.endsWith(".ts")) {
15522
15442
  modulePath = pluginPath;
15523
15443
  } else {
@@ -15539,9 +15459,10 @@ async function loadPlugins() {
15539
15459
 
15540
15460
  // src/server.ts
15541
15461
  init_config();
15542
- import { join as join11, dirname } from "path";
15543
- import { mkdirSync } from "fs";
15462
+ import { join as join13, dirname, resolve as resolve4 } from "path";
15463
+ import { mkdirSync, existsSync } from "fs";
15544
15464
  var PORT = parseInt(process.env.PORT ?? "3010", 10);
15465
+ var DASHBOARD_DIST = resolve4(join13(import.meta.dir, "..", "dashboard", "dist"));
15545
15466
  var HOST = process.env.HOST ?? "127.0.0.1";
15546
15467
  var EVENT_TYPES = [
15547
15468
  "task.created",
@@ -15554,7 +15475,7 @@ var EVENT_TYPES = [
15554
15475
  "tool.completed"
15555
15476
  ];
15556
15477
  var wsClients = new Set;
15557
- var MEMORY_DB_PATH = process.env.AGENT_MEMORY_DB_PATH ?? join11(getAgentOsHome(), "database.db");
15478
+ var MEMORY_DB_PATH = process.env.AGENT_MEMORY_DB_PATH ?? join13(getAgentOsHome(), "database.db");
15558
15479
  mkdirSync(dirname(MEMORY_DB_PATH), { recursive: true });
15559
15480
  var memoryStore = new MemoryStore(MEMORY_DB_PATH);
15560
15481
  setAgentStore(memoryStore);
@@ -15566,6 +15487,44 @@ function broadcastEvent(event) {
15566
15487
  } catch {}
15567
15488
  }
15568
15489
  }
15490
+ function serveDashboard(pathname) {
15491
+ if (!existsSync(DASHBOARD_DIST) || !existsSync(join13(DASHBOARD_DIST, "index.html"))) {
15492
+ return null;
15493
+ }
15494
+ const decoded = decodeURIComponent(pathname);
15495
+ if (decoded.includes("..")) {
15496
+ return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
15497
+ }
15498
+ const subpath = decoded === "/" ? "index.html" : decoded.slice(1);
15499
+ const filePath = join13(DASHBOARD_DIST, subpath);
15500
+ if (existsSync(filePath)) {
15501
+ const file = Bun.file(filePath);
15502
+ const ext = subpath.split(".").pop() ?? "";
15503
+ const mime = {
15504
+ html: "text/html",
15505
+ js: "application/javascript",
15506
+ css: "text/css",
15507
+ json: "application/json",
15508
+ png: "image/png",
15509
+ jpg: "image/jpeg",
15510
+ jpeg: "image/jpeg",
15511
+ ico: "image/x-icon",
15512
+ svg: "image/svg+xml",
15513
+ woff: "font/woff",
15514
+ woff2: "font/woff2"
15515
+ };
15516
+ return new Response(file, {
15517
+ headers: { "Content-Type": mime[ext] ?? "application/octet-stream" }
15518
+ });
15519
+ }
15520
+ const indexPath = join13(DASHBOARD_DIST, "index.html");
15521
+ if (existsSync(indexPath)) {
15522
+ return new Response(Bun.file(indexPath), {
15523
+ headers: { "Content-Type": "text/html" }
15524
+ });
15525
+ }
15526
+ return null;
15527
+ }
15569
15528
  for (const type of EVENT_TYPES) {
15570
15529
  subscribe(type, broadcastEvent);
15571
15530
  }
@@ -15941,6 +15900,17 @@ async function startServer() {
15941
15900
  return;
15942
15901
  return Response.json({ error: "Upgrade failed" }, { status: 500, headers: CORS_HEADERS });
15943
15902
  }
15903
+ if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
15904
+ const dashboardResponse = serveDashboard(url.pathname);
15905
+ if (dashboardResponse)
15906
+ return dashboardResponse;
15907
+ if (url.pathname === "/" || url.pathname === "") {
15908
+ return Response.json({
15909
+ error: "Dashboard not built",
15910
+ hint: "From the sulala package root run: cd dashboard && npm run build"
15911
+ }, { status: 404, headers: CORS_HEADERS });
15912
+ }
15913
+ }
15944
15914
  return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
15945
15915
  },
15946
15916
  error(error) {