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