@electric-ax/agents 0.4.3 → 0.4.5
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/entrypoint.js +9 -434
- package/dist/index.cjs +9 -434
- package/dist/index.d.cts +4 -30
- package/dist/index.d.ts +4 -30
- package/dist/index.js +9 -434
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -716,184 +716,6 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
716
716
|
};
|
|
717
717
|
}
|
|
718
718
|
|
|
719
|
-
//#endregion
|
|
720
|
-
//#region src/skills/tools.ts
|
|
721
|
-
function skillContextId(name) {
|
|
722
|
-
return `skill:${name}`;
|
|
723
|
-
}
|
|
724
|
-
function createSkillTools(registry, ctx) {
|
|
725
|
-
const useSkill = {
|
|
726
|
-
name: `use_skill`,
|
|
727
|
-
label: `Use Skill`,
|
|
728
|
-
description: `Load a skill into your context. Call with a skill name to load it. Pass args if the skill accepts arguments.`,
|
|
729
|
-
parameters: __sinclair_typebox.Type.Object({
|
|
730
|
-
name: __sinclair_typebox.Type.String({ description: `Name of the skill to load` }),
|
|
731
|
-
args: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String({ description: `Arguments to pass to the skill (space-separated, or quoted for multi-word values)` }))
|
|
732
|
-
}),
|
|
733
|
-
execute: async (_toolCallId, params) => {
|
|
734
|
-
const { name, args } = params;
|
|
735
|
-
const meta = registry.catalog.get(name);
|
|
736
|
-
if (!meta) {
|
|
737
|
-
const available = Array.from(registry.catalog.keys()).join(`, `);
|
|
738
|
-
return {
|
|
739
|
-
content: [{
|
|
740
|
-
type: `text`,
|
|
741
|
-
text: `Skill "${name}" not found. Available skills: ${available || `none`}`
|
|
742
|
-
}],
|
|
743
|
-
details: { loaded: false }
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
const contextId = skillContextId(name);
|
|
747
|
-
if (ctx.getContext(contextId)) return {
|
|
748
|
-
content: [{
|
|
749
|
-
type: `text`,
|
|
750
|
-
text: `Skill "${name}" is already loaded.`
|
|
751
|
-
}],
|
|
752
|
-
details: {
|
|
753
|
-
loaded: false,
|
|
754
|
-
alreadyLoaded: true
|
|
755
|
-
}
|
|
756
|
-
};
|
|
757
|
-
let content = await registry.readContent(name);
|
|
758
|
-
if (content === null) return {
|
|
759
|
-
content: [{
|
|
760
|
-
type: `text`,
|
|
761
|
-
text: `Error: could not read skill file for "${name}".`
|
|
762
|
-
}],
|
|
763
|
-
details: { loaded: false }
|
|
764
|
-
};
|
|
765
|
-
let truncated = false;
|
|
766
|
-
if (content.length > meta.max) {
|
|
767
|
-
truncated = true;
|
|
768
|
-
content = content.slice(0, meta.max);
|
|
769
|
-
}
|
|
770
|
-
if (args) content = substituteArgs(content, args, meta.arguments);
|
|
771
|
-
ctx.insertContext(contextId, {
|
|
772
|
-
name: `skill_instructions`,
|
|
773
|
-
attrs: {
|
|
774
|
-
skill: name,
|
|
775
|
-
type: `directive`
|
|
776
|
-
},
|
|
777
|
-
content
|
|
778
|
-
});
|
|
779
|
-
const skillDir = node_path.default.join(node_path.default.dirname(meta.source), name);
|
|
780
|
-
const truncNote = truncated ? `\n\nWARNING: Content was truncated from ${meta.charCount.toLocaleString()} to ${meta.max.toLocaleString()} chars. Inform the user.` : ``;
|
|
781
|
-
const allRefFiles = listRefFiles(skillDir);
|
|
782
|
-
const mdFiles = allRefFiles.filter((f) => f.endsWith(`.md`));
|
|
783
|
-
const refContents = [];
|
|
784
|
-
for (const f of mdFiles) try {
|
|
785
|
-
const refContent = await node_fs_promises.default.readFile(node_path.default.join(skillDir, f), `utf-8`);
|
|
786
|
-
const refId = `${skillContextId(name)}:${f}`;
|
|
787
|
-
ctx.insertContext(refId, {
|
|
788
|
-
name: `skill_reference`,
|
|
789
|
-
attrs: {
|
|
790
|
-
skill: name,
|
|
791
|
-
file: f
|
|
792
|
-
},
|
|
793
|
-
content: refContent
|
|
794
|
-
});
|
|
795
|
-
refContents.push(`--- ${f} ---\n${refContent}`);
|
|
796
|
-
} catch {}
|
|
797
|
-
const hasRefDir = allRefFiles.length > 0;
|
|
798
|
-
const dirNote = hasRefDir ? `\nSkill directory: ${skillDir}` : ``;
|
|
799
|
-
const refSection = refContents.length > 0 ? `\n\n${refContents.join(`\n\n`)}` : ``;
|
|
800
|
-
const toolResult = `SKILL ACTIVATED: "${name}". The instructions below override your default behavior. Follow them exactly. Do not read any files to find this content — it is all here.\n${dirNote}${truncNote}\n\n${content}${refSection}`;
|
|
801
|
-
return {
|
|
802
|
-
content: [{
|
|
803
|
-
type: `text`,
|
|
804
|
-
text: toolResult
|
|
805
|
-
}],
|
|
806
|
-
details: {
|
|
807
|
-
loaded: true,
|
|
808
|
-
truncated,
|
|
809
|
-
chars: content.length
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
const removeSkill = {
|
|
815
|
-
name: `remove_skill`,
|
|
816
|
-
label: `Remove Skill`,
|
|
817
|
-
description: `Unload a previously loaded skill from your context.`,
|
|
818
|
-
parameters: __sinclair_typebox.Type.Object({ name: __sinclair_typebox.Type.String({ description: `Name of the skill to remove` }) }),
|
|
819
|
-
execute: async (_toolCallId, params) => {
|
|
820
|
-
const { name } = params;
|
|
821
|
-
ctx.removeContext(skillContextId(name));
|
|
822
|
-
const meta = registry.catalog.get(name);
|
|
823
|
-
if (meta) {
|
|
824
|
-
const skillDir = node_path.default.join(node_path.default.dirname(meta.source), name);
|
|
825
|
-
for (const f of listRefFiles(skillDir)) ctx.removeContext(`${skillContextId(name)}:${f}`);
|
|
826
|
-
}
|
|
827
|
-
return {
|
|
828
|
-
content: [{
|
|
829
|
-
type: `text`,
|
|
830
|
-
text: `Skill "${name}" removed from context.`
|
|
831
|
-
}],
|
|
832
|
-
details: { removed: true }
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
return [useSkill, removeSkill];
|
|
837
|
-
}
|
|
838
|
-
function parseArgs(raw) {
|
|
839
|
-
const args = [];
|
|
840
|
-
let current = ``;
|
|
841
|
-
let inQuote = false;
|
|
842
|
-
let quoteChar = ``;
|
|
843
|
-
for (const ch of raw) if (inQuote) if (ch === quoteChar) inQuote = false;
|
|
844
|
-
else current += ch;
|
|
845
|
-
else if (ch === `"` || ch === `'`) {
|
|
846
|
-
inQuote = true;
|
|
847
|
-
quoteChar = ch;
|
|
848
|
-
} else if (ch === ` ` || ch === `\t`) {
|
|
849
|
-
if (current.length > 0) {
|
|
850
|
-
args.push(current);
|
|
851
|
-
current = ``;
|
|
852
|
-
}
|
|
853
|
-
} else current += ch;
|
|
854
|
-
if (current.length > 0) args.push(current);
|
|
855
|
-
return args;
|
|
856
|
-
}
|
|
857
|
-
function substituteArgs(content, rawArgs, argNames) {
|
|
858
|
-
const parsed = parseArgs(rawArgs);
|
|
859
|
-
let result = content;
|
|
860
|
-
let matched = false;
|
|
861
|
-
if (argNames) for (let i = 0; i < argNames.length && i < parsed.length; i++) {
|
|
862
|
-
const pattern = new RegExp(`\\$${argNames[i]}\\b`, `g`);
|
|
863
|
-
if (pattern.test(result)) {
|
|
864
|
-
result = result.replace(pattern, parsed[i]);
|
|
865
|
-
matched = true;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
for (let i = 0; i < parsed.length; i++) {
|
|
869
|
-
const pattern = new RegExp(`\\$${i}\\b`, `g`);
|
|
870
|
-
if (pattern.test(result)) {
|
|
871
|
-
result = result.replace(pattern, parsed[i]);
|
|
872
|
-
matched = true;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
if (result.includes(`$ARGUMENTS`)) {
|
|
876
|
-
result = result.replace(/\$ARGUMENTS/g, rawArgs);
|
|
877
|
-
matched = true;
|
|
878
|
-
}
|
|
879
|
-
if (!matched) result += `\n\nArguments: ${rawArgs}`;
|
|
880
|
-
return result;
|
|
881
|
-
}
|
|
882
|
-
function listRefFiles(dir, prefix = ``) {
|
|
883
|
-
try {
|
|
884
|
-
const results = [];
|
|
885
|
-
for (const entry of node_fs.default.readdirSync(dir)) {
|
|
886
|
-
const full = node_path.default.join(dir, entry);
|
|
887
|
-
const rel = prefix ? `${prefix}/${entry}` : entry;
|
|
888
|
-
if (node_fs.default.statSync(full).isDirectory()) results.push(...listRefFiles(full, rel));
|
|
889
|
-
else results.push(rel);
|
|
890
|
-
}
|
|
891
|
-
return results;
|
|
892
|
-
} catch {
|
|
893
|
-
return [];
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
719
|
//#endregion
|
|
898
720
|
//#region src/tools/spawn-worker.ts
|
|
899
721
|
const WORKER_TOOL_NAMES = [
|
|
@@ -903,7 +725,8 @@ const WORKER_TOOL_NAMES = [
|
|
|
903
725
|
`edit`,
|
|
904
726
|
`web_search`,
|
|
905
727
|
`fetch_url`,
|
|
906
|
-
`spawn_worker
|
|
728
|
+
`spawn_worker`,
|
|
729
|
+
`send`
|
|
907
730
|
];
|
|
908
731
|
function createSpawnWorkerTool(ctx, modelConfig) {
|
|
909
732
|
return {
|
|
@@ -1253,6 +1076,7 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
|
|
|
1253
1076
|
- web_search: search the web
|
|
1254
1077
|
- fetch_url: fetch and convert a URL to markdown
|
|
1255
1078
|
- spawn_worker: dispatch a subagent for an isolated task
|
|
1079
|
+
- send: send a message to an Electric Agent/entity by entity URL
|
|
1256
1080
|
${docsTools}${skillsTools}
|
|
1257
1081
|
|
|
1258
1082
|
# Working with files
|
|
@@ -1300,6 +1124,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
|
1300
1124
|
logPrefix: opts.logPrefix ?? `[horton]`
|
|
1301
1125
|
})] : [__electric_ax_agents_runtime_tools.fetchUrlTool],
|
|
1302
1126
|
createSpawnWorkerTool(ctx, opts.modelConfig),
|
|
1127
|
+
(0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send),
|
|
1303
1128
|
...opts.docsSearchTool ? [opts.docsSearchTool] : []
|
|
1304
1129
|
];
|
|
1305
1130
|
}
|
|
@@ -1348,7 +1173,7 @@ function createAssistantHandler(options) {
|
|
|
1348
1173
|
modelCatalog,
|
|
1349
1174
|
logPrefix: `[horton ${ctx.entityUrl}]`
|
|
1350
1175
|
}),
|
|
1351
|
-
...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
|
|
1176
|
+
...skillsRegistry && skillsRegistry.catalog.size > 0 ? (0, __electric_ax_agents_runtime.createSkillTools)(skillsRegistry, ctx) : [],
|
|
1352
1177
|
...__electric_ax_agents_mcp.mcp.tools()
|
|
1353
1178
|
];
|
|
1354
1179
|
const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
|
|
@@ -1548,6 +1373,9 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
|
|
|
1548
1373
|
case `spawn_worker`:
|
|
1549
1374
|
out.push(createSpawnWorkerTool(ctx));
|
|
1550
1375
|
break;
|
|
1376
|
+
case `send`:
|
|
1377
|
+
out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send));
|
|
1378
|
+
break;
|
|
1551
1379
|
}
|
|
1552
1380
|
return out;
|
|
1553
1381
|
}
|
|
@@ -1672,259 +1500,6 @@ function registerWorker(registry, options) {
|
|
|
1672
1500
|
});
|
|
1673
1501
|
}
|
|
1674
1502
|
|
|
1675
|
-
//#endregion
|
|
1676
|
-
//#region src/skills/preamble.ts
|
|
1677
|
-
function parsePreamble(content) {
|
|
1678
|
-
const lines = content.split(`\n`);
|
|
1679
|
-
if (lines[0]?.trim() !== `---`) return {};
|
|
1680
|
-
let closingIndex = -1;
|
|
1681
|
-
for (let i = 1; i < Math.min(lines.length, 25); i++) if (lines[i]?.trim() === `---`) {
|
|
1682
|
-
closingIndex = i;
|
|
1683
|
-
break;
|
|
1684
|
-
}
|
|
1685
|
-
if (closingIndex === -1) return {};
|
|
1686
|
-
const result = {};
|
|
1687
|
-
for (let i = 1; i < closingIndex; i++) {
|
|
1688
|
-
const line = lines[i];
|
|
1689
|
-
const colonIndex = line.indexOf(`:`);
|
|
1690
|
-
if (colonIndex === -1) continue;
|
|
1691
|
-
const key = line.slice(0, colonIndex).trim();
|
|
1692
|
-
const rawValue = line.slice(colonIndex + 1).trim();
|
|
1693
|
-
switch (key) {
|
|
1694
|
-
case `description`:
|
|
1695
|
-
result.description = stripQuotes(rawValue);
|
|
1696
|
-
break;
|
|
1697
|
-
case `whenToUse`:
|
|
1698
|
-
result.whenToUse = stripQuotes(rawValue);
|
|
1699
|
-
break;
|
|
1700
|
-
case `keywords`: {
|
|
1701
|
-
if (rawValue.length === 0) {
|
|
1702
|
-
const items = [];
|
|
1703
|
-
for (let j = i + 1; j < closingIndex; j++) {
|
|
1704
|
-
const next = lines[j];
|
|
1705
|
-
const match = next.match(/^\s+-\s+(.+)$/);
|
|
1706
|
-
if (match) {
|
|
1707
|
-
items.push(match[1].trim());
|
|
1708
|
-
i = j;
|
|
1709
|
-
} else break;
|
|
1710
|
-
}
|
|
1711
|
-
result.keywords = items;
|
|
1712
|
-
} else result.keywords = parseKeywords(rawValue);
|
|
1713
|
-
break;
|
|
1714
|
-
}
|
|
1715
|
-
case `arguments`: {
|
|
1716
|
-
if (rawValue.length === 0) {
|
|
1717
|
-
const items = [];
|
|
1718
|
-
for (let j = i + 1; j < closingIndex; j++) {
|
|
1719
|
-
const next = lines[j];
|
|
1720
|
-
const match = next.match(/^\s+-\s+(.+)$/);
|
|
1721
|
-
if (match) {
|
|
1722
|
-
items.push(match[1].trim());
|
|
1723
|
-
i = j;
|
|
1724
|
-
} else break;
|
|
1725
|
-
}
|
|
1726
|
-
result.arguments = items;
|
|
1727
|
-
} else result.arguments = parseKeywords(rawValue);
|
|
1728
|
-
break;
|
|
1729
|
-
}
|
|
1730
|
-
case `argument-hint`:
|
|
1731
|
-
result.argumentHint = stripQuotes(rawValue);
|
|
1732
|
-
break;
|
|
1733
|
-
case `user-invocable`:
|
|
1734
|
-
result.userInvocable = rawValue === `true`;
|
|
1735
|
-
break;
|
|
1736
|
-
case `max`: {
|
|
1737
|
-
const num = parseInt(rawValue, 10);
|
|
1738
|
-
if (!Number.isNaN(num) && num > 0) result.max = num;
|
|
1739
|
-
break;
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
return result;
|
|
1744
|
-
}
|
|
1745
|
-
function parseKeywords(raw) {
|
|
1746
|
-
const stripped = raw.replace(/^\[/, ``).replace(/\]$/, ``);
|
|
1747
|
-
return stripped.split(`,`).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1748
|
-
}
|
|
1749
|
-
function stripQuotes(value) {
|
|
1750
|
-
if (value.length >= 2 && value.startsWith(`"`) && value.endsWith(`"`)) return value.slice(1, -1);
|
|
1751
|
-
return value;
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
//#endregion
|
|
1755
|
-
//#region src/skills/extract-meta.ts
|
|
1756
|
-
const DEFAULT_MAX = 1e4;
|
|
1757
|
-
async function extractSkillMeta(name, content) {
|
|
1758
|
-
const preamble = parsePreamble(content);
|
|
1759
|
-
if (preamble.description && preamble.whenToUse && preamble.keywords) return {
|
|
1760
|
-
description: preamble.description,
|
|
1761
|
-
whenToUse: preamble.whenToUse,
|
|
1762
|
-
keywords: preamble.keywords,
|
|
1763
|
-
...preamble.arguments && { arguments: preamble.arguments },
|
|
1764
|
-
...preamble.argumentHint && { argumentHint: preamble.argumentHint },
|
|
1765
|
-
...preamble.userInvocable && { userInvocable: true },
|
|
1766
|
-
max: preamble.max ?? DEFAULT_MAX
|
|
1767
|
-
};
|
|
1768
|
-
try {
|
|
1769
|
-
return await llmExtract(name, content, preamble);
|
|
1770
|
-
} catch (err) {
|
|
1771
|
-
serverLog.warn(`[skills] LLM metadata extraction failed for "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1772
|
-
}
|
|
1773
|
-
return {
|
|
1774
|
-
description: preamble.description ?? humanize(name),
|
|
1775
|
-
whenToUse: preamble.whenToUse ?? `User asks about ${humanize(name).toLowerCase()}`,
|
|
1776
|
-
keywords: preamble.keywords ?? [name],
|
|
1777
|
-
max: preamble.max ?? DEFAULT_MAX
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
async function llmExtract(name, content, partial) {
|
|
1781
|
-
const truncated = content.slice(0, 8e3);
|
|
1782
|
-
const prompt = `Analyze this skill document and extract metadata. The skill is named "${name}".
|
|
1783
|
-
|
|
1784
|
-
<skill>
|
|
1785
|
-
${truncated}
|
|
1786
|
-
</skill>
|
|
1787
|
-
|
|
1788
|
-
Return ONLY a JSON object with these fields:
|
|
1789
|
-
- "description": one-line summary of what this skill provides (max 100 chars)
|
|
1790
|
-
- "whenToUse": when should an AI agent load this skill (max 200 chars)
|
|
1791
|
-
- "keywords": array of 3-8 relevant keywords
|
|
1792
|
-
|
|
1793
|
-
Return raw JSON, no markdown fences.`;
|
|
1794
|
-
const text = await (0, __electric_ax_agents_runtime.completeWithLowCostModel)({
|
|
1795
|
-
purpose: `skill metadata extraction`,
|
|
1796
|
-
systemPrompt: `Extract metadata from skill documents. Return only valid JSON that matches the requested schema.`,
|
|
1797
|
-
prompt,
|
|
1798
|
-
maxTokens: 256,
|
|
1799
|
-
log: (message) => serverLog.info(message),
|
|
1800
|
-
logPrefix: `[skills]`
|
|
1801
|
-
});
|
|
1802
|
-
const parsed = JSON.parse(text);
|
|
1803
|
-
return {
|
|
1804
|
-
description: partial.description ?? parsed.description ?? humanize(name),
|
|
1805
|
-
whenToUse: partial.whenToUse ?? parsed.whenToUse ?? `User asks about ${name}`,
|
|
1806
|
-
keywords: partial.keywords ?? parsed.keywords ?? [name],
|
|
1807
|
-
max: partial.max ?? DEFAULT_MAX
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
function humanize(name) {
|
|
1811
|
-
return name.replace(/[-_]/g, ` `).replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
//#endregion
|
|
1815
|
-
//#region src/skills/registry.ts
|
|
1816
|
-
const CACHE_FILENAME = `skills-cache.json`;
|
|
1817
|
-
async function createSkillsRegistry(opts) {
|
|
1818
|
-
const { baseSkillsDir, appSkillsDir, cacheDir } = opts;
|
|
1819
|
-
const cachePath = node_path.default.join(cacheDir, CACHE_FILENAME);
|
|
1820
|
-
const existingCache = await loadCache(cachePath);
|
|
1821
|
-
const files = new Map();
|
|
1822
|
-
await scanDir(baseSkillsDir, files);
|
|
1823
|
-
if (appSkillsDir) await scanDir(appSkillsDir, files);
|
|
1824
|
-
const catalog = new Map();
|
|
1825
|
-
for (const [name, filePath] of files) {
|
|
1826
|
-
const content = await node_fs_promises.default.readFile(filePath, `utf-8`);
|
|
1827
|
-
const hash = sha256(content);
|
|
1828
|
-
const cached = existingCache[name];
|
|
1829
|
-
if (cached && cached.contentHash === hash && cached.source === filePath) {
|
|
1830
|
-
catalog.set(name, cached);
|
|
1831
|
-
continue;
|
|
1832
|
-
}
|
|
1833
|
-
serverLog.info(`[skills] extracting metadata for "${name}"`);
|
|
1834
|
-
const meta = await extractSkillMeta(name, content);
|
|
1835
|
-
const entry = {
|
|
1836
|
-
name,
|
|
1837
|
-
...meta,
|
|
1838
|
-
charCount: content.length,
|
|
1839
|
-
contentHash: hash,
|
|
1840
|
-
source: filePath
|
|
1841
|
-
};
|
|
1842
|
-
catalog.set(name, entry);
|
|
1843
|
-
}
|
|
1844
|
-
await saveCache(cachePath, catalog, cacheDir);
|
|
1845
|
-
return {
|
|
1846
|
-
catalog,
|
|
1847
|
-
renderCatalog(budget) {
|
|
1848
|
-
if (catalog.size === 0) return ``;
|
|
1849
|
-
const skills = Array.from(catalog.values());
|
|
1850
|
-
const full = renderSkillList(skills, `full`);
|
|
1851
|
-
if (!budget || full.length <= budget) return full;
|
|
1852
|
-
const compact = renderSkillList(skills, `compact`);
|
|
1853
|
-
if (compact.length <= budget) return compact;
|
|
1854
|
-
return renderSkillList(skills, `names`);
|
|
1855
|
-
},
|
|
1856
|
-
async readContent(name) {
|
|
1857
|
-
const meta = catalog.get(name);
|
|
1858
|
-
if (!meta) return null;
|
|
1859
|
-
try {
|
|
1860
|
-
return await node_fs_promises.default.readFile(meta.source, `utf-8`);
|
|
1861
|
-
} catch {
|
|
1862
|
-
return null;
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
async function scanDir(dir, out) {
|
|
1868
|
-
let entries;
|
|
1869
|
-
try {
|
|
1870
|
-
entries = await node_fs_promises.default.readdir(dir, { withFileTypes: true });
|
|
1871
|
-
} catch {
|
|
1872
|
-
return;
|
|
1873
|
-
}
|
|
1874
|
-
for (const entry of entries) {
|
|
1875
|
-
if (!entry.isFile() || !entry.name.endsWith(`.md`)) continue;
|
|
1876
|
-
const name = entry.name.slice(0, -3);
|
|
1877
|
-
out.set(name, node_path.default.resolve(dir, entry.name));
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
async function loadCache(cachePath) {
|
|
1881
|
-
try {
|
|
1882
|
-
const raw = await node_fs_promises.default.readFile(cachePath, `utf-8`);
|
|
1883
|
-
return JSON.parse(raw);
|
|
1884
|
-
} catch {
|
|
1885
|
-
return {};
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
async function saveCache(cachePath, catalog, cacheDir) {
|
|
1889
|
-
const obj = {};
|
|
1890
|
-
for (const [name, meta] of catalog) obj[name] = meta;
|
|
1891
|
-
node_fs.default.mkdirSync(cacheDir, { recursive: true });
|
|
1892
|
-
await node_fs_promises.default.writeFile(node_path.default.join(cacheDir, `.gitignore`), `*\n`, `utf-8`);
|
|
1893
|
-
await node_fs_promises.default.writeFile(cachePath, JSON.stringify(obj, null, 2), `utf-8`);
|
|
1894
|
-
}
|
|
1895
|
-
function sha256(content) {
|
|
1896
|
-
return (0, node_crypto.createHash)(`sha256`).update(content).digest(`hex`);
|
|
1897
|
-
}
|
|
1898
|
-
function renderSkillList(skills, mode) {
|
|
1899
|
-
const invocable = skills.filter((s) => s.userInvocable);
|
|
1900
|
-
const others = skills.filter((s) => !s.userInvocable);
|
|
1901
|
-
const lines = [`Available skills:`];
|
|
1902
|
-
if (invocable.length > 0 && mode !== `names`) {
|
|
1903
|
-
lines.push(`\nUser-invocable (the user can trigger these directly):`);
|
|
1904
|
-
for (const meta of invocable) {
|
|
1905
|
-
const hint = meta.argumentHint ? ` ${meta.argumentHint}` : ``;
|
|
1906
|
-
lines.push(`- /${meta.name}${hint} — ${mode === `compact` ? truncate(meta.description, 100) : meta.description}`);
|
|
1907
|
-
}
|
|
1908
|
-
if (others.length > 0) lines.push(``);
|
|
1909
|
-
}
|
|
1910
|
-
const all = mode === `names` ? skills : others.length > 0 ? others : invocable.length === 0 ? skills : [];
|
|
1911
|
-
for (const meta of all) {
|
|
1912
|
-
if (mode === `names`) {
|
|
1913
|
-
const prefix = meta.userInvocable ? `/${meta.name}` : meta.name;
|
|
1914
|
-
lines.push(`- ${prefix}: ${truncate(meta.description, 60)}`);
|
|
1915
|
-
continue;
|
|
1916
|
-
}
|
|
1917
|
-
lines.push(`- ${meta.name} (${meta.charCount.toLocaleString()} chars): ${mode === `compact` ? truncate(meta.description, 100) : meta.description}`);
|
|
1918
|
-
lines.push(` Use when: ${meta.whenToUse}`);
|
|
1919
|
-
if (mode === `full`) lines.push(` Keywords: ${meta.keywords.join(`, `)}`);
|
|
1920
|
-
if (meta.argumentHint) lines.push(` Usage: use_skill("${meta.name}", "${meta.argumentHint}")`);
|
|
1921
|
-
}
|
|
1922
|
-
return lines.join(`\n`);
|
|
1923
|
-
}
|
|
1924
|
-
function truncate(str, max) {
|
|
1925
|
-
return str.length <= max ? str : str.slice(0, max - 3) + `...`;
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
1503
|
//#endregion
|
|
1929
1504
|
//#region src/bootstrap.ts
|
|
1930
1505
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
@@ -1940,7 +1515,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1940
1515
|
const baseSkillsDir = baseSkillsDirOverride ?? node_path.default.resolve(here, `../skills`);
|
|
1941
1516
|
let skillsRegistry = null;
|
|
1942
1517
|
try {
|
|
1943
|
-
skillsRegistry = await createSkillsRegistry({
|
|
1518
|
+
skillsRegistry = await (0, __electric_ax_agents_runtime.createSkillsRegistry)({
|
|
1944
1519
|
baseSkillsDir,
|
|
1945
1520
|
appSkillsDir: node_path.default.resolve(cwd, `skills`),
|
|
1946
1521
|
cacheDir: node_path.default.resolve(cwd, `.electric-agents`)
|
package/dist/index.d.cts
CHANGED
|
@@ -1,32 +1,10 @@
|
|
|
1
|
-
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, WakeEvent } from "@electric-ax/agents-runtime";
|
|
1
|
+
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
|
|
2
2
|
import { ChangeEvent } from "@durable-streams/state";
|
|
3
3
|
import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
|
|
4
4
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
5
5
|
import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
|
|
6
6
|
import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
|
|
7
7
|
|
|
8
|
-
//#region src/skills/types.d.ts
|
|
9
|
-
interface SkillMeta {
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
whenToUse: string;
|
|
13
|
-
keywords: Array<string>;
|
|
14
|
-
arguments?: Array<string>;
|
|
15
|
-
argumentHint?: string;
|
|
16
|
-
userInvocable?: boolean;
|
|
17
|
-
max: number;
|
|
18
|
-
charCount: number;
|
|
19
|
-
contentHash: string;
|
|
20
|
-
source: string;
|
|
21
|
-
}
|
|
22
|
-
interface SkillsRegistry {
|
|
23
|
-
/** All skill metadata, keyed by name. */
|
|
24
|
-
catalog: ReadonlyMap<string, SkillMeta>;
|
|
25
|
-
/** Render the skill catalog as text for context injection. Fits within budget (chars). */
|
|
26
|
-
renderCatalog: (budget?: number) => string;
|
|
27
|
-
/** Read skill content from disk. Returns null if skill not found. */
|
|
28
|
-
readContent: (name: string) => Promise<string | null>;
|
|
29
|
-
} //#endregion
|
|
30
8
|
//#region src/bootstrap.d.ts
|
|
31
9
|
declare const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = "/_electric/builtin-agent-handler";
|
|
32
10
|
interface AgentHandlerResult {
|
|
@@ -83,9 +61,7 @@ interface BuiltinAgentHandlerOptions {
|
|
|
83
61
|
declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
|
|
84
62
|
declare function createAgentHandler(agentServerUrl: string, workingDirectory?: string, streamFn?: StreamFn, createElectricTools?: BuiltinAgentHandlerOptions[`createElectricTools`], serveEndpoint?: string): Promise<AgentHandlerResult | null>;
|
|
85
63
|
declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
|
|
86
|
-
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
87
|
-
|
|
88
|
-
//#endregion
|
|
64
|
+
declare const registerAgentTypes: typeof registerBuiltinAgentTypes; //#endregion
|
|
89
65
|
//#region src/server.d.ts
|
|
90
66
|
interface BuiltinAgentsServerOptions {
|
|
91
67
|
agentServerUrl: string;
|
|
@@ -178,9 +154,7 @@ declare class BuiltinAgentsServer {
|
|
|
178
154
|
start(): Promise<string>;
|
|
179
155
|
stop(): Promise<void>;
|
|
180
156
|
private registerPullWakeRunner;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
//#endregion
|
|
157
|
+
} //#endregion
|
|
184
158
|
//#region src/entrypoint-lib.d.ts
|
|
185
159
|
type EnvSource = Record<string, string | undefined>;
|
|
186
160
|
interface BuiltinAgentsEntrypointOptions extends BuiltinAgentsServerOptions {}
|
|
@@ -261,7 +235,7 @@ declare function registerWorker(registry: EntityRegistry, options: {
|
|
|
261
235
|
|
|
262
236
|
//#endregion
|
|
263
237
|
//#region src/tools/spawn-worker.d.ts
|
|
264
|
-
declare const WORKER_TOOL_NAMES: readonly ["bash", "read", "write", "edit", "web_search", "fetch_url", "spawn_worker"];
|
|
238
|
+
declare const WORKER_TOOL_NAMES: readonly ["bash", "read", "write", "edit", "web_search", "fetch_url", "spawn_worker", "send"];
|
|
265
239
|
type WorkerToolName = (typeof WORKER_TOOL_NAMES)[number];
|
|
266
240
|
declare function createSpawnWorkerTool(ctx: HandlerContext, modelConfig?: BuiltinAgentModelConfig): AgentTool$1;
|
|
267
241
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,10 @@
|
|
|
1
|
-
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, WakeEvent } from "@electric-ax/agents-runtime";
|
|
1
|
+
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
|
|
2
2
|
import { ChangeEvent } from "@durable-streams/state";
|
|
3
3
|
import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
|
|
4
4
|
import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
|
|
5
5
|
import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
|
|
6
6
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
7
7
|
|
|
8
|
-
//#region src/skills/types.d.ts
|
|
9
|
-
interface SkillMeta {
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
whenToUse: string;
|
|
13
|
-
keywords: Array<string>;
|
|
14
|
-
arguments?: Array<string>;
|
|
15
|
-
argumentHint?: string;
|
|
16
|
-
userInvocable?: boolean;
|
|
17
|
-
max: number;
|
|
18
|
-
charCount: number;
|
|
19
|
-
contentHash: string;
|
|
20
|
-
source: string;
|
|
21
|
-
}
|
|
22
|
-
interface SkillsRegistry {
|
|
23
|
-
/** All skill metadata, keyed by name. */
|
|
24
|
-
catalog: ReadonlyMap<string, SkillMeta>;
|
|
25
|
-
/** Render the skill catalog as text for context injection. Fits within budget (chars). */
|
|
26
|
-
renderCatalog: (budget?: number) => string;
|
|
27
|
-
/** Read skill content from disk. Returns null if skill not found. */
|
|
28
|
-
readContent: (name: string) => Promise<string | null>;
|
|
29
|
-
} //#endregion
|
|
30
8
|
//#region src/bootstrap.d.ts
|
|
31
9
|
declare const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = "/_electric/builtin-agent-handler";
|
|
32
10
|
interface AgentHandlerResult {
|
|
@@ -83,9 +61,7 @@ interface BuiltinAgentHandlerOptions {
|
|
|
83
61
|
declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
|
|
84
62
|
declare function createAgentHandler(agentServerUrl: string, workingDirectory?: string, streamFn?: StreamFn, createElectricTools?: BuiltinAgentHandlerOptions[`createElectricTools`], serveEndpoint?: string): Promise<AgentHandlerResult | null>;
|
|
85
63
|
declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
|
|
86
|
-
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
87
|
-
|
|
88
|
-
//#endregion
|
|
64
|
+
declare const registerAgentTypes: typeof registerBuiltinAgentTypes; //#endregion
|
|
89
65
|
//#region src/server.d.ts
|
|
90
66
|
interface BuiltinAgentsServerOptions {
|
|
91
67
|
agentServerUrl: string;
|
|
@@ -178,9 +154,7 @@ declare class BuiltinAgentsServer {
|
|
|
178
154
|
start(): Promise<string>;
|
|
179
155
|
stop(): Promise<void>;
|
|
180
156
|
private registerPullWakeRunner;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
//#endregion
|
|
157
|
+
} //#endregion
|
|
184
158
|
//#region src/entrypoint-lib.d.ts
|
|
185
159
|
type EnvSource = Record<string, string | undefined>;
|
|
186
160
|
interface BuiltinAgentsEntrypointOptions extends BuiltinAgentsServerOptions {}
|
|
@@ -261,7 +235,7 @@ declare function registerWorker(registry: EntityRegistry, options: {
|
|
|
261
235
|
|
|
262
236
|
//#endregion
|
|
263
237
|
//#region src/tools/spawn-worker.d.ts
|
|
264
|
-
declare const WORKER_TOOL_NAMES: readonly ["bash", "read", "write", "edit", "web_search", "fetch_url", "spawn_worker"];
|
|
238
|
+
declare const WORKER_TOOL_NAMES: readonly ["bash", "read", "write", "edit", "web_search", "fetch_url", "spawn_worker", "send"];
|
|
265
239
|
type WorkerToolName = (typeof WORKER_TOOL_NAMES)[number];
|
|
266
240
|
declare function createSpawnWorkerTool(ctx: HandlerContext, modelConfig?: BuiltinAgentModelConfig): AgentTool$1;
|
|
267
241
|
|