@electric-ax/agents 0.4.12 → 0.4.14
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 +155 -79
- package/dist/index.cjs +154 -78
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +155 -79
- package/docs/entities/patterns/blackboard.md +1 -1
- package/docs/entities/patterns/dispatcher.md +3 -3
- package/docs/entities/patterns/manager-worker.md +11 -23
- package/docs/entities/patterns/map-reduce.md +1 -1
- package/docs/entities/patterns/pipeline.md +3 -3
- package/docs/index.md +61 -39
- package/docs/quickstart.md +26 -22
- package/docs/reference/entity-handle.md +51 -25
- package/docs/reference/handler-context.md +1 -1
- package/docs/reference/wake-event.md +1 -1
- package/docs/usage/defining-tools.md +4 -5
- package/docs/usage/overview.md +10 -6
- package/docs/usage/shared-state.md +3 -3
- package/docs/usage/spawning-and-coordinating.md +34 -18
- package/docs/usage/writing-handlers.md +1 -1
- package/docs/walkthrough.md +1156 -0
- package/package.json +4 -3
- package/skills/quickstart/scaffold/package.json +1 -1
- package/skills/quickstart.md +16 -10
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ const sqlite_vec = __toESM(require("sqlite-vec"));
|
|
|
39
39
|
const nanoid = __toESM(require("nanoid"));
|
|
40
40
|
const __mariozechner_pi_ai = __toESM(require("@mariozechner/pi-ai"));
|
|
41
41
|
const __electric_ax_agents_mcp = __toESM(require("@electric-ax/agents-mcp"));
|
|
42
|
+
const undici = __toESM(require("undici"));
|
|
42
43
|
|
|
43
44
|
//#region src/log.ts
|
|
44
45
|
const LOG_LEVEL = process.env.ELECTRIC_AGENTS_LOG_LEVEL ?? `info`;
|
|
@@ -889,6 +890,15 @@ async function fetchAvailableModelIds(provider) {
|
|
|
889
890
|
function knownModelsForProvider(provider) {
|
|
890
891
|
return provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER ? (0, __electric_ax_agents_runtime.getMoonshotModels)() : (0, __mariozechner_pi_ai.getModels)(provider);
|
|
891
892
|
}
|
|
893
|
+
function resolveBuiltinModelContextWindow(modelConfig) {
|
|
894
|
+
const modelId = String(modelConfig.model);
|
|
895
|
+
if (modelConfig.provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER) return (0, __electric_ax_agents_runtime.getMoonshotModel)(modelId)?.contextWindow ?? null;
|
|
896
|
+
if (!modelConfig.provider) return null;
|
|
897
|
+
return knownModelsForProvider(modelConfig.provider).find((model) => model.id === modelId)?.contextWindow ?? null;
|
|
898
|
+
}
|
|
899
|
+
function resolveBuiltinModelSourceBudget(modelConfig) {
|
|
900
|
+
return resolveBuiltinModelContextWindow(modelConfig) ?? 1e5;
|
|
901
|
+
}
|
|
892
902
|
function choiceForKnownModel(provider, model) {
|
|
893
903
|
return {
|
|
894
904
|
provider,
|
|
@@ -992,6 +1002,8 @@ function modelInputSchemaDefs(catalog) {
|
|
|
992
1002
|
const HORTON_MODEL = `claude-sonnet-4-6`;
|
|
993
1003
|
const TITLE_SYSTEM_PROMPT = "You generate concise chat session titles in 3-5 words. Respond with only the title, no quotes, no punctuation, no preamble.";
|
|
994
1004
|
const TITLE_USER_PROMPT = (userMessage) => `User request:\n${userMessage}`;
|
|
1005
|
+
const TITLE_GENERATION_TIMEOUT_MS = 8e3;
|
|
1006
|
+
const HORTON_SKILLS_SLASH_COMMAND_OWNER = `horton:skills`;
|
|
995
1007
|
const TITLE_STOP_WORDS = new Set([
|
|
996
1008
|
`a`,
|
|
997
1009
|
`an`,
|
|
@@ -1071,6 +1083,17 @@ function createConfiguredTitleCall(catalog, modelConfig, logPrefix) {
|
|
|
1071
1083
|
maxTokens: 64
|
|
1072
1084
|
});
|
|
1073
1085
|
}
|
|
1086
|
+
function withTimeout(promise, ms, description) {
|
|
1087
|
+
let timeout;
|
|
1088
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
1089
|
+
timeout = setTimeout(() => {
|
|
1090
|
+
reject(new Error(`${description} timed out after ${ms}ms`));
|
|
1091
|
+
}, ms);
|
|
1092
|
+
});
|
|
1093
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
1094
|
+
if (timeout) clearTimeout(timeout);
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1074
1097
|
async function generateTitle(userMessage, llmCall, onFallback) {
|
|
1075
1098
|
try {
|
|
1076
1099
|
const raw = await llmCall(TITLE_USER_PROMPT(userMessage));
|
|
@@ -1200,8 +1223,12 @@ function payloadToTitleText(payload) {
|
|
|
1200
1223
|
if (typeof payload === `string`) return payload;
|
|
1201
1224
|
if (payload == null) return ``;
|
|
1202
1225
|
if (typeof payload === `object`) {
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1226
|
+
const record = payload;
|
|
1227
|
+
const text = record.text;
|
|
1228
|
+
if (typeof text === `string`) return text;
|
|
1229
|
+
const source = record.source;
|
|
1230
|
+
if (typeof source === `string`) return source;
|
|
1231
|
+
return JSON.stringify(payload);
|
|
1205
1232
|
}
|
|
1206
1233
|
return String(payload);
|
|
1207
1234
|
}
|
|
@@ -1259,10 +1286,13 @@ async function readAgentsMd(sandbox) {
|
|
|
1259
1286
|
}
|
|
1260
1287
|
function createAssistantHandler(options) {
|
|
1261
1288
|
const { streamFn, docsSupport, docsSearchTool, skillsRegistry, modelCatalog, docsUrl } = options;
|
|
1262
|
-
const
|
|
1289
|
+
const skillLoader = (0, __electric_ax_agents_runtime.createContextSkillLoader)(skillsRegistry, { slashCommandOwner: HORTON_SKILLS_SLASH_COMMAND_OWNER });
|
|
1290
|
+
const hasSkills = skillLoader.hasSkills;
|
|
1263
1291
|
return async function assistantHandler(ctx, wake) {
|
|
1292
|
+
const loadedSkills = await skillLoader.load(ctx);
|
|
1264
1293
|
const readSet = new Set();
|
|
1265
1294
|
const modelConfig = resolveBuiltinModelConfig(modelCatalog, ctx.args);
|
|
1295
|
+
const sourceBudget = resolveBuiltinModelSourceBudget(modelConfig);
|
|
1266
1296
|
const sandboxCwd = ctx.sandbox.workingDirectory;
|
|
1267
1297
|
const agentsMd = await readAgentsMd(ctx.sandbox);
|
|
1268
1298
|
const tools = [
|
|
@@ -1273,7 +1303,7 @@ function createAssistantHandler(options) {
|
|
|
1273
1303
|
modelCatalog,
|
|
1274
1304
|
logPrefix: `[horton ${ctx.entityUrl}]`
|
|
1275
1305
|
}),
|
|
1276
|
-
...
|
|
1306
|
+
...loadedSkills.tools,
|
|
1277
1307
|
...__electric_ax_agents_mcp.mcp.tools()
|
|
1278
1308
|
];
|
|
1279
1309
|
const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
|
|
@@ -1282,21 +1312,22 @@ function createAssistantHandler(options) {
|
|
|
1282
1312
|
if (!firstUserMessage) return;
|
|
1283
1313
|
let title = null;
|
|
1284
1314
|
try {
|
|
1285
|
-
const result = await generateTitle(firstUserMessage, createConfiguredTitleCall(modelCatalog, modelConfig, `[horton ${ctx.entityUrl}]`), (reason) => {
|
|
1315
|
+
const result = await generateTitle(firstUserMessage, (prompt) => withTimeout(createConfiguredTitleCall(modelCatalog, modelConfig, `[horton ${ctx.entityUrl}]`)(prompt), TITLE_GENERATION_TIMEOUT_MS, `title generation`), (reason) => {
|
|
1286
1316
|
serverLog.warn(`[horton ${ctx.entityUrl}] title generation fell back to local title: ${reason}`);
|
|
1287
1317
|
});
|
|
1288
1318
|
if (result.length > 0) title = result;
|
|
1289
1319
|
} catch (err) {
|
|
1290
1320
|
serverLog.warn(`[horton ${ctx.entityUrl}] title generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1321
|
+
title = buildFallbackTitle(firstUserMessage);
|
|
1291
1322
|
}
|
|
1292
1323
|
if (title !== null) try {
|
|
1293
|
-
await ctx.setTag(`title`, title);
|
|
1324
|
+
await withTimeout(ctx.setTag(`title`, title), TITLE_GENERATION_TIMEOUT_MS, `set title tag`);
|
|
1294
1325
|
} catch (err) {
|
|
1295
1326
|
serverLog.warn(`[horton ${ctx.entityUrl}] setTag failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1296
1327
|
}
|
|
1297
1328
|
})() : Promise.resolve();
|
|
1298
1329
|
if (docsSupport) ctx.useContext({
|
|
1299
|
-
sourceBudget
|
|
1330
|
+
sourceBudget,
|
|
1300
1331
|
sources: {
|
|
1301
1332
|
docs_toc: {
|
|
1302
1333
|
content: () => docsSupport.renderCompressedToc(),
|
|
@@ -1317,21 +1348,13 @@ function createAssistantHandler(options) {
|
|
|
1317
1348
|
max: 2e4,
|
|
1318
1349
|
cache: `stable`
|
|
1319
1350
|
} } : {},
|
|
1320
|
-
...skillsRegistry && skillsRegistry.catalog.size > 0 ?
|
|
1321
|
-
content: () => skillsRegistry.renderCatalog(2e3),
|
|
1322
|
-
max: 2e3,
|
|
1323
|
-
cache: `stable`
|
|
1324
|
-
} } : {}
|
|
1351
|
+
...skillsRegistry && skillsRegistry.catalog.size > 0 ? loadedSkills.sources : {}
|
|
1325
1352
|
}
|
|
1326
1353
|
});
|
|
1327
1354
|
else if (skillsRegistry && skillsRegistry.catalog.size > 0) ctx.useContext({
|
|
1328
|
-
sourceBudget
|
|
1355
|
+
sourceBudget,
|
|
1329
1356
|
sources: {
|
|
1330
|
-
|
|
1331
|
-
content: () => skillsRegistry.renderCatalog(2e3),
|
|
1332
|
-
max: 2e3,
|
|
1333
|
-
cache: `stable`
|
|
1334
|
-
},
|
|
1357
|
+
...loadedSkills.sources,
|
|
1335
1358
|
conversation: {
|
|
1336
1359
|
content: () => ctx.timelineMessages(),
|
|
1337
1360
|
cache: `volatile`
|
|
@@ -1344,7 +1367,7 @@ function createAssistantHandler(options) {
|
|
|
1344
1367
|
}
|
|
1345
1368
|
});
|
|
1346
1369
|
else if (agentsMd) ctx.useContext({
|
|
1347
|
-
sourceBudget
|
|
1370
|
+
sourceBudget,
|
|
1348
1371
|
sources: {
|
|
1349
1372
|
conversation: {
|
|
1350
1373
|
content: () => ctx.timelineMessages(),
|
|
@@ -1401,6 +1424,16 @@ function registerHorton(registry, options) {
|
|
|
1401
1424
|
registry.define(`horton`, {
|
|
1402
1425
|
description: `Friendly capable assistant — chat, code, research, dispatch`,
|
|
1403
1426
|
creationSchema: hortonCreationSchema,
|
|
1427
|
+
permissionGrants: [{
|
|
1428
|
+
subject_kind: `principal_kind`,
|
|
1429
|
+
subject_value: `user`,
|
|
1430
|
+
permission: `spawn`
|
|
1431
|
+
}, {
|
|
1432
|
+
subject_kind: `principal_kind`,
|
|
1433
|
+
subject_value: `user`,
|
|
1434
|
+
permission: `manage`
|
|
1435
|
+
}],
|
|
1436
|
+
slashCommands: (0, __electric_ax_agents_runtime.buildSkillSlashCommands)(skillsRegistry),
|
|
1404
1437
|
handler: assistantHandler
|
|
1405
1438
|
});
|
|
1406
1439
|
return [`horton`];
|
|
@@ -1442,7 +1475,7 @@ function parseWorkerArgs(value) {
|
|
|
1442
1475
|
if (typeof value.reasoningEffort === `string` && REASONING_EFFORT_VALUES.includes(value.reasoningEffort)) args.reasoningEffort = value.reasoningEffort;
|
|
1443
1476
|
return args;
|
|
1444
1477
|
}
|
|
1445
|
-
function buildToolsForWorker(tools, sandbox, ctx, readSet) {
|
|
1478
|
+
function buildToolsForWorker(tools, sandbox, ctx, readSet, opts) {
|
|
1446
1479
|
const out = [];
|
|
1447
1480
|
for (const name of tools) switch (name) {
|
|
1448
1481
|
case `bash`:
|
|
@@ -1461,7 +1494,10 @@ function buildToolsForWorker(tools, sandbox, ctx, readSet) {
|
|
|
1461
1494
|
out.push(__electric_ax_agents_runtime_tools.braveSearchTool);
|
|
1462
1495
|
break;
|
|
1463
1496
|
case `fetch_url`:
|
|
1464
|
-
out.push((0, __electric_ax_agents_runtime_tools.createFetchUrlTool)(sandbox
|
|
1497
|
+
out.push((0, __electric_ax_agents_runtime_tools.createFetchUrlTool)(sandbox, {
|
|
1498
|
+
catalog: opts.modelCatalog,
|
|
1499
|
+
modelConfig: opts.modelConfig
|
|
1500
|
+
}));
|
|
1465
1501
|
break;
|
|
1466
1502
|
case `spawn_worker`:
|
|
1467
1503
|
out.push(createSpawnWorkerTool(ctx));
|
|
@@ -1572,11 +1608,23 @@ function registerWorker(registry, options) {
|
|
|
1572
1608
|
const { streamFn, modelCatalog } = options;
|
|
1573
1609
|
registry.define(`worker`, {
|
|
1574
1610
|
description: `Internal — generic worker spawned by other agents. Configure via spawn args (systemPrompt + tools + optional sharedDb).`,
|
|
1611
|
+
permissionGrants: [{
|
|
1612
|
+
subject_kind: `principal_kind`,
|
|
1613
|
+
subject_value: `user`,
|
|
1614
|
+
permission: `spawn`
|
|
1615
|
+
}, {
|
|
1616
|
+
subject_kind: `principal_kind`,
|
|
1617
|
+
subject_value: `user`,
|
|
1618
|
+
permission: `manage`
|
|
1619
|
+
}],
|
|
1575
1620
|
async handler(ctx) {
|
|
1576
1621
|
const args = parseWorkerArgs(ctx.args);
|
|
1577
1622
|
const readSet = new Set();
|
|
1578
|
-
const builtinTools = buildToolsForWorker(args.tools, ctx.sandbox, ctx, readSet);
|
|
1579
1623
|
const modelConfig = resolveBuiltinModelConfig(modelCatalog, args);
|
|
1624
|
+
const builtinTools = buildToolsForWorker(args.tools, ctx.sandbox, ctx, readSet, {
|
|
1625
|
+
modelCatalog,
|
|
1626
|
+
modelConfig
|
|
1627
|
+
});
|
|
1580
1628
|
const sharedStateTools = [];
|
|
1581
1629
|
if (args.sharedDb) {
|
|
1582
1630
|
const shared = await ctx.observe((0, __electric_ax_agents_runtime.db)(args.sharedDb.id, args.sharedDb.schema));
|
|
@@ -1770,6 +1818,18 @@ function resolveCwd(args, fallback) {
|
|
|
1770
1818
|
return readWorkingDirectoryArg(args) ?? fallback;
|
|
1771
1819
|
}
|
|
1772
1820
|
|
|
1821
|
+
//#endregion
|
|
1822
|
+
//#region src/durable-streams-cache.ts
|
|
1823
|
+
const MEMORY_CACHE_SIZE_BYTES = 100 * 1024 * 1024;
|
|
1824
|
+
function installDurableStreamsFetchCache(options = {}) {
|
|
1825
|
+
if (options === false) return;
|
|
1826
|
+
const store = options.store === `sqlite` || options.sqliteLocation ? new undici.cacheStores.SqliteCacheStore({
|
|
1827
|
+
location: options.sqliteLocation,
|
|
1828
|
+
maxCount: options.maxCount
|
|
1829
|
+
}) : new undici.cacheStores.MemoryCacheStore({ maxSize: MEMORY_CACHE_SIZE_BYTES });
|
|
1830
|
+
(0, undici.setGlobalDispatcher)(new undici.Agent().compose(undici.interceptors.cache({ store })));
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1773
1833
|
//#endregion
|
|
1774
1834
|
//#region src/server.ts
|
|
1775
1835
|
var BuiltinAgentsServer = class {
|
|
@@ -1779,6 +1839,8 @@ var BuiltinAgentsServer = class {
|
|
|
1779
1839
|
mcpToolProviderName = null;
|
|
1780
1840
|
mcpApplyInFlight = new Set();
|
|
1781
1841
|
mcpStopping = false;
|
|
1842
|
+
mcpExtras = [];
|
|
1843
|
+
mcpLastJsonConfig = null;
|
|
1782
1844
|
pullWakeRunner = null;
|
|
1783
1845
|
options;
|
|
1784
1846
|
constructor(options) {
|
|
@@ -1788,8 +1850,70 @@ var BuiltinAgentsServer = class {
|
|
|
1788
1850
|
get mcpRegistry() {
|
|
1789
1851
|
return this._mcpRegistry;
|
|
1790
1852
|
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Replace the in-memory `extras` list and re-apply the merged config
|
|
1855
|
+
* against the last-known workspace `mcp.json` state. Workspace
|
|
1856
|
+
* `mcp.json` still wins on name collision. No-op once `stop()` has
|
|
1857
|
+
* latched `mcpStopping`.
|
|
1858
|
+
*/
|
|
1859
|
+
async setExtraMcpServers(extras) {
|
|
1860
|
+
if (!this._mcpRegistry || this.mcpStopping) return;
|
|
1861
|
+
this.mcpExtras = extras;
|
|
1862
|
+
await this.applyMerged(this.mcpLastJsonConfig);
|
|
1863
|
+
}
|
|
1864
|
+
async wirePersistence(cfg) {
|
|
1865
|
+
const servers = [];
|
|
1866
|
+
for (const s of cfg.servers) if (s.transport === `http` && s.auth?.mode === `authorizationCode`) {
|
|
1867
|
+
const persist = await (0, __electric_ax_agents_mcp.keychainPersistence)({ server: s.name });
|
|
1868
|
+
servers.push({
|
|
1869
|
+
...s,
|
|
1870
|
+
auth: {
|
|
1871
|
+
...s.auth,
|
|
1872
|
+
...persist
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
} else servers.push(s);
|
|
1876
|
+
return {
|
|
1877
|
+
...cfg,
|
|
1878
|
+
servers
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
mergeMcp(jsonCfg) {
|
|
1882
|
+
const jsonServers = jsonCfg?.servers ?? [];
|
|
1883
|
+
const jsonNames = new Set(jsonServers.map((s) => s.name));
|
|
1884
|
+
const filteredExtras = this.mcpExtras.filter((s) => !jsonNames.has(s.name));
|
|
1885
|
+
return {
|
|
1886
|
+
servers: [...filteredExtras, ...jsonServers],
|
|
1887
|
+
raw: jsonCfg?.raw
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
async runApply(jsonCfg) {
|
|
1891
|
+
if (this.mcpStopping) return;
|
|
1892
|
+
const registry = this._mcpRegistry;
|
|
1893
|
+
if (!registry) return;
|
|
1894
|
+
try {
|
|
1895
|
+
const wired = await this.wirePersistence(this.mergeMcp(jsonCfg));
|
|
1896
|
+
if (this.mcpStopping) return;
|
|
1897
|
+
await registry.applyConfig(wired);
|
|
1898
|
+
} catch (e) {
|
|
1899
|
+
serverLog.error(`[mcp] applyConfig:`, e);
|
|
1900
|
+
try {
|
|
1901
|
+
this.options.onConfigError?.(e);
|
|
1902
|
+
} catch (cbErr) {
|
|
1903
|
+
serverLog.error(`[mcp] onConfigError callback failed:`, cbErr);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
applyMerged(jsonCfg) {
|
|
1908
|
+
this.mcpLastJsonConfig = jsonCfg;
|
|
1909
|
+
const p = this.runApply(jsonCfg);
|
|
1910
|
+
this.mcpApplyInFlight.add(p);
|
|
1911
|
+
p.finally(() => this.mcpApplyInFlight.delete(p));
|
|
1912
|
+
return p;
|
|
1913
|
+
}
|
|
1791
1914
|
async start() {
|
|
1792
1915
|
if (this.bootstrap || this.pullWakeRunner) throw new Error(`Builtin agents runtime already started`);
|
|
1916
|
+
installDurableStreamsFetchCache(this.options.durableStreamsFetchCache);
|
|
1793
1917
|
const pullWake = this.options.pullWake;
|
|
1794
1918
|
if (!pullWake?.runnerId) throw new Error(`Builtin agents require a pull-wake runner id`);
|
|
1795
1919
|
try {
|
|
@@ -1800,76 +1924,28 @@ var BuiltinAgentsServer = class {
|
|
|
1800
1924
|
});
|
|
1801
1925
|
this._mcpRegistry = mcpRegistry;
|
|
1802
1926
|
const mcpConfigPath = this.options.loadProjectMcpConfig ? node_path.default.resolve(this.options.workingDirectory ?? process.cwd(), `mcp.json`) : null;
|
|
1803
|
-
|
|
1804
|
-
const wirePersistence = async (cfg) => {
|
|
1805
|
-
const servers = [];
|
|
1806
|
-
for (const s of cfg.servers) if (s.transport === `http` && s.auth?.mode === `authorizationCode`) {
|
|
1807
|
-
const persist = await (0, __electric_ax_agents_mcp.keychainPersistence)({ server: s.name });
|
|
1808
|
-
servers.push({
|
|
1809
|
-
...s,
|
|
1810
|
-
auth: {
|
|
1811
|
-
...s.auth,
|
|
1812
|
-
...persist
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
} else servers.push(s);
|
|
1816
|
-
return {
|
|
1817
|
-
...cfg,
|
|
1818
|
-
servers
|
|
1819
|
-
};
|
|
1820
|
-
};
|
|
1821
|
-
const merge = (jsonCfg) => {
|
|
1822
|
-
const jsonServers = jsonCfg?.servers ?? [];
|
|
1823
|
-
const jsonNames = new Set(jsonServers.map((s) => s.name));
|
|
1824
|
-
const filteredExtras = extras.filter((s) => !jsonNames.has(s.name));
|
|
1825
|
-
return {
|
|
1826
|
-
servers: [...filteredExtras, ...jsonServers],
|
|
1827
|
-
raw: jsonCfg?.raw
|
|
1828
|
-
};
|
|
1829
|
-
};
|
|
1830
|
-
const onConfigError = this.options.onConfigError;
|
|
1831
|
-
const runApply = async (jsonCfg) => {
|
|
1832
|
-
if (this.mcpStopping) return;
|
|
1833
|
-
try {
|
|
1834
|
-
const wired = await wirePersistence(merge(jsonCfg));
|
|
1835
|
-
if (this.mcpStopping) return;
|
|
1836
|
-
await mcpRegistry.applyConfig(wired);
|
|
1837
|
-
} catch (e) {
|
|
1838
|
-
serverLog.error(`[mcp] applyConfig:`, e);
|
|
1839
|
-
try {
|
|
1840
|
-
onConfigError?.(e);
|
|
1841
|
-
} catch (cbErr) {
|
|
1842
|
-
serverLog.error(`[mcp] onConfigError callback failed:`, cbErr);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
};
|
|
1846
|
-
const applyMerged = (jsonCfg) => {
|
|
1847
|
-
const p = runApply(jsonCfg);
|
|
1848
|
-
this.mcpApplyInFlight.add(p);
|
|
1849
|
-
p.finally(() => this.mcpApplyInFlight.delete(p));
|
|
1850
|
-
return p;
|
|
1851
|
-
};
|
|
1927
|
+
this.mcpExtras = this.options.extraMcpServers ?? [];
|
|
1852
1928
|
if (mcpConfigPath) {
|
|
1853
1929
|
try {
|
|
1854
1930
|
const cfg = await (0, __electric_ax_agents_mcp.loadConfig)(mcpConfigPath, process.env);
|
|
1855
|
-
applyMerged(cfg);
|
|
1931
|
+
this.applyMerged(cfg);
|
|
1856
1932
|
} catch (err) {
|
|
1857
1933
|
if (err.code !== `ENOENT`) throw err;
|
|
1858
|
-
if (
|
|
1859
|
-
else serverLog.info(`[mcp] no ${mcpConfigPath} — starting with ${
|
|
1860
|
-
applyMerged(null);
|
|
1934
|
+
if (this.mcpExtras.length === 0) serverLog.info(`[mcp] no ${mcpConfigPath} — starting with no servers`);
|
|
1935
|
+
else serverLog.info(`[mcp] no ${mcpConfigPath} — starting with ${this.mcpExtras.length} server(s) from extras`);
|
|
1936
|
+
this.applyMerged(null);
|
|
1861
1937
|
}
|
|
1862
1938
|
try {
|
|
1863
1939
|
this.mcpWatcherCloser = await (0, __electric_ax_agents_mcp.watchConfig)(mcpConfigPath, {
|
|
1864
|
-
onChange: (cfg) => void applyMerged(cfg),
|
|
1940
|
+
onChange: (cfg) => void this.applyMerged(cfg),
|
|
1865
1941
|
onError: (e) => serverLog.error(`[mcp] config error:`, e)
|
|
1866
1942
|
});
|
|
1867
1943
|
} catch (e) {
|
|
1868
1944
|
serverLog.error(`[mcp] config watcher failed to start:`, e);
|
|
1869
1945
|
}
|
|
1870
1946
|
} else {
|
|
1871
|
-
if (
|
|
1872
|
-
applyMerged(null);
|
|
1947
|
+
if (this.mcpExtras.length > 0) serverLog.info(`[mcp] starting with ${this.mcpExtras.length} server(s) from extras`);
|
|
1948
|
+
this.applyMerged(null);
|
|
1873
1949
|
}
|
|
1874
1950
|
this.mcpToolProviderName = `mcp`;
|
|
1875
1951
|
(0, __electric_ax_agents_runtime.registerToolProvider)({
|
package/dist/index.d.cts
CHANGED
|
@@ -36,12 +36,26 @@ declare function createAgentHandler(agentServerUrl: string, workingDirectory?: s
|
|
|
36
36
|
declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
|
|
37
37
|
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
38
38
|
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/durable-streams-cache.d.ts
|
|
41
|
+
type DurableStreamsFetchCacheOptions = false | {
|
|
42
|
+
store?: `memory` | `sqlite`;
|
|
43
|
+
sqliteLocation?: string;
|
|
44
|
+
maxCount?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
39
47
|
//#endregion
|
|
40
48
|
//#region src/server.d.ts
|
|
41
49
|
interface BuiltinAgentsServerOptions {
|
|
42
50
|
agentServerUrl: string;
|
|
43
51
|
workingDirectory?: string;
|
|
44
52
|
mockStreamFn?: StreamFn;
|
|
53
|
+
/**
|
|
54
|
+
* Configure the process-wide HTTP cache used by Undici-backed fetch calls.
|
|
55
|
+
* Defaults to a 100 MiB in-memory cache. Pass `false` to leave the global
|
|
56
|
+
* dispatcher unchanged.
|
|
57
|
+
*/
|
|
58
|
+
durableStreamsFetchCache?: DurableStreamsFetchCacheOptions;
|
|
45
59
|
/** Pull-wake runner configuration for built-in agents. */
|
|
46
60
|
pullWake: {
|
|
47
61
|
runnerId: string;
|
|
@@ -92,11 +106,24 @@ declare class BuiltinAgentsServer {
|
|
|
92
106
|
private mcpToolProviderName;
|
|
93
107
|
private mcpApplyInFlight;
|
|
94
108
|
private mcpStopping;
|
|
109
|
+
private mcpExtras;
|
|
110
|
+
private mcpLastJsonConfig;
|
|
95
111
|
private pullWakeRunner;
|
|
96
112
|
readonly options: BuiltinAgentsServerOptions;
|
|
97
113
|
constructor(options: BuiltinAgentsServerOptions);
|
|
98
114
|
/** Embedded MCP registry. `null` until `start()` has run. */
|
|
99
115
|
get mcpRegistry(): Registry | null;
|
|
116
|
+
/**
|
|
117
|
+
* Replace the in-memory `extras` list and re-apply the merged config
|
|
118
|
+
* against the last-known workspace `mcp.json` state. Workspace
|
|
119
|
+
* `mcp.json` still wins on name collision. No-op once `stop()` has
|
|
120
|
+
* latched `mcpStopping`.
|
|
121
|
+
*/
|
|
122
|
+
setExtraMcpServers(extras: ReadonlyArray<McpServerConfig$1>): Promise<void>;
|
|
123
|
+
private wirePersistence;
|
|
124
|
+
private mergeMcp;
|
|
125
|
+
private runApply;
|
|
126
|
+
private applyMerged;
|
|
100
127
|
start(): Promise<string>;
|
|
101
128
|
stop(): Promise<void>;
|
|
102
129
|
private registerPullWakeRunner;
|
package/dist/index.d.ts
CHANGED
|
@@ -36,12 +36,26 @@ declare function createAgentHandler(agentServerUrl: string, workingDirectory?: s
|
|
|
36
36
|
declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
|
|
37
37
|
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
38
38
|
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/durable-streams-cache.d.ts
|
|
41
|
+
type DurableStreamsFetchCacheOptions = false | {
|
|
42
|
+
store?: `memory` | `sqlite`;
|
|
43
|
+
sqliteLocation?: string;
|
|
44
|
+
maxCount?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
39
47
|
//#endregion
|
|
40
48
|
//#region src/server.d.ts
|
|
41
49
|
interface BuiltinAgentsServerOptions {
|
|
42
50
|
agentServerUrl: string;
|
|
43
51
|
workingDirectory?: string;
|
|
44
52
|
mockStreamFn?: StreamFn;
|
|
53
|
+
/**
|
|
54
|
+
* Configure the process-wide HTTP cache used by Undici-backed fetch calls.
|
|
55
|
+
* Defaults to a 100 MiB in-memory cache. Pass `false` to leave the global
|
|
56
|
+
* dispatcher unchanged.
|
|
57
|
+
*/
|
|
58
|
+
durableStreamsFetchCache?: DurableStreamsFetchCacheOptions;
|
|
45
59
|
/** Pull-wake runner configuration for built-in agents. */
|
|
46
60
|
pullWake: {
|
|
47
61
|
runnerId: string;
|
|
@@ -92,11 +106,24 @@ declare class BuiltinAgentsServer {
|
|
|
92
106
|
private mcpToolProviderName;
|
|
93
107
|
private mcpApplyInFlight;
|
|
94
108
|
private mcpStopping;
|
|
109
|
+
private mcpExtras;
|
|
110
|
+
private mcpLastJsonConfig;
|
|
95
111
|
private pullWakeRunner;
|
|
96
112
|
readonly options: BuiltinAgentsServerOptions;
|
|
97
113
|
constructor(options: BuiltinAgentsServerOptions);
|
|
98
114
|
/** Embedded MCP registry. `null` until `start()` has run. */
|
|
99
115
|
get mcpRegistry(): Registry | null;
|
|
116
|
+
/**
|
|
117
|
+
* Replace the in-memory `extras` list and re-apply the merged config
|
|
118
|
+
* against the last-known workspace `mcp.json` state. Workspace
|
|
119
|
+
* `mcp.json` still wins on name collision. No-op once `stop()` has
|
|
120
|
+
* latched `mcpStopping`.
|
|
121
|
+
*/
|
|
122
|
+
setExtraMcpServers(extras: ReadonlyArray<McpServerConfig$1>): Promise<void>;
|
|
123
|
+
private wirePersistence;
|
|
124
|
+
private mergeMcp;
|
|
125
|
+
private runApply;
|
|
126
|
+
private applyMerged;
|
|
100
127
|
start(): Promise<string>;
|
|
101
128
|
stop(): Promise<void>;
|
|
102
129
|
private registerPullWakeRunner;
|