@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/cli.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() {
@@ -850,6 +854,66 @@ var init_agent_registry = __esm(() => {
850
854
  ];
851
855
  });
852
856
 
857
+ // src/core/tool-registry.ts
858
+ function registerTool(tool) {
859
+ registry.set(tool.id, tool);
860
+ }
861
+ function unregisterTool(id) {
862
+ return registry.delete(id);
863
+ }
864
+ function getTool(id) {
865
+ return registry.get(id);
866
+ }
867
+ function getAllTools() {
868
+ return [...registry.values()];
869
+ }
870
+ function getSkillIdFromToolId(toolId) {
871
+ if (toolId.endsWith("_request"))
872
+ return toolId.replace(/_request$/, "");
873
+ const idx = toolId.indexOf(":");
874
+ if (idx > 0)
875
+ return toolId.slice(0, idx);
876
+ return null;
877
+ }
878
+ function unregisterSkillTools() {
879
+ const all = getAllTools();
880
+ for (const t of all) {
881
+ if (getSkillIdFromToolId(t.id) !== null)
882
+ unregisterTool(t.id);
883
+ }
884
+ }
885
+ function getToolsForAgent(agent) {
886
+ const all = getAllTools();
887
+ const allowlist = agent.tools;
888
+ if (allowlist?.length) {
889
+ const set = new Set(allowlist);
890
+ return all.filter((t) => set.has(t.id));
891
+ }
892
+ const skills = agent.skills;
893
+ if (skills?.length && !skills.includes("*")) {
894
+ const skillSet = new Set(skills);
895
+ return all.filter((t) => {
896
+ const skillId = getSkillIdFromToolId(t.id);
897
+ return skillId === null || skillSet.has(skillId);
898
+ });
899
+ }
900
+ return all;
901
+ }
902
+ function toolToOpenAIFormat(tool) {
903
+ return {
904
+ type: "function",
905
+ function: {
906
+ name: tool.id,
907
+ description: tool.description,
908
+ parameters: tool.input_schema
909
+ }
910
+ };
911
+ }
912
+ var registry;
913
+ var init_tool_registry = __esm(() => {
914
+ registry = new Map;
915
+ });
916
+
853
917
  // node_modules/yaml/dist/nodes/identity.js
854
918
  var require_identity = __commonJS((exports) => {
855
919
  var ALIAS = Symbol.for("yaml.alias");
@@ -7801,75 +7865,9 @@ var require_dist = __commonJS((exports) => {
7801
7865
  exports.visitAsync = visit.visitAsync;
7802
7866
  });
7803
7867
 
7804
- // src/core/tool-registry.ts
7805
- function registerTool(tool) {
7806
- registry.set(tool.id, tool);
7807
- }
7808
- function unregisterTool(id) {
7809
- return registry.delete(id);
7810
- }
7811
- function getTool(id) {
7812
- return registry.get(id);
7813
- }
7814
- function getAllTools() {
7815
- return [...registry.values()];
7816
- }
7817
- function getSkillIdFromToolId(toolId) {
7818
- if (toolId.endsWith("_request"))
7819
- return toolId.replace(/_request$/, "");
7820
- const idx = toolId.indexOf(":");
7821
- if (idx > 0)
7822
- return toolId.slice(0, idx);
7823
- return null;
7824
- }
7825
- function unregisterSkillTools() {
7826
- const all = getAllTools();
7827
- for (const t of all) {
7828
- if (getSkillIdFromToolId(t.id) !== null)
7829
- unregisterTool(t.id);
7830
- }
7831
- }
7832
- function getToolsForAgent(agent) {
7833
- const all = getAllTools();
7834
- const allowlist = agent.tools;
7835
- if (allowlist?.length) {
7836
- const set = new Set(allowlist);
7837
- return all.filter((t) => set.has(t.id));
7838
- }
7839
- const skills = agent.skills;
7840
- if (skills?.length && !skills.includes("*")) {
7841
- const skillSet = new Set(skills);
7842
- return all.filter((t) => {
7843
- const skillId = getSkillIdFromToolId(t.id);
7844
- return skillId === null || skillSet.has(skillId);
7845
- });
7846
- }
7847
- return all;
7848
- }
7849
- function toolToOpenAIFormat(tool) {
7850
- return {
7851
- type: "function",
7852
- function: {
7853
- name: tool.id,
7854
- description: tool.description,
7855
- parameters: tool.input_schema
7856
- }
7857
- };
7858
- }
7859
- var registry;
7860
- var init_tool_registry = __esm(() => {
7861
- registry = new Map;
7862
- });
7863
-
7864
- // src/core/error.ts
7865
- function errorMessage(err) {
7866
- return err instanceof Error ? err.message : String(err);
7867
- }
7868
-
7869
- // src/skills/loader.ts
7870
- import { readFile as readFile3, readdir as readdir2, cp, mkdir as mkdir3, writeFile as writeFile3, rm } from "fs/promises";
7871
- import { join as join3, resolve as resolve2, basename } from "path";
7872
- import { tmpdir } from "os";
7868
+ // src/skills/skill-doc.ts
7869
+ import { readFile as readFile3 } from "fs/promises";
7870
+ import { join as join3 } from "path";
7873
7871
  function getRequiredEnvFromDoc(doc) {
7874
7872
  const creds = Array.isArray(doc.credentials) ? doc.credentials : typeof doc.credentials === "string" ? [doc.credentials] : [];
7875
7873
  const baseEnv = doc.base_url_env?.trim() ? [doc.base_url_env.trim()] : [];
@@ -7890,18 +7888,6 @@ async function getRequiredEnvForSkill(skillDir, subEntries, doc) {
7890
7888
  }
7891
7889
  return env;
7892
7890
  }
7893
- async function loadSkillYamlFromFile(path) {
7894
- try {
7895
- const raw = await readFile3(path, "utf-8");
7896
- const parsed = import_yaml.default.parse(raw);
7897
- return parsed;
7898
- } catch (err) {
7899
- if (err.code === "ENOENT")
7900
- return null;
7901
- console.error(`[skills] Failed to read YAML from ${path}:`, err);
7902
- return null;
7903
- }
7904
- }
7905
7891
  function extractYamlBlockFromMarkdown(md) {
7906
7892
  const lines = md.split(`
7907
7893
  `);
@@ -7915,15 +7901,12 @@ function extractYamlBlockFromMarkdown(md) {
7915
7901
  }
7916
7902
  continue;
7917
7903
  }
7918
- if (trimmed.startsWith("```")) {
7904
+ if (trimmed.startsWith("```"))
7919
7905
  break;
7920
- }
7921
7906
  buf.push(line);
7922
7907
  }
7923
- if (buf.length === 0)
7924
- return null;
7925
- return buf.join(`
7926
- `);
7908
+ return buf.length > 0 ? buf.join(`
7909
+ `) : null;
7927
7910
  }
7928
7911
  function extractFrontmatterFromMarkdown(md) {
7929
7912
  const lines = md.split(`
@@ -7936,16 +7919,53 @@ function extractFrontmatterFromMarkdown(md) {
7936
7919
  break;
7937
7920
  buf.push(lines[i]);
7938
7921
  }
7939
- if (buf.length === 0)
7922
+ return buf.length > 0 ? buf.join(`
7923
+ `) : null;
7924
+ }
7925
+ function extractMarkdownBody(md) {
7926
+ const trimmed = md.replace(/\r\n/g, `
7927
+ `).trim();
7928
+ if (!trimmed.startsWith("---"))
7929
+ return trimmed;
7930
+ const afterFirst = trimmed.slice(3);
7931
+ const closeIdx = afterFirst.indexOf(`
7932
+ ---`);
7933
+ if (closeIdx === -1)
7934
+ return trimmed;
7935
+ return afterFirst.slice(closeIdx + 4).trim();
7936
+ }
7937
+ async function loadSkillDocument(dir, entries) {
7938
+ if (!entries.includes("SKILL.md"))
7940
7939
  return null;
7941
- return buf.join(`
7942
- `);
7940
+ try {
7941
+ const raw = await readFile3(join3(dir, "SKILL.md"), "utf-8");
7942
+ const yamlBlock = extractFrontmatterFromMarkdown(raw) || extractYamlBlockFromMarkdown(raw);
7943
+ if (!yamlBlock) {
7944
+ console.warn(`[skills] No YAML frontmatter or block in SKILL.md for ${dir}, skipping.`);
7945
+ return null;
7946
+ }
7947
+ return import_yaml.default.parse(yamlBlock);
7948
+ } catch (err) {
7949
+ console.error(`[skills] Failed to read SKILL.md in ${dir}:`, err);
7950
+ return null;
7951
+ }
7952
+ }
7953
+ var import_yaml;
7954
+ var init_skill_doc = __esm(() => {
7955
+ import_yaml = __toESM(require_dist(), 1);
7956
+ });
7957
+
7958
+ // src/skills/skill-extract.ts
7959
+ import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
7960
+ import { join as join4 } from "path";
7961
+ function isJunkDir(name) {
7962
+ return JUNK_DIR_NAMES.has(name.toLowerCase()) || name.startsWith(".");
7943
7963
  }
7944
7964
  async function deriveSkillIdFromFlatRoot(extractDir, archiveFilename) {
7945
7965
  const entries = await readdir2(extractDir);
7946
7966
  if (entries.includes("_meta.json")) {
7947
7967
  try {
7948
- const raw = await readFile3(join3(extractDir, "_meta.json"), "utf-8");
7968
+ const raw = await readFile4(join4(extractDir, "_meta.json"), "utf-8");
7949
7969
  const meta = JSON.parse(raw);
7950
7970
  if (typeof meta.slug === "string" && meta.slug.length > 0) {
7951
7971
  return meta.slug.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "skill";
@@ -7954,10 +7974,10 @@ async function deriveSkillIdFromFlatRoot(extractDir, archiveFilename) {
7954
7974
  }
7955
7975
  if (entries.includes("SKILL.md")) {
7956
7976
  try {
7957
- const raw = await readFile3(join3(extractDir, "SKILL.md"), "utf-8");
7977
+ const raw = await readFile4(join4(extractDir, "SKILL.md"), "utf-8");
7958
7978
  const yamlBlock = extractFrontmatterFromMarkdown(raw) || extractYamlBlockFromMarkdown(raw);
7959
7979
  if (yamlBlock) {
7960
- const parsed = import_yaml.default.parse(yamlBlock);
7980
+ const parsed = import_yaml2.default.parse(yamlBlock);
7961
7981
  if (typeof parsed.name === "string" && parsed.name.length > 0) {
7962
7982
  return parsed.name.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "skill";
7963
7983
  }
@@ -7967,9 +7987,6 @@ async function deriveSkillIdFromFlatRoot(extractDir, archiveFilename) {
7967
7987
  const stem = archiveFilename.replace(/\.tar\.gz$/i, "").replace(/\.tgz$/i, "").replace(/\.zip$/i, "").replace(/\.tar$/i, "");
7968
7988
  return stem.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "skill";
7969
7989
  }
7970
- function isJunkDir(name) {
7971
- return JUNK_DIR_NAMES.has(name.toLowerCase()) || name.startsWith(".");
7972
- }
7973
7990
  async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
7974
7991
  const entries = await readdir2(tmpDir, { withFileTypes: true });
7975
7992
  const rootNames = new Set(entries.map((e) => e.name));
@@ -7977,7 +7994,7 @@ async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
7977
7994
  const dirs = entries.filter((e) => e.isDirectory() && !isJunkDir(e.name));
7978
7995
  const dirsWithSkillDoc = [];
7979
7996
  for (const d of dirs) {
7980
- const sub = await readdir2(join3(tmpDir, d.name)).catch(() => []);
7997
+ const sub = await readdir2(join4(tmpDir, d.name)).catch(() => []);
7981
7998
  const hasSkillMd = sub.includes("SKILL.md");
7982
7999
  if (hasSkillMd || sub.includes("README.md"))
7983
8000
  dirsWithSkillDoc.push({ name: d.name, hasSkillMd });
@@ -7988,12 +8005,11 @@ async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
7988
8005
  }
7989
8006
  if (dirsWithSkillDoc.length >= 1) {
7990
8007
  const preferred = dirsWithSkillDoc.find((d) => d.hasSkillMd) ?? dirsWithSkillDoc[0];
7991
- const dirName = preferred.name;
7992
- return { id: dirName, sourcePath: join3(tmpDir, dirName) };
8008
+ return { id: preferred.name, sourcePath: join4(tmpDir, preferred.name) };
7993
8009
  }
7994
8010
  if (dirs.length === 1) {
7995
8011
  const dirName = dirs[0].name;
7996
- return { id: dirName, sourcePath: join3(tmpDir, dirName) };
8012
+ return { id: dirName, sourcePath: join4(tmpDir, dirName) };
7997
8013
  }
7998
8014
  if (hasSkillDocAtRoot) {
7999
8015
  const id2 = await deriveSkillIdFromFlatRoot(tmpDir, archiveFilename);
@@ -8001,33 +8017,43 @@ async function chooseSkillRootFromExtract(tmpDir, archiveFilename) {
8001
8017
  }
8002
8018
  const firstDir = dirs[0];
8003
8019
  if (firstDir) {
8004
- return { id: firstDir.name, sourcePath: join3(tmpDir, firstDir.name) };
8020
+ return { id: firstDir.name, sourcePath: join4(tmpDir, firstDir.name) };
8005
8021
  }
8006
8022
  const id = await deriveSkillIdFromFlatRoot(tmpDir, archiveFilename);
8007
8023
  return { id, sourcePath: tmpDir };
8008
8024
  }
8009
- async function loadSkillDocument(dir, entries) {
8010
- const mdName = "SKILL.md";
8011
- const yamlName = "skill.yaml";
8012
- if (entries.includes(mdName)) {
8025
+ function slugToSkillId(slug) {
8026
+ return slug.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "skill";
8027
+ }
8028
+ function deriveSkillIdFromSkillMdContent(md, filename) {
8029
+ const yamlBlock = extractFrontmatterFromMarkdown(md) || extractYamlBlockFromMarkdown(md);
8030
+ if (yamlBlock) {
8013
8031
  try {
8014
- const raw = await readFile3(join3(dir, mdName), "utf-8");
8015
- const yamlBlock = extractYamlBlockFromMarkdown(raw) || extractFrontmatterFromMarkdown(raw);
8016
- if (!yamlBlock) {
8017
- console.warn(`[skills] No YAML block or frontmatter in ${mdName} for ${dir}, skipping.`);
8018
- return null;
8032
+ const parsed = import_yaml2.default.parse(yamlBlock);
8033
+ if (typeof parsed.name === "string" && parsed.name.trim()) {
8034
+ return parsed.name.trim().replace(/[^a-z0-9_-]/gi, "_").toLowerCase().replace(/^_+|_+$/g, "") || "skill";
8019
8035
  }
8020
- const parsed = import_yaml.default.parse(yamlBlock);
8021
- return parsed;
8022
- } catch (err) {
8023
- console.error(`[skills] Failed to read SKILL.md in ${dir}:`, err);
8024
- }
8036
+ } catch {}
8025
8037
  }
8026
- if (entries.includes(yamlName)) {
8027
- return loadSkillYamlFromFile(join3(dir, yamlName));
8038
+ const stem = filename.replace(/\.(md|markdown)$/i, "").trim();
8039
+ if (stem && stem !== "SKILL") {
8040
+ return stem.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "uploaded_skill";
8028
8041
  }
8029
- return null;
8042
+ return "uploaded_skill";
8043
+ }
8044
+ var import_yaml2, JUNK_DIR_NAMES;
8045
+ var init_skill_extract = __esm(() => {
8046
+ init_skill_doc();
8047
+ import_yaml2 = __toESM(require_dist(), 1);
8048
+ JUNK_DIR_NAMES = new Set(["__macosx"]);
8049
+ });
8050
+
8051
+ // src/core/error.ts
8052
+ function errorMessage(err) {
8053
+ return err instanceof Error ? err.message : String(err);
8030
8054
  }
8055
+
8056
+ // src/skills/skill-tools.ts
8031
8057
  function getSkillBaseUrl() {
8032
8058
  const env = process.env.AGENT_OS_SKILL_BASE_URL?.trim();
8033
8059
  if (env)
@@ -8036,10 +8062,23 @@ function getSkillBaseUrl() {
8036
8062
  const port = process.env.PORT || "3010";
8037
8063
  return `http://${host}:${port}`;
8038
8064
  }
8039
- function httpToolFromDescriptor(skill, dir, desc) {
8065
+ async function getAuthToken(skillId, credentials) {
8066
+ if (!credentials?.length)
8067
+ return null;
8068
+ const { readSkillConfig: readSkillConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
8069
+ const stored = await readSkillConfig2(skillId);
8070
+ for (const envVar of credentials) {
8071
+ const v = stored[envVar]?.trim() ?? process.env[envVar]?.trim();
8072
+ if (v)
8073
+ return v;
8074
+ }
8075
+ return null;
8076
+ }
8077
+ function httpToolFromDescriptor(skill, _dir, desc) {
8040
8078
  const id = desc.id;
8041
8079
  const method = (desc.method || "GET").toUpperCase();
8042
8080
  const path = desc.path || "/";
8081
+ const base = getSkillBaseUrl();
8043
8082
  return {
8044
8083
  id,
8045
8084
  name: `${skill}:${id}`,
@@ -8052,7 +8091,6 @@ function httpToolFromDescriptor(skill, dir, desc) {
8052
8091
  }
8053
8092
  },
8054
8093
  async execute(input) {
8055
- const base = getSkillBaseUrl();
8056
8094
  const url = `${base}${path}`;
8057
8095
  const query = input.query ?? {};
8058
8096
  let body = input.body;
@@ -8070,9 +8108,7 @@ function httpToolFromDescriptor(skill, dir, desc) {
8070
8108
  }
8071
8109
  const res = await fetch(urlObj.toString(), {
8072
8110
  method,
8073
- headers: {
8074
- "Content-Type": "application/json"
8075
- },
8111
+ headers: { "Content-Type": "application/json" },
8076
8112
  body: method === "GET" || method === "HEAD" ? undefined : JSON.stringify(body ?? {})
8077
8113
  });
8078
8114
  const text = await res.text();
@@ -8082,26 +8118,10 @@ function httpToolFromDescriptor(skill, dir, desc) {
8082
8118
  } catch {
8083
8119
  json = text;
8084
8120
  }
8085
- return {
8086
- status: res.status,
8087
- ok: res.ok,
8088
- data: json
8089
- };
8121
+ return { status: res.status, ok: res.ok, data: json };
8090
8122
  }
8091
8123
  };
8092
8124
  }
8093
- async function getAuthToken(skillId, credentials) {
8094
- if (!credentials?.length)
8095
- return null;
8096
- const { readSkillConfig: readSkillConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
8097
- const stored = await readSkillConfig2(skillId);
8098
- for (const envVar of credentials) {
8099
- const v = stored[envVar]?.trim() ?? process.env[envVar]?.trim();
8100
- if (v)
8101
- return v;
8102
- }
8103
- return null;
8104
- }
8105
8125
  function createDocOnlyRequestTool(skillId, apiBase, credentials, authScheme, authLocation, authParam, skillDescription) {
8106
8126
  const base = apiBase.replace(/\/$/, "");
8107
8127
  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.`;
@@ -8226,11 +8246,17 @@ function createTokenRequestTool(skillId, toolId, baseUrl, description) {
8226
8246
  }
8227
8247
  };
8228
8248
  }
8249
+ var init_skill_tools = () => {};
8250
+
8251
+ // src/skills/loader.ts
8252
+ import { readFile as readFile5, readdir as readdir3, cp, mkdir as mkdir3, writeFile as writeFile3, rm } from "fs/promises";
8253
+ import { join as join5, resolve as resolve2, basename } from "path";
8254
+ import { tmpdir } from "os";
8229
8255
  async function loadSkill(name) {
8230
- const dir = join3(getSkillsDir(), name);
8256
+ const dir = join5(getSkillsDir(), name);
8231
8257
  let entries;
8232
8258
  try {
8233
- entries = await readdir2(dir);
8259
+ entries = await readdir3(dir);
8234
8260
  } catch (err) {
8235
8261
  if (err.code === "ENOENT")
8236
8262
  return;
@@ -8244,8 +8270,7 @@ async function loadSkill(name) {
8244
8270
  for (const t of skill.tools) {
8245
8271
  if (!t.id)
8246
8272
  continue;
8247
- const tool = httpToolFromDescriptor(name, dir, t);
8248
- registerTool(tool);
8273
+ registerTool(httpToolFromDescriptor(name, dir, t));
8249
8274
  }
8250
8275
  return;
8251
8276
  }
@@ -8264,7 +8289,7 @@ async function loadSkill(name) {
8264
8289
  }
8265
8290
  if (!apiBase && entries.includes("skill.json")) {
8266
8291
  try {
8267
- const raw = await readFile3(join3(dir, "skill.json"), "utf-8");
8292
+ const raw = await readFile5(join5(dir, "skill.json"), "utf-8");
8268
8293
  const meta = JSON.parse(raw);
8269
8294
  if (meta.base_url_env && process.env[meta.base_url_env]?.trim()) {
8270
8295
  apiBase = process.env[meta.base_url_env].trim().replace(/\/$/, "");
@@ -8277,7 +8302,7 @@ async function loadSkill(name) {
8277
8302
  }
8278
8303
  if (!apiBase && entries.includes("SKILL.md")) {
8279
8304
  try {
8280
- const raw = await readFile3(join3(dir, "SKILL.md"), "utf-8");
8305
+ const raw = await readFile5(join5(dir, "SKILL.md"), "utf-8");
8281
8306
  const match = raw.match(/Base\s+URL:\s*[`"]?(\s*https?:\/\/[^\s`"]+)/i) || raw.match(/api_base:\s*["']?([^"'\s]+)/i);
8282
8307
  if (match?.[1])
8283
8308
  apiBase = match[1].trim().replace(/[`"]/g, "");
@@ -8285,8 +8310,7 @@ async function loadSkill(name) {
8285
8310
  }
8286
8311
  if (apiBase) {
8287
8312
  const authScheme = skill.auth_scheme === "Apikey" ? "Apikey" : "Bearer";
8288
- const tool = createDocOnlyRequestTool(name, apiBase, credentials, authScheme, skill.auth_location, skill.auth_param, skill.description);
8289
- registerTool(tool);
8313
+ registerTool(createDocOnlyRequestTool(name, apiBase, credentials, authScheme, skill.auth_location, skill.auth_param, skill.description));
8290
8314
  } else if (baseUrlEnv) {
8291
8315
  console.warn(`[skills] ${name} skill loaded but request tool not registered: set ${baseUrlEnv} so the agent can call the API.`);
8292
8316
  }
@@ -8305,28 +8329,16 @@ async function loadSkillsForAgent(agent) {
8305
8329
  }
8306
8330
  await Promise.all(skills.map((name) => loadSkill(name)));
8307
8331
  }
8308
- function extractMarkdownBody(md) {
8309
- const trimmed = md.replace(/\r\n/g, `
8310
- `).trim();
8311
- if (!trimmed.startsWith("---"))
8312
- return trimmed;
8313
- const afterFirst = trimmed.slice(3);
8314
- const closeIdx = afterFirst.indexOf(`
8315
- ---`);
8316
- if (closeIdx === -1)
8317
- return trimmed;
8318
- return afterFirst.slice(closeIdx + 4).trim();
8319
- }
8320
8332
  async function getSkillDocContext(skillNames) {
8321
8333
  const dir = getSkillsDir();
8322
8334
  const parts = [];
8323
8335
  for (const name of skillNames) {
8324
8336
  if (!name?.trim())
8325
8337
  continue;
8326
- const skillDir = join3(dir, name);
8327
- const mdPath = join3(skillDir, "SKILL.md");
8338
+ const skillDir = join5(dir, name);
8339
+ const mdPath = join5(skillDir, "SKILL.md");
8328
8340
  try {
8329
- const raw = await readFile3(mdPath, "utf-8");
8341
+ const raw = await readFile5(mdPath, "utf-8");
8330
8342
  const body = extractMarkdownBody(raw);
8331
8343
  if (body)
8332
8344
  parts.push(`## Skill: ${name}
@@ -8341,10 +8353,9 @@ ${body}`);
8341
8353
  `);
8342
8354
  }
8343
8355
  async function getSkillConfigSchema(skillId) {
8344
- const dir = join3(getSkillsDir(), skillId);
8345
- const path = join3(dir, "config.schema.json");
8356
+ const path = join5(getSkillsDir(), skillId, "config.schema.json");
8346
8357
  try {
8347
- const raw = await readFile3(path, "utf-8");
8358
+ const raw = await readFile5(path, "utf-8");
8348
8359
  const parsed = JSON.parse(raw);
8349
8360
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8350
8361
  return parsed;
@@ -8357,10 +8368,9 @@ async function getSkillConfigSchema(skillId) {
8357
8368
  return null;
8358
8369
  }
8359
8370
  async function getSkillSetupMarkdown(skillId) {
8360
- const dir = join3(getSkillsDir(), skillId);
8361
- const mdPath = join3(dir, "SKILL.md");
8371
+ const mdPath = join5(getSkillsDir(), skillId, "SKILL.md");
8362
8372
  try {
8363
- const raw = await readFile3(mdPath, "utf-8");
8373
+ const raw = await readFile5(mdPath, "utf-8");
8364
8374
  const lines = raw.split(`
8365
8375
  `);
8366
8376
  let inSetup = false;
@@ -8391,22 +8401,19 @@ async function getSkillSetupMarkdown(skillId) {
8391
8401
  }
8392
8402
  async function getStoreRegistry() {
8393
8403
  const registryUrl = process.env.SKILLS_REGISTRY_URL?.trim() ?? null;
8394
- if (!registryUrl) {
8404
+ if (!registryUrl)
8395
8405
  return { skills: [], storeBase: null, registryUrl: null };
8396
- }
8397
8406
  let storeBase = null;
8398
8407
  try {
8399
8408
  storeBase = new URL(registryUrl).origin;
8400
8409
  } catch {}
8401
8410
  try {
8402
8411
  const res = await fetch(registryUrl, { redirect: "follow" });
8403
- if (!res.ok) {
8412
+ if (!res.ok)
8404
8413
  return { skills: [], storeBase, registryUrl };
8405
- }
8406
8414
  const contentType = (res.headers.get("content-type") ?? "").toLowerCase();
8407
- if (!contentType.includes("application/json")) {
8415
+ if (!contentType.includes("application/json"))
8408
8416
  return { skills: [], storeBase, registryUrl };
8409
- }
8410
8417
  const data = await res.json();
8411
8418
  const raw = Array.isArray(data) ? data : Array.isArray(data?.skills) ? data.skills : [];
8412
8419
  const skills = raw.filter((e) => e != null && typeof e === "object").filter((e) => typeof e.slug === "string").map((e) => {
@@ -8433,13 +8440,10 @@ async function getStoreRegistry() {
8433
8440
  }
8434
8441
  async function getSkillMarketplace() {
8435
8442
  const home = getAgentOsHome();
8436
- const paths = [
8437
- join3(home, "skill-marketplace.json"),
8438
- join3(process.cwd(), "data", "skill-marketplace.json")
8439
- ];
8443
+ const paths = [join5(home, "skill-marketplace.json"), join5(process.cwd(), "data", "skill-marketplace.json")];
8440
8444
  for (const path of paths) {
8441
8445
  try {
8442
- const raw = await readFile3(path, "utf-8");
8446
+ const raw = await readFile5(path, "utf-8");
8443
8447
  const parsed = JSON.parse(raw);
8444
8448
  if (Array.isArray(parsed)) {
8445
8449
  return parsed.filter((e) => e && typeof e === "object" && typeof e.id === "string" && typeof e.name === "string");
@@ -8459,15 +8463,13 @@ async function uninstallSkill(skillId) {
8459
8463
  if (SYSTEM_SKILL_IDS.has(skillId)) {
8460
8464
  throw new Error("Cannot uninstall system skill");
8461
8465
  }
8462
- const dir = getSkillsDir();
8463
- const skillDir = join3(dir, skillId);
8464
- await rm(skillDir, { recursive: true, force: true });
8466
+ await rm(join5(getSkillsDir(), skillId), { recursive: true, force: true });
8465
8467
  }
8466
8468
  async function listSkills() {
8467
8469
  const dir = getSkillsDir();
8468
8470
  let entries;
8469
8471
  try {
8470
- entries = await readdir2(dir, { withFileTypes: true });
8472
+ entries = await readdir3(dir, { withFileTypes: true });
8471
8473
  } catch (err) {
8472
8474
  if (err.code === "ENOENT")
8473
8475
  return [];
@@ -8480,10 +8482,10 @@ async function listSkills() {
8480
8482
  const name = e.name;
8481
8483
  if (!name || name.startsWith("."))
8482
8484
  continue;
8483
- const skillDir = join3(dir, name);
8485
+ const skillDir = join5(dir, name);
8484
8486
  let subEntries;
8485
8487
  try {
8486
- subEntries = await readdir2(skillDir);
8488
+ subEntries = await readdir3(skillDir);
8487
8489
  } catch {
8488
8490
  continue;
8489
8491
  }
@@ -8512,16 +8514,13 @@ function getAllowedPathBase() {
8512
8514
  async function installSkillFromPath(sourcePath) {
8513
8515
  const base = getAllowedPathBase();
8514
8516
  const resolved = resolve2(sourcePath);
8515
- if (!resolved.startsWith(base)) {
8517
+ if (!resolved.startsWith(base))
8516
8518
  throw new Error(`Path must be under ${base}`);
8517
- }
8518
8519
  const id = basename(resolved);
8519
- if (!id || id.startsWith(".")) {
8520
+ if (!id || id.startsWith("."))
8520
8521
  throw new Error("Invalid skill folder name");
8521
- }
8522
- const destDir = join3(getSkillsDir(), id);
8523
8522
  await mkdir3(getSkillsDir(), { recursive: true });
8524
- await cp(resolved, destDir, { recursive: true });
8523
+ await cp(resolved, join5(getSkillsDir(), id), { recursive: true });
8525
8524
  return { id };
8526
8525
  }
8527
8526
  async function installSystemSkills() {
@@ -8535,16 +8534,15 @@ async function installSystemSkills() {
8535
8534
  } catch {
8536
8535
  seedExists = false;
8537
8536
  }
8538
- if (!seedExists) {
8537
+ if (!seedExists)
8539
8538
  throw new Error(`Seed skills directory not found: ${seedDir}`);
8540
- }
8541
8539
  let installed = 0;
8542
8540
  for (const id of DEFAULT_SYSTEM_SKILL_IDS) {
8543
- const sourcePath = join3(seedDir, id);
8544
- const destPath = join3(skillsDir, id);
8541
+ const sourcePath = join5(seedDir, id);
8542
+ const destPath = join5(skillsDir, id);
8545
8543
  let hasSkillMd;
8546
8544
  try {
8547
- await access(join3(sourcePath, "SKILL.md"));
8545
+ await access(join5(sourcePath, "SKILL.md"));
8548
8546
  hasSkillMd = true;
8549
8547
  } catch {
8550
8548
  hasSkillMd = false;
@@ -8553,8 +8551,7 @@ async function installSystemSkills() {
8553
8551
  continue;
8554
8552
  let alreadyInstalled;
8555
8553
  try {
8556
- const st = await stat(destPath);
8557
- alreadyInstalled = st.isDirectory();
8554
+ alreadyInstalled = (await stat(destPath)).isDirectory();
8558
8555
  } catch {
8559
8556
  alreadyInstalled = false;
8560
8557
  }
@@ -8569,35 +8566,25 @@ async function installSystemSkills() {
8569
8566
  }
8570
8567
  return { installed };
8571
8568
  }
8572
- function slugToSkillId(slug) {
8573
- return slug.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "skill";
8574
- }
8575
8569
  async function installSkillFromUrl(url, explicitId) {
8576
8570
  const urlLower = url.toLowerCase();
8577
8571
  const isStoreSkillContentUrl = urlLower.includes("/api/sulalahub/skills/") && !urlLower.includes("/download") && !urlLower.endsWith(".zip");
8578
8572
  const headers = isStoreSkillContentUrl ? { Accept: "application/zip" } : {};
8579
8573
  const res = await fetch(url, { redirect: "follow", headers });
8580
- if (!res.ok) {
8574
+ if (!res.ok)
8581
8575
  throw new Error(`Fetch failed: ${res.status}`);
8582
- }
8583
8576
  const buf = await res.arrayBuffer();
8584
8577
  const contentType = (res.headers.get("content-type") ?? "").toLowerCase();
8585
- const isZipByType = contentType.includes("application/zip");
8586
- const isZipByUrl = urlLower.endsWith(".zip") || urlLower.includes("/download");
8587
- const isZip = isZipByType || isZipByUrl;
8578
+ const isZip = contentType.includes("application/zip") || urlLower.endsWith(".zip") || urlLower.includes("/download");
8588
8579
  const skillsDir = getSkillsDir();
8589
8580
  await mkdir3(skillsDir, { recursive: true });
8590
- const tmpDir = join3(tmpdir(), `agent-os-skill-${Date.now()}`);
8581
+ const tmpDir = join5(tmpdir(), `agent-os-skill-${Date.now()}`);
8591
8582
  await mkdir3(tmpDir, { recursive: true });
8592
8583
  try {
8593
8584
  if (isZip) {
8594
- const zipPath = join3(tmpDir, "archive.zip");
8585
+ const zipPath = join5(tmpDir, "archive.zip");
8595
8586
  await writeFile3(zipPath, new Uint8Array(buf));
8596
- const proc2 = Bun.spawn({
8597
- cmd: ["unzip", "-q", "-o", zipPath, "-d", tmpDir],
8598
- stdout: "ignore",
8599
- stderr: "pipe"
8600
- });
8587
+ const proc2 = Bun.spawn({ cmd: ["unzip", "-q", "-o", zipPath, "-d", tmpDir], stdout: "ignore", stderr: "pipe" });
8601
8588
  const exit2 = await proc2.exited;
8602
8589
  if (exit2 !== 0) {
8603
8590
  const err = await new Response(proc2.stderr).text();
@@ -8605,17 +8592,12 @@ async function installSkillFromUrl(url, explicitId) {
8605
8592
  }
8606
8593
  const { id: id2, sourcePath: sourcePath2 } = await chooseSkillRootFromExtract(tmpDir, "archive.zip");
8607
8594
  const destId2 = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id2;
8608
- const destDir2 = join3(skillsDir, destId2);
8609
- await cp(sourcePath2, destDir2, { recursive: true });
8595
+ await cp(sourcePath2, join5(skillsDir, destId2), { recursive: true });
8610
8596
  return { id: destId2 };
8611
8597
  }
8612
- const tarPath = join3(tmpDir, "archive.tar.gz");
8598
+ const tarPath = join5(tmpDir, "archive.tar.gz");
8613
8599
  await writeFile3(tarPath, new Uint8Array(buf));
8614
- const proc = Bun.spawn({
8615
- cmd: ["tar", "-xzf", tarPath, "-C", tmpDir],
8616
- stdout: "ignore",
8617
- stderr: "pipe"
8618
- });
8600
+ const proc = Bun.spawn({ cmd: ["tar", "-xzf", tarPath, "-C", tmpDir], stdout: "ignore", stderr: "pipe" });
8619
8601
  const exit = await proc.exited;
8620
8602
  if (exit !== 0) {
8621
8603
  const err = await new Response(proc.stderr).text();
@@ -8623,8 +8605,7 @@ async function installSkillFromUrl(url, explicitId) {
8623
8605
  }
8624
8606
  const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, "archive.tar.gz");
8625
8607
  const destId = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id;
8626
- const destDir = join3(skillsDir, destId);
8627
- await cp(sourcePath, destDir, { recursive: true });
8608
+ await cp(sourcePath, join5(skillsDir, destId), { recursive: true });
8628
8609
  return { id: destId };
8629
8610
  } finally {
8630
8611
  await rm(tmpDir, { recursive: true, force: true });
@@ -8633,25 +8614,20 @@ async function installSkillFromUrl(url, explicitId) {
8633
8614
  async function installSkillFromUpload(buffer, filename) {
8634
8615
  const skillsDir = getSkillsDir();
8635
8616
  await mkdir3(skillsDir, { recursive: true });
8636
- const tmpDir = join3(tmpdir(), `agent-os-skill-upload-${Date.now()}`);
8617
+ const tmpDir = join5(tmpdir(), `agent-os-skill-upload-${Date.now()}`);
8637
8618
  await mkdir3(tmpDir, { recursive: true });
8638
8619
  const lower = filename.toLowerCase();
8639
8620
  const isZip = lower.endsWith(".zip");
8640
8621
  const isTarGz = lower.endsWith(".tar.gz") || lower.endsWith(".tgz");
8641
8622
  const isTar = lower.endsWith(".tar");
8642
- if (!isZip && !isTarGz && !isTar) {
8623
+ if (!isZip && !isTarGz && !isTar)
8643
8624
  throw new Error("Upload must be a .tar.gz, .tar, or .zip archive");
8644
- }
8645
8625
  try {
8646
8626
  const ext = isZip ? "archive.zip" : isTar && !isTarGz ? "archive.tar" : "archive.tar.gz";
8647
- const archivePath = join3(tmpDir, ext);
8627
+ const archivePath = join5(tmpDir, ext);
8648
8628
  await writeFile3(archivePath, new Uint8Array(buffer));
8649
8629
  if (isZip) {
8650
- const proc = Bun.spawn({
8651
- cmd: ["unzip", "-q", "-o", archivePath, "-d", tmpDir],
8652
- stdout: "ignore",
8653
- stderr: "pipe"
8654
- });
8630
+ const proc = Bun.spawn({ cmd: ["unzip", "-q", "-o", archivePath, "-d", tmpDir], stdout: "ignore", stderr: "pipe" });
8655
8631
  const exit = await proc.exited;
8656
8632
  if (exit !== 0) {
8657
8633
  const err = await new Response(proc.stderr).text();
@@ -8670,29 +8646,12 @@ async function installSkillFromUpload(buffer, filename) {
8670
8646
  }
8671
8647
  }
8672
8648
  const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, filename);
8673
- const destDir = join3(skillsDir, id);
8674
- await cp(sourcePath, destDir, { recursive: true });
8649
+ await cp(sourcePath, join5(skillsDir, id), { recursive: true });
8675
8650
  return { id };
8676
8651
  } finally {
8677
8652
  await rm(tmpDir, { recursive: true, force: true });
8678
8653
  }
8679
8654
  }
8680
- function deriveSkillIdFromSkillMdContent(md, filename) {
8681
- const yamlBlock = extractFrontmatterFromMarkdown(md) || extractYamlBlockFromMarkdown(md);
8682
- if (yamlBlock) {
8683
- try {
8684
- const parsed = import_yaml.default.parse(yamlBlock);
8685
- if (typeof parsed.name === "string" && parsed.name.trim()) {
8686
- return parsed.name.trim().replace(/[^a-z0-9_-]/gi, "_").toLowerCase().replace(/^_+|_+$/g, "") || "skill";
8687
- }
8688
- } catch {}
8689
- }
8690
- const stem = filename.replace(/\.(md|markdown)$/i, "").trim();
8691
- if (stem && stem !== "SKILL") {
8692
- return stem.replace(/[^a-z0-9_-]/gi, "_").toLowerCase() || "uploaded_skill";
8693
- }
8694
- return "uploaded_skill";
8695
- }
8696
8655
  async function installSkillFromSkillMd(buffer, filename, explicitId) {
8697
8656
  const skillsDir = getSkillsDir();
8698
8657
  await mkdir3(skillsDir, { recursive: true });
@@ -8700,18 +8659,19 @@ async function installSkillFromSkillMd(buffer, filename, explicitId) {
8700
8659
  const id = explicitId?.trim() ? explicitId.replace(/[^a-z0-9_-]/gi, "_").toLowerCase().replace(/^_+|_+$/g, "") || "uploaded_skill" : deriveSkillIdFromSkillMdContent(text, filename);
8701
8660
  if (!id)
8702
8661
  throw new Error("Invalid skill id");
8703
- const destDir = join3(skillsDir, id);
8662
+ const destDir = join5(skillsDir, id);
8704
8663
  await mkdir3(destDir, { recursive: true });
8705
- await writeFile3(join3(destDir, "SKILL.md"), text, "utf-8");
8664
+ await writeFile3(join5(destDir, "SKILL.md"), text, "utf-8");
8706
8665
  return { id };
8707
8666
  }
8708
- var import_yaml, JUNK_DIR_NAMES, SYSTEM_SKILL_IDS, DEFAULT_SYSTEM_SKILL_IDS;
8667
+ var SYSTEM_SKILL_IDS, DEFAULT_SYSTEM_SKILL_IDS;
8709
8668
  var init_loader = __esm(() => {
8710
8669
  init_config();
8711
8670
  init_tool_registry();
8671
+ init_skill_doc();
8672
+ init_skill_extract();
8673
+ init_skill_tools();
8712
8674
  init_config();
8713
- import_yaml = __toESM(require_dist(), 1);
8714
- JUNK_DIR_NAMES = new Set(["__macosx"]);
8715
8675
  SYSTEM_SKILL_IDS = new Set(["memory"]);
8716
8676
  DEFAULT_SYSTEM_SKILL_IDS = ["memory", "date", "fetch", "jq", "file-search"];
8717
8677
  });
@@ -9014,7 +8974,7 @@ var init_echo = __esm(() => {
9014
8974
 
9015
8975
  // src/tools/exec.ts
9016
8976
  import { spawn } from "child_process";
9017
- import { basename as basename2, join as join4, resolve as resolve3 } from "path";
8977
+ import { basename as basename2, join as join6, resolve as resolve3 } from "path";
9018
8978
  function sanitizeSkillId(skillId) {
9019
8979
  return skillId.replace(/[^a-z0-9_-]/gi, "_");
9020
8980
  }
@@ -9048,7 +9008,7 @@ var init_exec = __esm(() => {
9048
9008
  const skillId = sanitizeSkillId(String(input.skill_id).trim());
9049
9009
  if (!skillId)
9050
9010
  return { ok: false, stdout: "", stderr: "skill_id must be non-empty", exitCode: -1 };
9051
- cwd = join4(getSkillsDir(), skillId);
9011
+ cwd = join6(getSkillsDir(), skillId);
9052
9012
  const skillConfig = await readSkillConfig(skillId);
9053
9013
  for (const [k, v] of Object.entries(skillConfig)) {
9054
9014
  if (typeof v === "string")
@@ -9172,7 +9132,7 @@ var init_tools = __esm(() => {
9172
9132
 
9173
9133
  // src/core/events.ts
9174
9134
  import { mkdir as mkdir4, appendFile } from "fs/promises";
9175
- import { join as join5 } from "path";
9135
+ import { join as join7 } from "path";
9176
9136
  function registerEventHooks(hooks) {
9177
9137
  for (const [type, handler] of Object.entries(hooks)) {
9178
9138
  if (type && handler && typeof handler === "function") {
@@ -9246,8 +9206,8 @@ var subscribers, recent, MAX_RECENT = 200, LOG_DIR, LOG_FILE, logInitPromise = n
9246
9206
  var init_events = __esm(() => {
9247
9207
  subscribers = new Map;
9248
9208
  recent = [];
9249
- LOG_DIR = process.env.AGENT_OS_LOGS_DIR || join5(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "logs");
9250
- LOG_FILE = join5(LOG_DIR, "agent-os.log");
9209
+ LOG_DIR = process.env.AGENT_OS_LOGS_DIR || join7(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "logs");
9210
+ LOG_FILE = join7(LOG_DIR, "agent-os.log");
9251
9211
  for (const type of [
9252
9212
  "task.created",
9253
9213
  "task.started",
@@ -9263,8 +9223,8 @@ var init_events = __esm(() => {
9263
9223
  });
9264
9224
 
9265
9225
  // src/core/runtime.ts
9266
- import { readFile as readFile4 } from "fs/promises";
9267
- import { join as join6 } from "path";
9226
+ import { readFile as readFile6 } from "fs/promises";
9227
+ import { join as join8 } from "path";
9268
9228
  function formatLLMErrorForUser(err) {
9269
9229
  const msg = errorMessage(err);
9270
9230
  if (msg.includes("429") || /rate_limit|rate limit/i.test(msg)) {
@@ -9688,7 +9648,7 @@ async function readWorkspacePromptContext(agentId) {
9688
9648
  const chunks = [];
9689
9649
  for (const name of WORKSPACE_PROMPT_FILES) {
9690
9650
  try {
9691
- const content = await readFile4(join6(workspaceDir, name), "utf-8");
9651
+ const content = await readFile6(join8(workspaceDir, name), "utf-8");
9692
9652
  const trimmed = content.trim();
9693
9653
  if (trimmed)
9694
9654
  chunks.push(trimmed);
@@ -9770,6 +9730,14 @@ function jsonResponse(data, status = 200) {
9770
9730
  headers: CORS_HEADERS
9771
9731
  });
9772
9732
  }
9733
+ async function parseJsonBody(req) {
9734
+ try {
9735
+ const body = await req.json();
9736
+ return { ok: true, body };
9737
+ } catch {
9738
+ return { ok: false, response: jsonResponse({ error: "Invalid JSON body" }, 400) };
9739
+ }
9740
+ }
9773
9741
  function getConversationHistoryForRun(memoryStore, conversationId, limit = 40) {
9774
9742
  const rows = memoryStore.getHistoryForConversation(conversationId, limit);
9775
9743
  return rows.map((row) => {
@@ -9863,12 +9831,10 @@ async function handleMemoryWrite(req, store) {
9863
9831
  if (req.method !== "POST") {
9864
9832
  return jsonResponse({ error: "Method not allowed" }, 405);
9865
9833
  }
9866
- let body;
9867
- try {
9868
- body = await req.json();
9869
- } catch {
9870
- return jsonResponse({ error: "Invalid JSON body" }, 400);
9871
- }
9834
+ const parsed = await parseJsonBody(req);
9835
+ if (!parsed.ok)
9836
+ return parsed.response;
9837
+ const body = parsed.body;
9872
9838
  const agent_id = typeof body.agent_id === "string" ? body.agent_id.trim() : "";
9873
9839
  const text = typeof body.text === "string" ? body.text.trim() : "";
9874
9840
  const user_id = typeof body.user_id === "string" ? body.user_id.trim() : null;
@@ -9969,12 +9935,10 @@ var init_memory = __esm(() => {
9969
9935
  // src/http/conversations.ts
9970
9936
  async function handleConversations(req, url, store) {
9971
9937
  if (req.method === "POST") {
9972
- let body;
9973
- try {
9974
- body = await req.json();
9975
- } catch {
9976
- return jsonResponse({ error: "Invalid JSON body" }, 400);
9977
- }
9938
+ const parsed = await parseJsonBody(req);
9939
+ if (!parsed.ok)
9940
+ return parsed.response;
9941
+ const body = parsed.body;
9978
9942
  const graph_id = typeof body.graph_id === "string" ? body.graph_id.trim() : null;
9979
9943
  const agent_id = typeof body.agent_id === "string" ? body.agent_id.trim() : graph_id ?? "";
9980
9944
  const role = body.role;
@@ -10068,13 +10032,10 @@ async function handleConversations(req, url, store) {
10068
10032
  async function handleConversationUpdate(req, conversationId, store) {
10069
10033
  if (req.method !== "PUT")
10070
10034
  return jsonResponse({ error: "Method not allowed" }, 405);
10071
- let body;
10072
- try {
10073
- body = await req.json();
10074
- } catch {
10075
- return jsonResponse({ error: "Invalid JSON body" }, 400);
10076
- }
10077
- const title = typeof body.title === "string" ? body.title.trim().slice(0, 200) : "";
10035
+ const parsed = await parseJsonBody(req);
10036
+ if (!parsed.ok)
10037
+ return parsed.response;
10038
+ const title = typeof parsed.body.title === "string" ? parsed.body.title.trim().slice(0, 200) : "";
10078
10039
  try {
10079
10040
  store.updateConversationTitle(conversationId, title || null);
10080
10041
  return jsonResponse({ ok: true });
@@ -10086,13 +10047,10 @@ async function handleConversationUpdate(req, conversationId, store) {
10086
10047
  async function handleConversationSummarize(req, store) {
10087
10048
  if (req.method !== "POST")
10088
10049
  return jsonResponse({ error: "Method not allowed" }, 405);
10089
- let body;
10090
- try {
10091
- body = await req.json();
10092
- } catch {
10093
- return jsonResponse({ error: "Invalid JSON body" }, 400);
10094
- }
10095
- const conversation_id = body.conversation_id?.trim();
10050
+ const parsed = await parseJsonBody(req);
10051
+ if (!parsed.ok)
10052
+ return parsed.response;
10053
+ const conversation_id = parsed.body.conversation_id?.trim();
10096
10054
  if (!conversation_id) {
10097
10055
  return jsonResponse({ error: "conversation_id is required" }, 400);
10098
10056
  }
@@ -10110,9 +10068,9 @@ async function handleConversationSummarize(req, store) {
10110
10068
  let text = row.content;
10111
10069
  if (typeof text === "string" && text.startsWith("{")) {
10112
10070
  try {
10113
- const parsed = JSON.parse(text);
10114
- if (typeof parsed.text === "string")
10115
- text = parsed.text;
10071
+ const parsed2 = JSON.parse(text);
10072
+ if (typeof parsed2.text === "string")
10073
+ text = parsed2.text;
10116
10074
  } catch {}
10117
10075
  }
10118
10076
  const prefix = row.role === "user" ? "User" : "Assistant";
@@ -11457,15 +11415,15 @@ var require_node_cron = __commonJS((exports) => {
11457
11415
  });
11458
11416
 
11459
11417
  // src/core/graphs.ts
11460
- import { readFile as readFile5, readdir as readdir3, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
11461
- import { join as join7 } from "path";
11418
+ import { readFile as readFile7, readdir as readdir4, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
11419
+ import { join as join9 } from "path";
11462
11420
  function getGraphsDir() {
11463
11421
  return process.env.AGENT_OS_GRAPHS_DIR || DEFAULT_GRAPHS_DIR;
11464
11422
  }
11465
11423
  async function listGraphs() {
11466
11424
  const dir = getGraphsDir();
11467
11425
  try {
11468
- const entries = await readdir3(dir);
11426
+ const entries = await readdir4(dir);
11469
11427
  const summaries = [];
11470
11428
  for (const name of entries) {
11471
11429
  if (name.endsWith(".json")) {
@@ -11484,11 +11442,11 @@ async function listGraphs() {
11484
11442
  async function loadGraph(id) {
11485
11443
  const dir = getGraphsDir();
11486
11444
  try {
11487
- const entries = await readdir3(dir);
11445
+ const entries = await readdir4(dir);
11488
11446
  const file = entries.find((name) => name === `${id}.json` || name === `${id}.graph.json`);
11489
11447
  if (!file)
11490
11448
  return null;
11491
- const raw = await readFile5(join7(dir, file), "utf-8");
11449
+ const raw = await readFile7(join9(dir, file), "utf-8");
11492
11450
  const parsed = JSON.parse(raw);
11493
11451
  validateGraph(parsed);
11494
11452
  return parsed;
@@ -11503,7 +11461,7 @@ async function saveGraph(graph) {
11503
11461
  validateGraph(graph);
11504
11462
  const dir = getGraphsDir();
11505
11463
  await mkdir5(dir, { recursive: true });
11506
- const path = join7(dir, `${graph.id}.json`);
11464
+ const path = join9(dir, `${graph.id}.json`);
11507
11465
  await writeFile4(path, JSON.stringify(graph, null, 2), "utf-8");
11508
11466
  }
11509
11467
  function validateGraph(graph) {
@@ -11678,14 +11636,14 @@ var DEFAULT_GRAPHS_DIR;
11678
11636
  var init_graphs = __esm(() => {
11679
11637
  init_agent_registry();
11680
11638
  init_runtime();
11681
- DEFAULT_GRAPHS_DIR = join7(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "graphs");
11639
+ DEFAULT_GRAPHS_DIR = join9(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "graphs");
11682
11640
  });
11683
11641
 
11684
11642
  // src/core/tasks.ts
11685
11643
  import { randomUUID } from "crypto";
11686
11644
  import os from "os";
11687
- import { readFile as readFile6, appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
11688
- import { join as join8 } from "path";
11645
+ import { readFile as readFile8, appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
11646
+ import { join as join10 } from "path";
11689
11647
  function nowIso() {
11690
11648
  return new Date().toISOString();
11691
11649
  }
@@ -11747,7 +11705,7 @@ class MemoryTaskStore {
11747
11705
  }
11748
11706
  async loadFromDisk() {
11749
11707
  try {
11750
- const raw = await readFile6(TASKS_FILE, "utf-8");
11708
+ const raw = await readFile8(TASKS_FILE, "utf-8");
11751
11709
  const lines = raw.split(`
11752
11710
  `);
11753
11711
  for (const line of lines) {
@@ -11929,23 +11887,20 @@ var init_tasks = __esm(() => {
11929
11887
  init_graphs();
11930
11888
  init_events();
11931
11889
  import_node_cron = __toESM(require_node_cron(), 1);
11932
- TASKS_DIR = process.env.AGENT_OS_TASKS_DIR || join8(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "tasks");
11933
- TASKS_FILE = join8(TASKS_DIR, "tasks.log");
11890
+ TASKS_DIR = process.env.AGENT_OS_TASKS_DIR || join10(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "tasks");
11891
+ TASKS_FILE = join10(TASKS_DIR, "tasks.log");
11934
11892
  memoryTaskStore = new MemoryTaskStore;
11935
11893
  maxWorkers = Math.max(1, (os.cpus?.().length ?? 1) * 2);
11936
11894
  });
11937
11895
 
11938
11896
  // src/http/handlers.ts
11939
- import { readFile as readFile7, writeFile as writeFile5, stat } from "fs/promises";
11940
- import { join as join9 } from "path";
11897
+ import { readFile as readFile9, writeFile as writeFile5, stat } from "fs/promises";
11898
+ import { join as join11 } from "path";
11941
11899
  async function handleRun(req, memoryStore) {
11942
- let body;
11943
- try {
11944
- body = await req.json();
11945
- } catch {
11946
- return jsonResponse({ error: "Invalid JSON body" }, 400);
11947
- }
11948
- const { agent_id, task, conversation_id } = body;
11900
+ const parsed = await parseJsonBody(req);
11901
+ if (!parsed.ok)
11902
+ return parsed.response;
11903
+ const { agent_id, task, conversation_id } = parsed.body;
11949
11904
  if (!agent_id || typeof task !== "string") {
11950
11905
  return jsonResponse({ error: "Missing required fields: agent_id, task" }, 400);
11951
11906
  }
@@ -11969,13 +11924,10 @@ async function handleRun(req, memoryStore) {
11969
11924
  }
11970
11925
  }
11971
11926
  async function handleRunStream(req, memoryStore) {
11972
- let body;
11973
- try {
11974
- body = await req.json();
11975
- } catch {
11976
- return jsonResponse({ error: "Invalid JSON body" }, 400);
11977
- }
11978
- const { agent_id, task, conversation_id, attachment_paths } = body;
11927
+ const parsed = await parseJsonBody(req);
11928
+ if (!parsed.ok)
11929
+ return parsed.response;
11930
+ const { agent_id, task, conversation_id, attachment_paths } = parsed.body;
11979
11931
  if (!agent_id || typeof task !== "string") {
11980
11932
  return jsonResponse({ error: "Missing required fields: agent_id, task" }, 400);
11981
11933
  }
@@ -12062,13 +12014,10 @@ async function handleTasks(req, url) {
12062
12014
  return jsonResponse({ tasks });
12063
12015
  }
12064
12016
  if (req.method === "POST") {
12065
- let body;
12066
- try {
12067
- body = await req.json();
12068
- } catch {
12069
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12070
- }
12071
- const { agent_id, graph_id, task } = body;
12017
+ const parsed = await parseJsonBody(req);
12018
+ if (!parsed.ok)
12019
+ return parsed.response;
12020
+ const { agent_id, graph_id, task } = parsed.body;
12072
12021
  if (typeof task !== "string") {
12073
12022
  return jsonResponse({ error: "Missing required field: task" }, 400);
12074
12023
  }
@@ -12092,12 +12041,10 @@ async function handleLogs(req) {
12092
12041
  return jsonResponse({ events });
12093
12042
  }
12094
12043
  async function handleSkillInstall(req) {
12095
- let body;
12096
- try {
12097
- body = await req.json();
12098
- } catch {
12099
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12100
- }
12044
+ const parsed = await parseJsonBody(req);
12045
+ if (!parsed.ok)
12046
+ return parsed.response;
12047
+ const body = parsed.body;
12101
12048
  const hasPath = typeof body.path === "string" && body.path.trim().length > 0;
12102
12049
  const hasUrl = typeof body.url === "string" && body.url.trim().length > 0;
12103
12050
  if (hasPath && hasUrl) {
@@ -12189,16 +12136,16 @@ async function handleSettings(req) {
12189
12136
  signal_default_agent_id: config.signal_default_agent_id ?? null,
12190
12137
  signal_bridge_url: config.signal_bridge_url ?? null,
12191
12138
  viber_configured: Boolean(config.viber_auth_token?.trim()),
12192
- viber_default_agent_id: config.viber_default_agent_id ?? null
12139
+ viber_default_agent_id: config.viber_default_agent_id ?? null,
12140
+ onboarding_completed: config.onboarding_completed === true
12193
12141
  });
12194
12142
  }
12195
12143
  if (req.method === "PUT") {
12196
12144
  let body;
12197
- try {
12198
- body = await req.json();
12199
- } catch {
12200
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12201
- }
12145
+ const parsed = await parseJsonBody(req);
12146
+ if (!parsed.ok)
12147
+ return parsed.response;
12148
+ body = parsed.body;
12202
12149
  const current = await readConfig();
12203
12150
  const provider = body.provider === "openrouter" || body.provider === "openai" ? body.provider : undefined;
12204
12151
  const rawKey = typeof body.api_key === "string" ? body.api_key.trim() : undefined;
@@ -12219,6 +12166,7 @@ async function handleSettings(req) {
12219
12166
  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;
12220
12167
  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;
12221
12168
  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;
12169
+ const onboarding_completed = body.onboarding_completed !== undefined ? body.onboarding_completed === true ? true : undefined : current.onboarding_completed;
12222
12170
  await writeConfig({
12223
12171
  provider: provider ?? current.provider,
12224
12172
  api_key: api_key || undefined,
@@ -12237,7 +12185,8 @@ async function handleSettings(req) {
12237
12185
  signal_bridge_url: signal_bridge_url || undefined,
12238
12186
  signal_default_agent_id: signal_default_agent_id || undefined,
12239
12187
  viber_auth_token: viber_auth_token || undefined,
12240
- viber_default_agent_id: viber_default_agent_id || undefined
12188
+ viber_default_agent_id: viber_default_agent_id || undefined,
12189
+ onboarding_completed: onboarding_completed ?? undefined
12241
12190
  });
12242
12191
  return jsonResponse({ ok: true });
12243
12192
  }
@@ -12247,13 +12196,10 @@ async function handleGraphRun(req) {
12247
12196
  if (req.method !== "POST") {
12248
12197
  return jsonResponse({ error: "Method not allowed" }, 405);
12249
12198
  }
12250
- let body;
12251
- try {
12252
- body = await req.json();
12253
- } catch {
12254
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12255
- }
12256
- const { graph_id, input } = body;
12199
+ const parsed = await parseJsonBody(req);
12200
+ if (!parsed.ok)
12201
+ return parsed.response;
12202
+ const { graph_id, input } = parsed.body;
12257
12203
  if (!graph_id || typeof input !== "string") {
12258
12204
  return jsonResponse({ error: "Missing required fields: graph_id, input" }, 400);
12259
12205
  }
@@ -12270,13 +12216,10 @@ async function handleGraphRun(req) {
12270
12216
  }
12271
12217
  }
12272
12218
  async function handleGraphRunStream(req) {
12273
- let body;
12274
- try {
12275
- body = await req.json();
12276
- } catch {
12277
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12278
- }
12279
- const { graph_id, input } = body;
12219
+ const parsed = await parseJsonBody(req);
12220
+ if (!parsed.ok)
12221
+ return parsed.response;
12222
+ const { graph_id, input } = parsed.body;
12280
12223
  if (!graph_id || typeof input !== "string") {
12281
12224
  return jsonResponse({ error: "Missing required fields: graph_id, input" }, 400);
12282
12225
  }
@@ -12331,13 +12274,10 @@ function extractJsonFromContent(content) {
12331
12274
  return trimmed;
12332
12275
  }
12333
12276
  async function handleAgentSuggest(req) {
12334
- let body;
12335
- try {
12336
- body = await req.json();
12337
- } catch {
12338
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12339
- }
12340
- const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
12277
+ const parsed = await parseJsonBody(req);
12278
+ if (!parsed.ok)
12279
+ return parsed.response;
12280
+ const prompt = typeof parsed.body.prompt === "string" ? parsed.body.prompt.trim() : "";
12341
12281
  if (!prompt)
12342
12282
  return jsonResponse({ error: "Missing prompt" }, 400);
12343
12283
  const skills = await listSkills();
@@ -12378,13 +12318,13 @@ Rules:
12378
12318
  if (!content)
12379
12319
  return jsonResponse({ error: "No suggestion from model" }, 502);
12380
12320
  const raw = extractJsonFromContent(content);
12381
- const parsed = JSON.parse(raw);
12382
- const name = typeof parsed.name === "string" ? parsed.name.trim() : "Agent";
12383
- const id = typeof parsed.id === "string" ? parsed.id.trim().toLowerCase().replace(/[^a-z0-9_.-]/g, "_").replace(/\s+/g, "_") || "agent" : "agent";
12384
- const description = typeof parsed.description === "string" ? parsed.description.trim() : "";
12385
- const skillsOut = Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && skillIds.includes(s)) : [];
12386
- const schedule = typeof parsed.schedule === "string" ? parsed.schedule.trim() : "";
12387
- const schedule_input = typeof parsed.schedule_input === "string" ? parsed.schedule_input.trim() : "";
12321
+ const parsed2 = JSON.parse(raw);
12322
+ const name = typeof parsed2.name === "string" ? parsed2.name.trim() : "Agent";
12323
+ const id = typeof parsed2.id === "string" ? parsed2.id.trim().toLowerCase().replace(/[^a-z0-9_.-]/g, "_").replace(/\s+/g, "_") || "agent" : "agent";
12324
+ const description = typeof parsed2.description === "string" ? parsed2.description.trim() : "";
12325
+ const skillsOut = Array.isArray(parsed2.skills) ? parsed2.skills.filter((s) => typeof s === "string" && skillIds.includes(s)) : [];
12326
+ const schedule = typeof parsed2.schedule === "string" ? parsed2.schedule.trim() : "";
12327
+ const schedule_input = typeof parsed2.schedule_input === "string" ? parsed2.schedule_input.trim() : "";
12388
12328
  const suggestion = {
12389
12329
  name,
12390
12330
  id,
@@ -12419,10 +12359,10 @@ async function handleAgentUpload(req, agentId) {
12419
12359
  const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
12420
12360
  const filename = `${unique}_${basename3}`;
12421
12361
  await ensureWorkspace(agentId);
12422
- const uploadsDir = join9(getWorkspaceDir(agentId), "uploads");
12362
+ const uploadsDir = join11(getWorkspaceDir(agentId), "uploads");
12423
12363
  const { mkdir: mkdir7 } = await import("fs/promises");
12424
12364
  await mkdir7(uploadsDir, { recursive: true });
12425
- const absolutePath = join9(uploadsDir, filename);
12365
+ const absolutePath = join11(uploadsDir, filename);
12426
12366
  const buffer = Buffer.from(await file.arrayBuffer());
12427
12367
  await writeFile5(absolutePath, buffer);
12428
12368
  return jsonResponse({ path: absolutePath });
@@ -12454,7 +12394,7 @@ async function handleGetAgentWorkspaceFile(agentId, filename) {
12454
12394
  const msg = errorMessage(err);
12455
12395
  return jsonResponse({ error: msg }, 500);
12456
12396
  }
12457
- const buf = await readFile7(absolutePath);
12397
+ const buf = await readFile9(absolutePath);
12458
12398
  const ext = basename3.replace(/^.*\./, "").toLowerCase();
12459
12399
  const mime = {
12460
12400
  png: "image/png",
@@ -12524,8 +12464,8 @@ async function handleGetDoc(name) {
12524
12464
  return jsonResponse({ error: "Unknown doc" }, 404);
12525
12465
  }
12526
12466
  try {
12527
- const path = join9(process.cwd(), "doc", filename);
12528
- const content = await readFile7(path, "utf-8");
12467
+ const path = join11(process.cwd(), "doc", filename);
12468
+ const content = await readFile9(path, "utf-8");
12529
12469
  return jsonResponse({ content });
12530
12470
  } catch (err) {
12531
12471
  const code = err?.code;
@@ -12571,6 +12511,15 @@ var init_handlers = __esm(() => {
12571
12511
  });
12572
12512
 
12573
12513
  // src/http/channel-run.ts
12514
+ async function getDefaultAgent(config, defaultAgentIdKey) {
12515
+ const agents = await loadAgents();
12516
+ const id = config[defaultAgentIdKey];
12517
+ const defaultAgentId = typeof id === "string" ? id.trim() : "";
12518
+ if (defaultAgentId) {
12519
+ return agents.find((a) => a.id === defaultAgentId) ?? null;
12520
+ }
12521
+ return agents[0] ?? null;
12522
+ }
12574
12523
  async function runAgentWithConversation(memoryStore, agent, conversationId, userId, task, sendReply) {
12575
12524
  memoryStore.ensureConversation(conversationId, agent.id, userId);
12576
12525
  const conversationHistory = getConversationHistoryForRun(memoryStore, conversationId);
@@ -12593,6 +12542,7 @@ async function runAgentWithConversation(memoryStore, agent, conversationId, user
12593
12542
  await sendReply(output);
12594
12543
  }
12595
12544
  var init_channel_run = __esm(() => {
12545
+ init_agent_registry();
12596
12546
  init_runtime();
12597
12547
  init_utils();
12598
12548
  });
@@ -12602,13 +12552,10 @@ async function handleTelegramSetWebhook(req) {
12602
12552
  if (req.method !== "POST") {
12603
12553
  return jsonResponse({ error: "Method not allowed" }, 405);
12604
12554
  }
12605
- let body;
12606
- try {
12607
- body = await req.json();
12608
- } catch {
12609
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12610
- }
12611
- const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : "";
12555
+ const parsed = await parseJsonBody(req);
12556
+ if (!parsed.ok)
12557
+ return parsed.response;
12558
+ const baseUrl = typeof parsed.body.base_url === "string" ? parsed.body.base_url.trim() : "";
12612
12559
  if (!baseUrl) {
12613
12560
  return jsonResponse({ error: "base_url is required" }, 400);
12614
12561
  }
@@ -12684,11 +12631,9 @@ async function processTelegramUpdate(memoryStore, body) {
12684
12631
  const botToken = config.telegram_bot_token?.trim();
12685
12632
  if (!botToken)
12686
12633
  return;
12687
- const agents = await loadAgents();
12688
- const defaultAgentId = config.telegram_default_agent_id?.trim();
12689
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
12634
+ const agent = await getDefaultAgent(config, "telegram_default_agent_id");
12690
12635
  if (!agent) {
12691
- await sendTelegramMessage(botToken, chatId, "No agent configured. Add an agent and set Telegram default agent in Settings.");
12636
+ await sendTelegramMessage(botToken, chatId, "No agent configured. Set default agent in Settings.");
12692
12637
  return;
12693
12638
  }
12694
12639
  if (!text) {
@@ -12702,12 +12647,10 @@ async function handleTelegramWebhook(req, memoryStore) {
12702
12647
  if (req.method !== "POST") {
12703
12648
  return jsonResponse({ error: "Method not allowed" }, 405);
12704
12649
  }
12705
- let body;
12706
- try {
12707
- body = await req.json();
12708
- } catch {
12709
- return jsonResponse({ error: "Invalid JSON body" }, 400);
12710
- }
12650
+ const parsed = await parseJsonBody(req);
12651
+ if (!parsed.ok)
12652
+ return parsed.response;
12653
+ const body = parsed.body;
12711
12654
  const message = body.message;
12712
12655
  if (!message?.chat?.id) {
12713
12656
  return jsonResponse({ ok: true });
@@ -12776,7 +12719,6 @@ function startTelegramPolling(memoryStore) {
12776
12719
  var POLLING_INTERVAL_MS = 1000, GET_UPDATES_TIMEOUT = 30;
12777
12720
  var init_telegram = __esm(() => {
12778
12721
  init_config();
12779
- init_agent_registry();
12780
12722
  init_utils();
12781
12723
  init_channel_run();
12782
12724
  });
@@ -12834,11 +12776,9 @@ async function processSlackEvent(memoryStore, payload) {
12834
12776
  const botToken = config.slack_bot_token?.trim();
12835
12777
  if (!botToken)
12836
12778
  return;
12837
- const agents = await loadAgents();
12838
- const defaultAgentId = config.slack_default_agent_id?.trim();
12839
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
12779
+ const agent = await getDefaultAgent(config, "slack_default_agent_id");
12840
12780
  if (!agent) {
12841
- await sendSlackMessage(botToken, event.channel, "No agent configured. Set Slack default agent in Settings.");
12781
+ await sendSlackMessage(botToken, event.channel, "No agent configured. Set default agent in Settings.");
12842
12782
  return;
12843
12783
  }
12844
12784
  const userId = event.user ?? "unknown";
@@ -12893,7 +12833,6 @@ async function handleSlackWebhook(req, memoryStore) {
12893
12833
  }
12894
12834
  var init_slack = __esm(() => {
12895
12835
  init_config();
12896
- init_agent_registry();
12897
12836
  init_utils();
12898
12837
  init_channel_run();
12899
12838
  });
@@ -15248,11 +15187,9 @@ async function processDiscordInteraction(memoryStore, interaction) {
15248
15187
  const botToken = config.discord_bot_token?.trim();
15249
15188
  if (!botToken)
15250
15189
  return;
15251
- const agents = await loadAgents();
15252
- const defaultAgentId = config.discord_default_agent_id?.trim();
15253
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
15190
+ const agent = await getDefaultAgent(config, "discord_default_agent_id");
15254
15191
  if (!agent) {
15255
- await sendDiscordFollowup(appId, token, botToken, "No agent configured. Set Discord default agent in Settings.");
15192
+ await sendDiscordFollowup(appId, token, botToken, "No agent configured. Set default agent in Settings.");
15256
15193
  return;
15257
15194
  }
15258
15195
  const conversationId = `discord:${channelId}:${userId}`;
@@ -15312,7 +15249,6 @@ async function handleDiscordWebhook(req, memoryStore) {
15312
15249
  var import_tweetnacl, INTERACTION_TYPE_PING = 1, INTERACTION_TYPE_APPLICATION_COMMAND = 2, INTERACTION_RESPONSE_DEFERRED = 5;
15313
15250
  var init_discord = __esm(() => {
15314
15251
  init_config();
15315
- init_agent_registry();
15316
15252
  init_utils();
15317
15253
  init_channel_run();
15318
15254
  import_tweetnacl = __toESM(require_nacl_fast(), 1);
@@ -15341,11 +15277,9 @@ async function processSignalWebhook(memoryStore, body) {
15341
15277
  const bridgeUrl = config.signal_bridge_url?.trim();
15342
15278
  if (!bridgeUrl)
15343
15279
  return;
15344
- const agents = await loadAgents();
15345
- const defaultAgentId = config.signal_default_agent_id?.trim();
15346
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
15280
+ const agent = await getDefaultAgent(config, "signal_default_agent_id");
15347
15281
  if (!agent) {
15348
- await sendSignalMessage(bridgeUrl, from, "No agent configured. Set Signal default agent in Settings.");
15282
+ await sendSignalMessage(bridgeUrl, from, "No agent configured. Set default agent in Settings.");
15349
15283
  return;
15350
15284
  }
15351
15285
  if (!text) {
@@ -15364,12 +15298,10 @@ async function handleSignalWebhook(req, memoryStore) {
15364
15298
  if (req.method !== "POST") {
15365
15299
  return jsonResponse({ error: "Method not allowed" }, 405);
15366
15300
  }
15367
- let body;
15368
- try {
15369
- body = await req.json();
15370
- } catch {
15371
- return jsonResponse({ error: "Invalid JSON body" }, 400);
15372
- }
15301
+ const parsed = await parseJsonBody(req);
15302
+ if (!parsed.ok)
15303
+ return parsed.response;
15304
+ const body = parsed.body;
15373
15305
  const config = await readConfig();
15374
15306
  if (!config.signal_bridge_url?.trim()) {
15375
15307
  console.warn("[signal] Webhook received but signal_bridge_url not configured");
@@ -15385,7 +15317,6 @@ async function handleSignalWebhook(req, memoryStore) {
15385
15317
  }
15386
15318
  var init_signal = __esm(() => {
15387
15319
  init_config();
15388
- init_agent_registry();
15389
15320
  init_utils();
15390
15321
  init_channel_run();
15391
15322
  });
@@ -15410,13 +15341,10 @@ async function handleViberSetWebhook(req) {
15410
15341
  if (req.method !== "POST") {
15411
15342
  return jsonResponse({ error: "Method not allowed" }, 405);
15412
15343
  }
15413
- let body;
15414
- try {
15415
- body = await req.json();
15416
- } catch {
15417
- return jsonResponse({ error: "Invalid JSON body" }, 400);
15418
- }
15419
- const baseUrl = typeof body.base_url === "string" ? body.base_url.trim() : "";
15344
+ const parsed = await parseJsonBody(req);
15345
+ if (!parsed.ok)
15346
+ return parsed.response;
15347
+ const baseUrl = typeof parsed.body.base_url === "string" ? parsed.body.base_url.trim() : "";
15420
15348
  if (!baseUrl) {
15421
15349
  return jsonResponse({ error: "base_url is required" }, 400);
15422
15350
  }
@@ -15517,11 +15445,9 @@ async function processViberCallback(memoryStore, payload) {
15517
15445
  const authToken = config.viber_auth_token?.trim();
15518
15446
  if (!authToken)
15519
15447
  return;
15520
- const agents = await loadAgents();
15521
- const defaultAgentId = config.viber_default_agent_id?.trim();
15522
- const agent = defaultAgentId ? agents.find((a) => a.id === defaultAgentId) : agents[0];
15448
+ const agent = await getDefaultAgent(config, "viber_default_agent_id");
15523
15449
  if (!agent) {
15524
- await sendViberMessage(authToken, senderId, "No agent configured. Set Viber default agent in Settings.");
15450
+ await sendViberMessage(authToken, senderId, "No agent configured. Set default agent in Settings.");
15525
15451
  return;
15526
15452
  }
15527
15453
  const conversationId = `viber:${senderId}`;
@@ -15568,14 +15494,13 @@ async function handleViberWebhook(req, memoryStore) {
15568
15494
  var VIBER_API = "https://chatapi.viber.com/pa", SENDER_NAME_MAX = 28;
15569
15495
  var init_viber = __esm(() => {
15570
15496
  init_config();
15571
- init_agent_registry();
15572
15497
  init_utils();
15573
15498
  init_channel_run();
15574
15499
  });
15575
15500
 
15576
15501
  // src/core/plugins.ts
15577
- import { readdir as readdir4, stat as stat2 } from "fs/promises";
15578
- import { join as join10 } from "path";
15502
+ import { readdir as readdir5, stat as stat2 } from "fs/promises";
15503
+ import { join as join12 } from "path";
15579
15504
  function getPluginsDir() {
15580
15505
  return process.env.AGENT_OS_PLUGINS_DIR || DEFAULT_PLUGINS_DIR;
15581
15506
  }
@@ -15589,7 +15514,7 @@ async function loadPlugins() {
15589
15514
  const dir = getPluginsDir();
15590
15515
  let entries = [];
15591
15516
  try {
15592
- entries = await readdir4(dir);
15517
+ entries = await readdir5(dir);
15593
15518
  } catch (err) {
15594
15519
  if (err?.code === "ENOENT")
15595
15520
  return;
@@ -15600,12 +15525,12 @@ async function loadPlugins() {
15600
15525
  for (const name of entries) {
15601
15526
  if (name.startsWith("."))
15602
15527
  continue;
15603
- const pluginPath = join10(dir, name);
15528
+ const pluginPath = join12(dir, name);
15604
15529
  let modulePath;
15605
15530
  try {
15606
15531
  const st = await stat2(pluginPath);
15607
15532
  if (st.isDirectory()) {
15608
- modulePath = join10(pluginPath, "index.js");
15533
+ modulePath = join12(pluginPath, "index.js");
15609
15534
  } else if (name.endsWith(".js") || name.endsWith(".ts")) {
15610
15535
  modulePath = pluginPath;
15611
15536
  } else {
@@ -15628,7 +15553,7 @@ var DEFAULT_PLUGINS_DIR;
15628
15553
  var init_plugins = __esm(() => {
15629
15554
  init_events();
15630
15555
  init_config();
15631
- DEFAULT_PLUGINS_DIR = join10(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "plugins");
15556
+ DEFAULT_PLUGINS_DIR = join12(process.env.HOME || process.env.USERPROFILE || "~", ".agent-os", "plugins");
15632
15557
  });
15633
15558
 
15634
15559
  // src/server.ts
@@ -15636,8 +15561,8 @@ var exports_server = {};
15636
15561
  __export(exports_server, {
15637
15562
  startServer: () => startServer
15638
15563
  });
15639
- import { join as join11, dirname } from "path";
15640
- import { mkdirSync } from "fs";
15564
+ import { join as join13, dirname, resolve as resolve4 } from "path";
15565
+ import { mkdirSync, existsSync } from "fs";
15641
15566
  function broadcastEvent(event) {
15642
15567
  const msg = JSON.stringify(event);
15643
15568
  for (const ws of wsClients) {
@@ -15646,6 +15571,44 @@ function broadcastEvent(event) {
15646
15571
  } catch {}
15647
15572
  }
15648
15573
  }
15574
+ function serveDashboard(pathname) {
15575
+ if (!existsSync(DASHBOARD_DIST) || !existsSync(join13(DASHBOARD_DIST, "index.html"))) {
15576
+ return null;
15577
+ }
15578
+ const decoded = decodeURIComponent(pathname);
15579
+ if (decoded.includes("..")) {
15580
+ return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
15581
+ }
15582
+ const subpath = decoded === "/" ? "index.html" : decoded.slice(1);
15583
+ const filePath = join13(DASHBOARD_DIST, subpath);
15584
+ if (existsSync(filePath)) {
15585
+ const file = Bun.file(filePath);
15586
+ const ext = subpath.split(".").pop() ?? "";
15587
+ const mime = {
15588
+ html: "text/html",
15589
+ js: "application/javascript",
15590
+ css: "text/css",
15591
+ json: "application/json",
15592
+ png: "image/png",
15593
+ jpg: "image/jpeg",
15594
+ jpeg: "image/jpeg",
15595
+ ico: "image/x-icon",
15596
+ svg: "image/svg+xml",
15597
+ woff: "font/woff",
15598
+ woff2: "font/woff2"
15599
+ };
15600
+ return new Response(file, {
15601
+ headers: { "Content-Type": mime[ext] ?? "application/octet-stream" }
15602
+ });
15603
+ }
15604
+ const indexPath = join13(DASHBOARD_DIST, "index.html");
15605
+ if (existsSync(indexPath)) {
15606
+ return new Response(Bun.file(indexPath), {
15607
+ headers: { "Content-Type": "text/html" }
15608
+ });
15609
+ }
15610
+ return null;
15611
+ }
15649
15612
  function createRoutes() {
15650
15613
  return {
15651
15614
  "/*": {
@@ -16018,6 +15981,17 @@ async function startServer() {
16018
15981
  return;
16019
15982
  return Response.json({ error: "Upgrade failed" }, { status: 500, headers: CORS_HEADERS });
16020
15983
  }
15984
+ if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
15985
+ const dashboardResponse = serveDashboard(url.pathname);
15986
+ if (dashboardResponse)
15987
+ return dashboardResponse;
15988
+ if (url.pathname === "/" || url.pathname === "") {
15989
+ return Response.json({
15990
+ error: "Dashboard not built",
15991
+ hint: "From the sulala package root run: cd dashboard && npm run build"
15992
+ }, { status: 404, headers: CORS_HEADERS });
15993
+ }
15994
+ }
16021
15995
  return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
16022
15996
  },
16023
15997
  error(error) {
@@ -16037,7 +16011,7 @@ async function startServer() {
16037
16011
  console.log(`Agent OS server running at ${server.url}`);
16038
16012
  startTelegramPolling(memoryStore);
16039
16013
  }
16040
- var PORT, HOST, EVENT_TYPES, wsClients, MEMORY_DB_PATH, memoryStore;
16014
+ var PORT, DASHBOARD_DIST, HOST, EVENT_TYPES, wsClients, MEMORY_DB_PATH, memoryStore;
16041
16015
  var init_server = __esm(() => {
16042
16016
  init_memory_store();
16043
16017
  init_utils();
@@ -16059,6 +16033,7 @@ var init_server = __esm(() => {
16059
16033
  init_loader();
16060
16034
  init_config();
16061
16035
  PORT = parseInt(process.env.PORT ?? "3010", 10);
16036
+ DASHBOARD_DIST = resolve4(join13(import.meta.dir, "..", "dashboard", "dist"));
16062
16037
  HOST = process.env.HOST ?? "127.0.0.1";
16063
16038
  EVENT_TYPES = [
16064
16039
  "task.created",
@@ -16071,7 +16046,7 @@ var init_server = __esm(() => {
16071
16046
  "tool.completed"
16072
16047
  ];
16073
16048
  wsClients = new Set;
16074
- MEMORY_DB_PATH = process.env.AGENT_MEMORY_DB_PATH ?? join11(getAgentOsHome(), "database.db");
16049
+ MEMORY_DB_PATH = process.env.AGENT_MEMORY_DB_PATH ?? join13(getAgentOsHome(), "database.db");
16075
16050
  mkdirSync(dirname(MEMORY_DB_PATH), { recursive: true });
16076
16051
  memoryStore = new MemoryStore(MEMORY_DB_PATH);
16077
16052
  setAgentStore(memoryStore);
@@ -16087,13 +16062,20 @@ init_agent_registry();
16087
16062
  init_loader();
16088
16063
  init_agent_registry();
16089
16064
  init_runtime();
16090
- import { join as join12, dirname as dirname2 } from "path";
16091
- import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir7, unlink as unlink2 } from "fs/promises";
16092
- import { existsSync, readFileSync } from "fs";
16093
- var PID_FILE = join12(getAgentOsHome(), "sulala.pid");
16065
+ import { join as join14, dirname as dirname2 } from "path";
16066
+ import { readFile as readFile10, writeFile as writeFile6, mkdir as mkdir7, unlink as unlink2 } from "fs/promises";
16067
+ import { existsSync as existsSync2, readFileSync } from "fs";
16068
+ var PID_FILE = join14(getAgentOsHome(), "sulala.pid");
16069
+ var DEFAULT_PORT = 3010;
16070
+ function openDashboard() {
16071
+ const port = parseInt(process.env.PORT ?? String(DEFAULT_PORT), 10);
16072
+ const url = `http://127.0.0.1:${port}`;
16073
+ const cmd = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
16074
+ Bun.spawn(cmd, { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
16075
+ }
16094
16076
  function getVersion() {
16095
16077
  try {
16096
- const path = join12(import.meta.dir, "..", "package.json");
16078
+ const path = join14(import.meta.dir, "..", "package.json");
16097
16079
  const raw = readFileSync(path, "utf-8");
16098
16080
  const pkg = JSON.parse(raw);
16099
16081
  return pkg.version ?? "0.0.0";
@@ -16102,8 +16084,8 @@ function getVersion() {
16102
16084
  }
16103
16085
  }
16104
16086
  async function getVersionAsync() {
16105
- const path = join12(import.meta.dir, "..", "package.json");
16106
- const raw = await readFile8(path, "utf-8");
16087
+ const path = join14(import.meta.dir, "..", "package.json");
16088
+ const raw = await readFile10(path, "utf-8");
16107
16089
  const pkg = JSON.parse(raw);
16108
16090
  return pkg.version ?? "0.0.0";
16109
16091
  }
@@ -16118,7 +16100,7 @@ Commands:
16118
16100
  version Show version
16119
16101
  start [--daemon] Start the server (default: foreground)
16120
16102
  stop Stop the server (when started with --daemon)
16121
- onboard First-time setup: create config, seed agents & skills
16103
+ onboard First-time setup: create config, seed agents & skills, open dashboard
16122
16104
  update Update system agents and skills
16123
16105
  run <agent_id> <task> Run an agent with a one-off task
16124
16106
 
@@ -16140,9 +16122,9 @@ async function cmdStart(args) {
16140
16122
  const daemon = args.includes("--daemon");
16141
16123
  if (daemon) {
16142
16124
  await mkdir7(getAgentOsHome(), { recursive: true });
16143
- const projectRoot = join12(import.meta.dir, "..");
16144
- const distEntry = join12(projectRoot, "dist", "index.js");
16145
- const serverEntry = existsSync(distEntry) ? "dist/index.js" : "src/index.ts";
16125
+ const projectRoot = join14(import.meta.dir, "..");
16126
+ const distEntry = join14(projectRoot, "dist", "index.js");
16127
+ const serverEntry = existsSync2(distEntry) ? "dist/index.js" : "src/index.ts";
16146
16128
  const child = Bun.spawn(["bun", "run", serverEntry], {
16147
16129
  cwd: projectRoot,
16148
16130
  stdout: "ignore",
@@ -16159,11 +16141,11 @@ async function cmdStart(args) {
16159
16141
  await startServer2();
16160
16142
  }
16161
16143
  async function cmdStop() {
16162
- if (!existsSync(PID_FILE)) {
16144
+ if (!existsSync2(PID_FILE)) {
16163
16145
  console.error("No PID file found. Is the server running with 'sulala start --daemon'?");
16164
16146
  process.exit(1);
16165
16147
  }
16166
- const pidStr = await readFile8(PID_FILE, "utf-8");
16148
+ const pidStr = await readFile10(PID_FILE, "utf-8");
16167
16149
  const pid = parseInt(pidStr.trim(), 10);
16168
16150
  if (Number.isNaN(pid)) {
16169
16151
  console.error("Invalid PID in", PID_FILE);
@@ -16184,14 +16166,14 @@ async function cmdStop() {
16184
16166
  console.log("Sulala server stopped.");
16185
16167
  }
16186
16168
  function getMemoryDbPath() {
16187
- return process.env.AGENT_MEMORY_DB_PATH ?? join12(getAgentOsHome(), "database.db");
16169
+ return process.env.AGENT_MEMORY_DB_PATH ?? join14(getAgentOsHome(), "database.db");
16188
16170
  }
16189
16171
  async function cmdOnboard() {
16190
16172
  const home = getAgentOsHome();
16191
16173
  await mkdir7(home, { recursive: true });
16192
16174
  await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
16193
- const configPath = join12(home, "config.json");
16194
- if (!existsSync(configPath)) {
16175
+ const configPath = join14(home, "config.json");
16176
+ if (!existsSync2(configPath)) {
16195
16177
  await writeFile6(configPath, JSON.stringify({
16196
16178
  provider: null,
16197
16179
  api_key: null,
@@ -16220,10 +16202,11 @@ async function cmdOnboard() {
16220
16202
  const { installed: agentsInstalled } = await installSystemAgents();
16221
16203
  const { installed: skillsInstalled } = await installSystemSkills();
16222
16204
  console.log("Onboard complete. Agents:", agentsInstalled, "Skills:", skillsInstalled);
16205
+ openDashboard();
16223
16206
  }
16224
16207
  async function cmdUpdate() {
16225
16208
  const dbPath = getMemoryDbPath();
16226
- if (!existsSync(dbPath)) {
16209
+ if (!existsSync2(dbPath)) {
16227
16210
  console.error("No database found. Run 'sulala onboard' first.");
16228
16211
  process.exit(1);
16229
16212
  }
@@ -16242,7 +16225,7 @@ async function cmdRun(args) {
16242
16225
  process.exit(1);
16243
16226
  }
16244
16227
  const dbPath = getMemoryDbPath();
16245
- if (existsSync(dbPath)) {
16228
+ if (existsSync2(dbPath)) {
16246
16229
  setAgentStore(new MemoryStore(dbPath));
16247
16230
  }
16248
16231
  const agent = await getAgent(agentId);