@sulala/agent-os 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +490 -459
- package/dist/index.js +335 -365
- 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/
|
|
7805
|
-
|
|
7806
|
-
|
|
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
|
-
|
|
7924
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
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
|
|
8015
|
-
|
|
8016
|
-
|
|
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
|
-
|
|
8021
|
-
return parsed;
|
|
8022
|
-
} catch (err) {
|
|
8023
|
-
console.error(`[skills] Failed to read SKILL.md in ${dir}:`, err);
|
|
8024
|
-
}
|
|
8036
|
+
} catch {}
|
|
8025
8037
|
}
|
|
8026
|
-
|
|
8027
|
-
|
|
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
|
|
8042
|
+
return "uploaded_skill";
|
|
8030
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);
|
|
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
|
|
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 =
|
|
8256
|
+
const dir = join5(getSkillsDir(), name);
|
|
8231
8257
|
let entries;
|
|
8232
8258
|
try {
|
|
8233
|
-
entries = await
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
8327
|
-
const mdPath =
|
|
8338
|
+
const skillDir = join5(dir, name);
|
|
8339
|
+
const mdPath = join5(skillDir, "SKILL.md");
|
|
8328
8340
|
try {
|
|
8329
|
-
const raw = await
|
|
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
|
|
8345
|
-
const path = join3(dir, "config.schema.json");
|
|
8356
|
+
const path = join5(getSkillsDir(), skillId, "config.schema.json");
|
|
8346
8357
|
try {
|
|
8347
|
-
const raw = await
|
|
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
|
|
8361
|
-
const mdPath = join3(dir, "SKILL.md");
|
|
8371
|
+
const mdPath = join5(getSkillsDir(), skillId, "SKILL.md");
|
|
8362
8372
|
try {
|
|
8363
|
-
const raw = await
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
8485
|
+
const skillDir = join5(dir, name);
|
|
8484
8486
|
let subEntries;
|
|
8485
8487
|
try {
|
|
8486
|
-
subEntries = await
|
|
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,
|
|
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 =
|
|
8544
|
-
const destPath =
|
|
8541
|
+
const sourcePath = join5(seedDir, id);
|
|
8542
|
+
const destPath = join5(skillsDir, id);
|
|
8545
8543
|
let hasSkillMd;
|
|
8546
8544
|
try {
|
|
8547
|
-
await access(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
8662
|
+
const destDir = join5(skillsDir, id);
|
|
8704
8663
|
await mkdir3(destDir, { recursive: true });
|
|
8705
|
-
await writeFile3(
|
|
8664
|
+
await writeFile3(join5(destDir, "SKILL.md"), text, "utf-8");
|
|
8706
8665
|
return { id };
|
|
8707
8666
|
}
|
|
8708
|
-
var
|
|
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
|
|
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 =
|
|
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
|
|
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 ||
|
|
9250
|
-
LOG_FILE =
|
|
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
|
|
9267
|
-
import { join as
|
|
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
|
|
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
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
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
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
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
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
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
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
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
|
|
10114
|
-
if (typeof
|
|
10115
|
-
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
|
|
11461
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
11688
|
-
import { join as
|
|
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
|
|
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 ||
|
|
11933
|
-
TASKS_FILE =
|
|
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
|
|
11940
|
-
import { join as
|
|
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
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
}
|
|
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
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
}
|
|
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
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
}
|
|
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
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
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
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
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
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
}
|
|
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
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
}
|
|
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
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
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
|
|
12382
|
-
const name = typeof
|
|
12383
|
-
const id = typeof
|
|
12384
|
-
const description = typeof
|
|
12385
|
-
const skillsOut = Array.isArray(
|
|
12386
|
-
const schedule = typeof
|
|
12387
|
-
const schedule_input = typeof
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
12528
|
-
const content = await
|
|
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
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
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
|
-
|
|
15414
|
-
|
|
15415
|
-
|
|
15416
|
-
|
|
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
|
|
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
|
|
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
|
|
15578
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ??
|
|
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
|
|
16091
|
-
import { readFile as
|
|
16092
|
-
import { existsSync, readFileSync } from "fs";
|
|
16093
|
-
var PID_FILE =
|
|
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 =
|
|
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 =
|
|
16106
|
-
const raw = await
|
|
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,8 +16100,8 @@ 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
|
|
16122
|
-
update Update
|
|
16103
|
+
onboard First-time setup: create config, seed agents & skills, open dashboard
|
|
16104
|
+
update Update package from npm and system agents/skills
|
|
16123
16105
|
run <agent_id> <task> Run an agent with a one-off task
|
|
16124
16106
|
|
|
16125
16107
|
Examples:
|
|
@@ -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 =
|
|
16144
|
-
const distEntry =
|
|
16145
|
-
const serverEntry =
|
|
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 (!
|
|
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
|
|
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,40 @@ async function cmdStop() {
|
|
|
16184
16166
|
console.log("Sulala server stopped.");
|
|
16185
16167
|
}
|
|
16186
16168
|
function getMemoryDbPath() {
|
|
16187
|
-
return process.env.AGENT_MEMORY_DB_PATH ??
|
|
16169
|
+
return process.env.AGENT_MEMORY_DB_PATH ?? join14(getAgentOsHome(), "database.db");
|
|
16170
|
+
}
|
|
16171
|
+
async function startServerDaemonIfNeeded() {
|
|
16172
|
+
if (existsSync2(PID_FILE)) {
|
|
16173
|
+
try {
|
|
16174
|
+
const pidStr = await readFile10(PID_FILE, "utf-8");
|
|
16175
|
+
const pid = parseInt(pidStr.trim(), 10);
|
|
16176
|
+
if (!Number.isNaN(pid)) {
|
|
16177
|
+
process.kill(pid, 0);
|
|
16178
|
+
return false;
|
|
16179
|
+
}
|
|
16180
|
+
} catch {}
|
|
16181
|
+
}
|
|
16182
|
+
await mkdir7(getAgentOsHome(), { recursive: true });
|
|
16183
|
+
const projectRoot = join14(import.meta.dir, "..");
|
|
16184
|
+
const distEntry = join14(projectRoot, "dist", "index.js");
|
|
16185
|
+
const serverEntry = existsSync2(distEntry) ? "dist/index.js" : "src/index.ts";
|
|
16186
|
+
const child = Bun.spawn(["bun", "run", serverEntry], {
|
|
16187
|
+
cwd: projectRoot,
|
|
16188
|
+
stdout: "ignore",
|
|
16189
|
+
stderr: "ignore",
|
|
16190
|
+
stdin: "ignore",
|
|
16191
|
+
detached: true
|
|
16192
|
+
});
|
|
16193
|
+
child.unref();
|
|
16194
|
+
await writeFile6(PID_FILE, String(child.pid), "utf-8");
|
|
16195
|
+
return true;
|
|
16188
16196
|
}
|
|
16189
16197
|
async function cmdOnboard() {
|
|
16190
16198
|
const home = getAgentOsHome();
|
|
16191
16199
|
await mkdir7(home, { recursive: true });
|
|
16192
16200
|
await mkdir7(dirname2(getMemoryDbPath()), { recursive: true });
|
|
16193
|
-
const configPath =
|
|
16194
|
-
if (!
|
|
16201
|
+
const configPath = join14(home, "config.json");
|
|
16202
|
+
if (!existsSync2(configPath)) {
|
|
16195
16203
|
await writeFile6(configPath, JSON.stringify({
|
|
16196
16204
|
provider: null,
|
|
16197
16205
|
api_key: null,
|
|
@@ -16220,12 +16228,35 @@ async function cmdOnboard() {
|
|
|
16220
16228
|
const { installed: agentsInstalled } = await installSystemAgents();
|
|
16221
16229
|
const { installed: skillsInstalled } = await installSystemSkills();
|
|
16222
16230
|
console.log("Onboard complete. Agents:", agentsInstalled, "Skills:", skillsInstalled);
|
|
16231
|
+
const started = await startServerDaemonIfNeeded();
|
|
16232
|
+
if (started) {
|
|
16233
|
+
console.log("Server starting in background. Use 'sulala stop' to stop.");
|
|
16234
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
16235
|
+
}
|
|
16236
|
+
openDashboard();
|
|
16223
16237
|
}
|
|
16238
|
+
var NPM_PACKAGE = "@sulala/agent-os";
|
|
16224
16239
|
async function cmdUpdate() {
|
|
16240
|
+
console.log(`Checking npm for latest ${NPM_PACKAGE}...`);
|
|
16241
|
+
const proc = Bun.spawn(["bun", "install", "-g", `${NPM_PACKAGE}@latest`], {
|
|
16242
|
+
stdout: "pipe",
|
|
16243
|
+
stderr: "pipe",
|
|
16244
|
+
stdin: "ignore"
|
|
16245
|
+
});
|
|
16246
|
+
const exit = await proc.exited;
|
|
16247
|
+
const out = await new Response(proc.stdout).text();
|
|
16248
|
+
const err = await new Response(proc.stderr).text();
|
|
16249
|
+
if (exit === 0) {
|
|
16250
|
+
console.log("Package updated to latest from npm.");
|
|
16251
|
+
if (out.trim())
|
|
16252
|
+
console.log(out.trim());
|
|
16253
|
+
} else {
|
|
16254
|
+
console.warn("Could not update package from npm (run 'bun install -g @sulala/agent-os@latest' manually):", err.trim() || out.trim());
|
|
16255
|
+
}
|
|
16225
16256
|
const dbPath = getMemoryDbPath();
|
|
16226
|
-
if (!
|
|
16227
|
-
console.
|
|
16228
|
-
|
|
16257
|
+
if (!existsSync2(dbPath)) {
|
|
16258
|
+
console.log("No database found. Run 'sulala onboard' to set up agents and skills.");
|
|
16259
|
+
return;
|
|
16229
16260
|
}
|
|
16230
16261
|
const memoryStore2 = new MemoryStore(dbPath);
|
|
16231
16262
|
setAgentStore(memoryStore2);
|
|
@@ -16242,7 +16273,7 @@ async function cmdRun(args) {
|
|
|
16242
16273
|
process.exit(1);
|
|
16243
16274
|
}
|
|
16244
16275
|
const dbPath = getMemoryDbPath();
|
|
16245
|
-
if (
|
|
16276
|
+
if (existsSync2(dbPath)) {
|
|
16246
16277
|
setAgentStore(new MemoryStore(dbPath));
|
|
16247
16278
|
}
|
|
16248
16279
|
const agent = await getAgent(agentId);
|