@electric-ax/agents 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoint.js +44 -24
- package/dist/index.cjs +34 -24
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +34 -24
- package/dist/server-headers-65vIhxvJ.cjs +25 -0
- package/dist/server-headers-KD5yHFYT.js +12 -0
- package/dist/server-headers.cjs +4 -0
- package/dist/server-headers.d.cts +6 -0
- package/dist/server-headers.d.ts +6 -0
- package/dist/server-headers.js +3 -0
- package/docs/index.md +3 -3
- package/docs/reference/built-in-collections.md +1 -1
- package/docs/reference/cli.md +2 -0
- package/docs/reference/wake-event.md +4 -4
- package/docs/usage/embedded-builtins.md +2 -0
- package/docs/usage/spawning-and-coordinating.md +1 -1
- package/docs/usage/waking-entities.md +5 -5
- package/docs/usage/writing-handlers.md +1 -1
- package/package.json +14 -4
package/dist/entrypoint.js
CHANGED
|
@@ -625,8 +625,8 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
625
625
|
logPrefix: `[horton-docs]`
|
|
626
626
|
});
|
|
627
627
|
function resolveCurrentQuestion(wake, events, inbox) {
|
|
628
|
-
if (wake.type === `
|
|
629
|
-
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `
|
|
628
|
+
if (wake.type === `inbox`) {
|
|
629
|
+
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `inbox`).map((event) => event.value));
|
|
630
630
|
if (eventQuestion) return eventQuestion;
|
|
631
631
|
}
|
|
632
632
|
const wakeQuestion = payloadToText(wake.payload).trim();
|
|
@@ -885,11 +885,11 @@ function createSpawnWorkerTool(ctx, modelConfig) {
|
|
|
885
885
|
return {
|
|
886
886
|
name: `spawn_worker`,
|
|
887
887
|
label: `Spawn Worker`,
|
|
888
|
-
description: `Dispatch a subagent (worker) to perform an isolated subtask. Provide a system prompt
|
|
888
|
+
description: `Dispatch a subagent (worker) to perform an isolated subtask. Provide a brief system prompt to give it its role and then a detailed initialMessage which briefs the worker like a colleague who just walked into the room (file paths, line numbers, what specifically to do, what form of answer you want back) and pick the subset of tools the worker needs.`,
|
|
889
889
|
parameters: Type.Object({
|
|
890
|
-
systemPrompt: Type.String({ description: `System prompt for the worker
|
|
890
|
+
systemPrompt: Type.String({ description: `System prompt for the worker.` }),
|
|
891
891
|
tools: Type.Array(Type.Union(WORKER_TOOL_NAMES.map((n) => Type.Literal(n))), { description: `Subset of tool names to enable for the worker. Must include at least one.` }),
|
|
892
|
-
initialMessage: Type.String({ description: `First user message sent to the worker. This is what kicks off its run — without it the worker will idle. Describe the concrete task to perform.` })
|
|
892
|
+
initialMessage: Type.String({ description: `First user message sent to the worker. Be concrete: include file paths, line numbers, and the form of answer you want back. This is what kicks off its run — without it the worker will idle. Describe the concrete task to perform and what form of message you want back.` })
|
|
893
893
|
}),
|
|
894
894
|
execute: async (_toolCallId, params) => {
|
|
895
895
|
const { systemPrompt, tools, initialMessage } = params;
|
|
@@ -1426,7 +1426,8 @@ function registerHorton(registry, options) {
|
|
|
1426
1426
|
const { workingDirectory, streamFn, skillsRegistry = null, modelCatalog } = options;
|
|
1427
1427
|
const docsUrl = options.docsUrl ?? process.env.HORTON_DOCS_URL;
|
|
1428
1428
|
if (process.env.BRAVE_SEARCH_API_KEY) serverLog.info(`[horton] Web search: using Brave Search API`);
|
|
1429
|
-
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search
|
|
1429
|
+
else if (process.env.ANTHROPIC_API_KEY) serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search`);
|
|
1430
|
+
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY and ANTHROPIC_API_KEY not set — web search tool will be unavailable`);
|
|
1430
1431
|
const docsSupport = createHortonDocsSupport(workingDirectory);
|
|
1431
1432
|
const docsSearchTool = docsSupport?.createSearchTool();
|
|
1432
1433
|
docsSupport?.ensureReady().catch((error) => {
|
|
@@ -1902,7 +1903,7 @@ function truncate(str, max) {
|
|
|
1902
1903
|
//#endregion
|
|
1903
1904
|
//#region src/bootstrap.ts
|
|
1904
1905
|
async function createBuiltinAgentHandler(options) {
|
|
1905
|
-
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1906
|
+
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1906
1907
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
1907
1908
|
if (!modelCatalog) {
|
|
1908
1909
|
serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
|
|
@@ -1910,7 +1911,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1910
1911
|
}
|
|
1911
1912
|
const cwd = workingDirectory ?? process.cwd();
|
|
1912
1913
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
1913
|
-
const baseSkillsDir = path.resolve(here, `../skills`);
|
|
1914
|
+
const baseSkillsDir = baseSkillsDirOverride ?? path.resolve(here, `../skills`);
|
|
1914
1915
|
let skillsRegistry = null;
|
|
1915
1916
|
try {
|
|
1916
1917
|
skillsRegistry = await createSkillsRegistry({
|
|
@@ -1962,6 +1963,19 @@ async function registerBuiltinAgentTypes(bootstrap) {
|
|
|
1962
1963
|
|
|
1963
1964
|
//#endregion
|
|
1964
1965
|
//#region src/server.ts
|
|
1966
|
+
const PRINCIPAL_KEY_PREFIXES = new Set([
|
|
1967
|
+
`user`,
|
|
1968
|
+
`agent`,
|
|
1969
|
+
`service`,
|
|
1970
|
+
`system`
|
|
1971
|
+
]);
|
|
1972
|
+
function normalizeOwnerUserId(ownerUserId) {
|
|
1973
|
+
const trimmed = ownerUserId?.trim();
|
|
1974
|
+
if (!trimmed) return void 0;
|
|
1975
|
+
const colon = trimmed.indexOf(`:`);
|
|
1976
|
+
if (colon > 0 && PRINCIPAL_KEY_PREFIXES.has(trimmed.slice(0, colon))) return trimmed;
|
|
1977
|
+
return `user:${trimmed}`;
|
|
1978
|
+
}
|
|
1965
1979
|
var BuiltinAgentsServer = class {
|
|
1966
1980
|
bootstrap = null;
|
|
1967
1981
|
_mcpRegistry = null;
|
|
@@ -2098,6 +2112,7 @@ var BuiltinAgentsServer = class {
|
|
|
2098
2112
|
createElectricTools: this.options.createElectricTools,
|
|
2099
2113
|
publicUrl,
|
|
2100
2114
|
runtimeName: `builtin-agents`,
|
|
2115
|
+
baseSkillsDir: this.options.baseSkillsDir,
|
|
2101
2116
|
serverHeaders: pullWake.headers
|
|
2102
2117
|
});
|
|
2103
2118
|
if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
|
|
@@ -2165,22 +2180,35 @@ var BuiltinAgentsServer = class {
|
|
|
2165
2180
|
async registerPullWakeRunner(pullWake) {
|
|
2166
2181
|
const headers = new Headers(typeof pullWake.headers === `function` ? await pullWake.headers() : pullWake.headers);
|
|
2167
2182
|
headers.set(`content-type`, `application/json`);
|
|
2183
|
+
const ownerUserId = normalizeOwnerUserId(pullWake.ownerUserId);
|
|
2184
|
+
const body = {
|
|
2185
|
+
id: pullWake.runnerId,
|
|
2186
|
+
label: pullWake.label ?? `Built-in agents`,
|
|
2187
|
+
kind: `local`,
|
|
2188
|
+
admin_status: `enabled`
|
|
2189
|
+
};
|
|
2190
|
+
if (ownerUserId) body.owner_user_id = ownerUserId;
|
|
2168
2191
|
const response = await fetch(appendPathToUrl(this.options.agentServerUrl, `/_electric/runners`), {
|
|
2169
2192
|
method: `POST`,
|
|
2170
2193
|
headers,
|
|
2171
|
-
body: JSON.stringify(
|
|
2172
|
-
id: pullWake.runnerId,
|
|
2173
|
-
owner_user_id: pullWake.ownerUserId,
|
|
2174
|
-
label: pullWake.label ?? `Built-in agents`,
|
|
2175
|
-
kind: `local`,
|
|
2176
|
-
admin_status: `enabled`
|
|
2177
|
-
})
|
|
2194
|
+
body: JSON.stringify(body)
|
|
2178
2195
|
});
|
|
2179
2196
|
if (!response.ok) throw new Error(`Failed to register pull-wake runner ${pullWake.runnerId}: ${response.status} ${await response.text()}`);
|
|
2180
2197
|
return await response.json();
|
|
2181
2198
|
}
|
|
2182
2199
|
};
|
|
2183
2200
|
|
|
2201
|
+
//#endregion
|
|
2202
|
+
//#region src/server-headers.ts
|
|
2203
|
+
const ELECTRIC_PRINCIPAL_HEADER = `electric-principal`;
|
|
2204
|
+
function mergeElectricPrincipalHeader(headers, principal) {
|
|
2205
|
+
const merged = new Headers(headers);
|
|
2206
|
+
const trimmedPrincipal = principal?.trim();
|
|
2207
|
+
if (trimmedPrincipal !== void 0 && trimmedPrincipal.length > 0) merged.set(ELECTRIC_PRINCIPAL_HEADER, trimmedPrincipal);
|
|
2208
|
+
const normalized = Object.fromEntries(merged.entries());
|
|
2209
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2184
2212
|
//#endregion
|
|
2185
2213
|
//#region src/entrypoint-lib.ts
|
|
2186
2214
|
function readEnv(env, names) {
|
|
@@ -2203,14 +2231,6 @@ function validateUrl(name, value) {
|
|
|
2203
2231
|
throw new Error(`Invalid ${name}: "${value}"`);
|
|
2204
2232
|
}
|
|
2205
2233
|
}
|
|
2206
|
-
function buildAssertedAuthHeaders(env) {
|
|
2207
|
-
const headers = {};
|
|
2208
|
-
const email = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_EMAIL`]);
|
|
2209
|
-
const name = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_NAME`]);
|
|
2210
|
-
if (email) headers[`X-Electric-Asserted-Email`] = email;
|
|
2211
|
-
if (name) headers[`X-Electric-Asserted-Name`] = name;
|
|
2212
|
-
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
2213
|
-
}
|
|
2214
2234
|
function parseAdditionalServerHeaders(env) {
|
|
2215
2235
|
const raw = readEnv(env, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
|
|
2216
2236
|
if (!raw) return void 0;
|
|
@@ -2244,7 +2264,7 @@ function hasHeader(headers, name) {
|
|
|
2244
2264
|
function resolveBuiltinAgentsEntrypointOptions(env = process.env, cwd = process.cwd()) {
|
|
2245
2265
|
const agentServerUrl = validateUrl(`agent server URL`, readRequiredEnv(env, [`ELECTRIC_AGENTS_SERVER_URL`, `ELECTRIC_AGENTS_BASE_URL`], `agent server base URL`));
|
|
2246
2266
|
const runnerId = readRequiredEnv(env, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`], `pull-wake runner id`);
|
|
2247
|
-
const serverHeaders = mergeHeaders(
|
|
2267
|
+
const serverHeaders = mergeHeaders(mergeElectricPrincipalHeader(parseAdditionalServerHeaders(env), readEnv(env, [`ELECTRIC_AGENTS_PRINCIPAL`])));
|
|
2248
2268
|
return {
|
|
2249
2269
|
agentServerUrl,
|
|
2250
2270
|
workingDirectory: readEnv(env, [`ELECTRIC_AGENTS_WORKING_DIRECTORY`, `WORKING_DIRECTORY`]) ?? cwd,
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
|
|
24
24
|
//#endregion
|
|
25
|
+
const require_server_headers = require('./server-headers-65vIhxvJ.cjs');
|
|
25
26
|
const node_path = __toESM(require("node:path"));
|
|
26
27
|
const node_url = __toESM(require("node:url"));
|
|
27
28
|
const __electric_ax_agents_runtime = __toESM(require("@electric-ax/agents-runtime"));
|
|
@@ -648,8 +649,8 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
648
649
|
logPrefix: `[horton-docs]`
|
|
649
650
|
});
|
|
650
651
|
function resolveCurrentQuestion(wake, events, inbox) {
|
|
651
|
-
if (wake.type === `
|
|
652
|
-
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `
|
|
652
|
+
if (wake.type === `inbox`) {
|
|
653
|
+
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `inbox`).map((event) => event.value));
|
|
653
654
|
if (eventQuestion) return eventQuestion;
|
|
654
655
|
}
|
|
655
656
|
const wakeQuestion = payloadToText(wake.payload).trim();
|
|
@@ -908,11 +909,11 @@ function createSpawnWorkerTool(ctx, modelConfig) {
|
|
|
908
909
|
return {
|
|
909
910
|
name: `spawn_worker`,
|
|
910
911
|
label: `Spawn Worker`,
|
|
911
|
-
description: `Dispatch a subagent (worker) to perform an isolated subtask. Provide a system prompt
|
|
912
|
+
description: `Dispatch a subagent (worker) to perform an isolated subtask. Provide a brief system prompt to give it its role and then a detailed initialMessage which briefs the worker like a colleague who just walked into the room (file paths, line numbers, what specifically to do, what form of answer you want back) and pick the subset of tools the worker needs.`,
|
|
912
913
|
parameters: __sinclair_typebox.Type.Object({
|
|
913
|
-
systemPrompt: __sinclair_typebox.Type.String({ description: `System prompt for the worker
|
|
914
|
+
systemPrompt: __sinclair_typebox.Type.String({ description: `System prompt for the worker.` }),
|
|
914
915
|
tools: __sinclair_typebox.Type.Array(__sinclair_typebox.Type.Union(WORKER_TOOL_NAMES.map((n) => __sinclair_typebox.Type.Literal(n))), { description: `Subset of tool names to enable for the worker. Must include at least one.` }),
|
|
915
|
-
initialMessage: __sinclair_typebox.Type.String({ description: `First user message sent to the worker. This is what kicks off its run — without it the worker will idle. Describe the concrete task to perform.` })
|
|
916
|
+
initialMessage: __sinclair_typebox.Type.String({ description: `First user message sent to the worker. Be concrete: include file paths, line numbers, and the form of answer you want back. This is what kicks off its run — without it the worker will idle. Describe the concrete task to perform and what form of message you want back.` })
|
|
916
917
|
}),
|
|
917
918
|
execute: async (_toolCallId, params) => {
|
|
918
919
|
const { systemPrompt, tools, initialMessage } = params;
|
|
@@ -1450,7 +1451,8 @@ function registerHorton(registry, options) {
|
|
|
1450
1451
|
const { workingDirectory, streamFn, skillsRegistry = null, modelCatalog } = options;
|
|
1451
1452
|
const docsUrl = options.docsUrl ?? process.env.HORTON_DOCS_URL;
|
|
1452
1453
|
if (process.env.BRAVE_SEARCH_API_KEY) serverLog.info(`[horton] Web search: using Brave Search API`);
|
|
1453
|
-
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search
|
|
1454
|
+
else if (process.env.ANTHROPIC_API_KEY) serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search`);
|
|
1455
|
+
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY and ANTHROPIC_API_KEY not set — web search tool will be unavailable`);
|
|
1454
1456
|
const docsSupport = createHortonDocsSupport(workingDirectory);
|
|
1455
1457
|
const docsSearchTool = docsSupport?.createSearchTool();
|
|
1456
1458
|
docsSupport?.ensureReady().catch((error) => {
|
|
@@ -1927,7 +1929,7 @@ function truncate(str, max) {
|
|
|
1927
1929
|
//#region src/bootstrap.ts
|
|
1928
1930
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
1929
1931
|
async function createBuiltinAgentHandler(options) {
|
|
1930
|
-
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1932
|
+
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1931
1933
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
1932
1934
|
if (!modelCatalog) {
|
|
1933
1935
|
serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
|
|
@@ -1935,7 +1937,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1935
1937
|
}
|
|
1936
1938
|
const cwd = workingDirectory ?? process.cwd();
|
|
1937
1939
|
const here = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
1938
|
-
const baseSkillsDir = node_path.default.resolve(here, `../skills`);
|
|
1940
|
+
const baseSkillsDir = baseSkillsDirOverride ?? node_path.default.resolve(here, `../skills`);
|
|
1939
1941
|
let skillsRegistry = null;
|
|
1940
1942
|
try {
|
|
1941
1943
|
skillsRegistry = await createSkillsRegistry({
|
|
@@ -1997,6 +1999,19 @@ const registerAgentTypes = registerBuiltinAgentTypes;
|
|
|
1997
1999
|
|
|
1998
2000
|
//#endregion
|
|
1999
2001
|
//#region src/server.ts
|
|
2002
|
+
const PRINCIPAL_KEY_PREFIXES = new Set([
|
|
2003
|
+
`user`,
|
|
2004
|
+
`agent`,
|
|
2005
|
+
`service`,
|
|
2006
|
+
`system`
|
|
2007
|
+
]);
|
|
2008
|
+
function normalizeOwnerUserId(ownerUserId) {
|
|
2009
|
+
const trimmed = ownerUserId?.trim();
|
|
2010
|
+
if (!trimmed) return void 0;
|
|
2011
|
+
const colon = trimmed.indexOf(`:`);
|
|
2012
|
+
if (colon > 0 && PRINCIPAL_KEY_PREFIXES.has(trimmed.slice(0, colon))) return trimmed;
|
|
2013
|
+
return `user:${trimmed}`;
|
|
2014
|
+
}
|
|
2000
2015
|
var BuiltinAgentsServer = class {
|
|
2001
2016
|
bootstrap = null;
|
|
2002
2017
|
_mcpRegistry = null;
|
|
@@ -2133,6 +2148,7 @@ var BuiltinAgentsServer = class {
|
|
|
2133
2148
|
createElectricTools: this.options.createElectricTools,
|
|
2134
2149
|
publicUrl,
|
|
2135
2150
|
runtimeName: `builtin-agents`,
|
|
2151
|
+
baseSkillsDir: this.options.baseSkillsDir,
|
|
2136
2152
|
serverHeaders: pullWake.headers
|
|
2137
2153
|
});
|
|
2138
2154
|
if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
|
|
@@ -2200,16 +2216,18 @@ var BuiltinAgentsServer = class {
|
|
|
2200
2216
|
async registerPullWakeRunner(pullWake) {
|
|
2201
2217
|
const headers = new Headers(typeof pullWake.headers === `function` ? await pullWake.headers() : pullWake.headers);
|
|
2202
2218
|
headers.set(`content-type`, `application/json`);
|
|
2219
|
+
const ownerUserId = normalizeOwnerUserId(pullWake.ownerUserId);
|
|
2220
|
+
const body = {
|
|
2221
|
+
id: pullWake.runnerId,
|
|
2222
|
+
label: pullWake.label ?? `Built-in agents`,
|
|
2223
|
+
kind: `local`,
|
|
2224
|
+
admin_status: `enabled`
|
|
2225
|
+
};
|
|
2226
|
+
if (ownerUserId) body.owner_user_id = ownerUserId;
|
|
2203
2227
|
const response = await fetch((0, __electric_ax_agents_runtime.appendPathToUrl)(this.options.agentServerUrl, `/_electric/runners`), {
|
|
2204
2228
|
method: `POST`,
|
|
2205
2229
|
headers,
|
|
2206
|
-
body: JSON.stringify(
|
|
2207
|
-
id: pullWake.runnerId,
|
|
2208
|
-
owner_user_id: pullWake.ownerUserId,
|
|
2209
|
-
label: pullWake.label ?? `Built-in agents`,
|
|
2210
|
-
kind: `local`,
|
|
2211
|
-
admin_status: `enabled`
|
|
2212
|
-
})
|
|
2230
|
+
body: JSON.stringify(body)
|
|
2213
2231
|
});
|
|
2214
2232
|
if (!response.ok) throw new Error(`Failed to register pull-wake runner ${pullWake.runnerId}: ${response.status} ${await response.text()}`);
|
|
2215
2233
|
return await response.json();
|
|
@@ -2238,14 +2256,6 @@ function validateUrl(name, value) {
|
|
|
2238
2256
|
throw new Error(`Invalid ${name}: "${value}"`);
|
|
2239
2257
|
}
|
|
2240
2258
|
}
|
|
2241
|
-
function buildAssertedAuthHeaders(env) {
|
|
2242
|
-
const headers = {};
|
|
2243
|
-
const email = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_EMAIL`]);
|
|
2244
|
-
const name = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_NAME`]);
|
|
2245
|
-
if (email) headers[`X-Electric-Asserted-Email`] = email;
|
|
2246
|
-
if (name) headers[`X-Electric-Asserted-Name`] = name;
|
|
2247
|
-
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
2248
|
-
}
|
|
2249
2259
|
function parseAdditionalServerHeaders(env) {
|
|
2250
2260
|
const raw = readEnv(env, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
|
|
2251
2261
|
if (!raw) return void 0;
|
|
@@ -2279,7 +2289,7 @@ function hasHeader(headers, name) {
|
|
|
2279
2289
|
function resolveBuiltinAgentsEntrypointOptions(env = process.env, cwd = process.cwd()) {
|
|
2280
2290
|
const agentServerUrl = validateUrl(`agent server URL`, readRequiredEnv(env, [`ELECTRIC_AGENTS_SERVER_URL`, `ELECTRIC_AGENTS_BASE_URL`], `agent server base URL`));
|
|
2281
2291
|
const runnerId = readRequiredEnv(env, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`], `pull-wake runner id`);
|
|
2282
|
-
const serverHeaders = mergeHeaders(
|
|
2292
|
+
const serverHeaders = mergeHeaders(require_server_headers.mergeElectricPrincipalHeader(parseAdditionalServerHeaders(env), readEnv(env, [`ELECTRIC_AGENTS_PRINCIPAL`])));
|
|
2283
2293
|
return {
|
|
2284
2294
|
agentServerUrl,
|
|
2285
2295
|
workingDirectory: readEnv(env, [`ELECTRIC_AGENTS_WORKING_DIRECTORY`, `WORKING_DIRECTORY`]) ?? cwd,
|
package/dist/index.d.cts
CHANGED
|
@@ -43,6 +43,8 @@ interface BuiltinAgentHandlerOptions {
|
|
|
43
43
|
streamFn?: StreamFn;
|
|
44
44
|
publicUrl?: string;
|
|
45
45
|
runtimeName?: string;
|
|
46
|
+
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
47
|
+
baseSkillsDir?: string;
|
|
46
48
|
serverHeaders?: HeadersProvider;
|
|
47
49
|
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
48
50
|
createElectricTools?: (context: {
|
|
@@ -125,6 +127,8 @@ interface BuiltinAgentsServerOptions {
|
|
|
125
127
|
* so the embedder must opt in.
|
|
126
128
|
*/
|
|
127
129
|
loadProjectMcpConfig?: boolean;
|
|
130
|
+
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
131
|
+
baseSkillsDir?: string;
|
|
128
132
|
createElectricTools?: (context: {
|
|
129
133
|
entityUrl: string;
|
|
130
134
|
entityType: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ interface BuiltinAgentHandlerOptions {
|
|
|
43
43
|
streamFn?: StreamFn;
|
|
44
44
|
publicUrl?: string;
|
|
45
45
|
runtimeName?: string;
|
|
46
|
+
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
47
|
+
baseSkillsDir?: string;
|
|
46
48
|
serverHeaders?: HeadersProvider;
|
|
47
49
|
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
48
50
|
createElectricTools?: (context: {
|
|
@@ -125,6 +127,8 @@ interface BuiltinAgentsServerOptions {
|
|
|
125
127
|
* so the embedder must opt in.
|
|
126
128
|
*/
|
|
127
129
|
loadProjectMcpConfig?: boolean;
|
|
130
|
+
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
131
|
+
baseSkillsDir?: string;
|
|
128
132
|
createElectricTools?: (context: {
|
|
129
133
|
entityUrl: string;
|
|
130
134
|
entityType: string;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mergeElectricPrincipalHeader } from "./server-headers-KD5yHFYT.js";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
|
|
@@ -624,8 +625,8 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
624
625
|
logPrefix: `[horton-docs]`
|
|
625
626
|
});
|
|
626
627
|
function resolveCurrentQuestion(wake, events, inbox) {
|
|
627
|
-
if (wake.type === `
|
|
628
|
-
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `
|
|
628
|
+
if (wake.type === `inbox`) {
|
|
629
|
+
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `inbox`).map((event) => event.value));
|
|
629
630
|
if (eventQuestion) return eventQuestion;
|
|
630
631
|
}
|
|
631
632
|
const wakeQuestion = payloadToText(wake.payload).trim();
|
|
@@ -884,11 +885,11 @@ function createSpawnWorkerTool(ctx, modelConfig) {
|
|
|
884
885
|
return {
|
|
885
886
|
name: `spawn_worker`,
|
|
886
887
|
label: `Spawn Worker`,
|
|
887
|
-
description: `Dispatch a subagent (worker) to perform an isolated subtask. Provide a system prompt
|
|
888
|
+
description: `Dispatch a subagent (worker) to perform an isolated subtask. Provide a brief system prompt to give it its role and then a detailed initialMessage which briefs the worker like a colleague who just walked into the room (file paths, line numbers, what specifically to do, what form of answer you want back) and pick the subset of tools the worker needs.`,
|
|
888
889
|
parameters: Type.Object({
|
|
889
|
-
systemPrompt: Type.String({ description: `System prompt for the worker
|
|
890
|
+
systemPrompt: Type.String({ description: `System prompt for the worker.` }),
|
|
890
891
|
tools: Type.Array(Type.Union(WORKER_TOOL_NAMES.map((n) => Type.Literal(n))), { description: `Subset of tool names to enable for the worker. Must include at least one.` }),
|
|
891
|
-
initialMessage: Type.String({ description: `First user message sent to the worker. This is what kicks off its run — without it the worker will idle. Describe the concrete task to perform.` })
|
|
892
|
+
initialMessage: Type.String({ description: `First user message sent to the worker. Be concrete: include file paths, line numbers, and the form of answer you want back. This is what kicks off its run — without it the worker will idle. Describe the concrete task to perform and what form of message you want back.` })
|
|
892
893
|
}),
|
|
893
894
|
execute: async (_toolCallId, params) => {
|
|
894
895
|
const { systemPrompt, tools, initialMessage } = params;
|
|
@@ -1426,7 +1427,8 @@ function registerHorton(registry, options) {
|
|
|
1426
1427
|
const { workingDirectory, streamFn, skillsRegistry = null, modelCatalog } = options;
|
|
1427
1428
|
const docsUrl = options.docsUrl ?? process.env.HORTON_DOCS_URL;
|
|
1428
1429
|
if (process.env.BRAVE_SEARCH_API_KEY) serverLog.info(`[horton] Web search: using Brave Search API`);
|
|
1429
|
-
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search
|
|
1430
|
+
else if (process.env.ANTHROPIC_API_KEY) serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search`);
|
|
1431
|
+
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY and ANTHROPIC_API_KEY not set — web search tool will be unavailable`);
|
|
1430
1432
|
const docsSupport = createHortonDocsSupport(workingDirectory);
|
|
1431
1433
|
const docsSearchTool = docsSupport?.createSearchTool();
|
|
1432
1434
|
docsSupport?.ensureReady().catch((error) => {
|
|
@@ -1903,7 +1905,7 @@ function truncate(str, max) {
|
|
|
1903
1905
|
//#region src/bootstrap.ts
|
|
1904
1906
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
1905
1907
|
async function createBuiltinAgentHandler(options) {
|
|
1906
|
-
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1908
|
+
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1907
1909
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
1908
1910
|
if (!modelCatalog) {
|
|
1909
1911
|
serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
|
|
@@ -1911,7 +1913,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1911
1913
|
}
|
|
1912
1914
|
const cwd = workingDirectory ?? process.cwd();
|
|
1913
1915
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
1914
|
-
const baseSkillsDir = path.resolve(here, `../skills`);
|
|
1916
|
+
const baseSkillsDir = baseSkillsDirOverride ?? path.resolve(here, `../skills`);
|
|
1915
1917
|
let skillsRegistry = null;
|
|
1916
1918
|
try {
|
|
1917
1919
|
skillsRegistry = await createSkillsRegistry({
|
|
@@ -1973,6 +1975,19 @@ const registerAgentTypes = registerBuiltinAgentTypes;
|
|
|
1973
1975
|
|
|
1974
1976
|
//#endregion
|
|
1975
1977
|
//#region src/server.ts
|
|
1978
|
+
const PRINCIPAL_KEY_PREFIXES = new Set([
|
|
1979
|
+
`user`,
|
|
1980
|
+
`agent`,
|
|
1981
|
+
`service`,
|
|
1982
|
+
`system`
|
|
1983
|
+
]);
|
|
1984
|
+
function normalizeOwnerUserId(ownerUserId) {
|
|
1985
|
+
const trimmed = ownerUserId?.trim();
|
|
1986
|
+
if (!trimmed) return void 0;
|
|
1987
|
+
const colon = trimmed.indexOf(`:`);
|
|
1988
|
+
if (colon > 0 && PRINCIPAL_KEY_PREFIXES.has(trimmed.slice(0, colon))) return trimmed;
|
|
1989
|
+
return `user:${trimmed}`;
|
|
1990
|
+
}
|
|
1976
1991
|
var BuiltinAgentsServer = class {
|
|
1977
1992
|
bootstrap = null;
|
|
1978
1993
|
_mcpRegistry = null;
|
|
@@ -2109,6 +2124,7 @@ var BuiltinAgentsServer = class {
|
|
|
2109
2124
|
createElectricTools: this.options.createElectricTools,
|
|
2110
2125
|
publicUrl,
|
|
2111
2126
|
runtimeName: `builtin-agents`,
|
|
2127
|
+
baseSkillsDir: this.options.baseSkillsDir,
|
|
2112
2128
|
serverHeaders: pullWake.headers
|
|
2113
2129
|
});
|
|
2114
2130
|
if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
|
|
@@ -2176,16 +2192,18 @@ var BuiltinAgentsServer = class {
|
|
|
2176
2192
|
async registerPullWakeRunner(pullWake) {
|
|
2177
2193
|
const headers = new Headers(typeof pullWake.headers === `function` ? await pullWake.headers() : pullWake.headers);
|
|
2178
2194
|
headers.set(`content-type`, `application/json`);
|
|
2195
|
+
const ownerUserId = normalizeOwnerUserId(pullWake.ownerUserId);
|
|
2196
|
+
const body = {
|
|
2197
|
+
id: pullWake.runnerId,
|
|
2198
|
+
label: pullWake.label ?? `Built-in agents`,
|
|
2199
|
+
kind: `local`,
|
|
2200
|
+
admin_status: `enabled`
|
|
2201
|
+
};
|
|
2202
|
+
if (ownerUserId) body.owner_user_id = ownerUserId;
|
|
2179
2203
|
const response = await fetch(appendPathToUrl(this.options.agentServerUrl, `/_electric/runners`), {
|
|
2180
2204
|
method: `POST`,
|
|
2181
2205
|
headers,
|
|
2182
|
-
body: JSON.stringify(
|
|
2183
|
-
id: pullWake.runnerId,
|
|
2184
|
-
owner_user_id: pullWake.ownerUserId,
|
|
2185
|
-
label: pullWake.label ?? `Built-in agents`,
|
|
2186
|
-
kind: `local`,
|
|
2187
|
-
admin_status: `enabled`
|
|
2188
|
-
})
|
|
2206
|
+
body: JSON.stringify(body)
|
|
2189
2207
|
});
|
|
2190
2208
|
if (!response.ok) throw new Error(`Failed to register pull-wake runner ${pullWake.runnerId}: ${response.status} ${await response.text()}`);
|
|
2191
2209
|
return await response.json();
|
|
@@ -2214,14 +2232,6 @@ function validateUrl(name, value) {
|
|
|
2214
2232
|
throw new Error(`Invalid ${name}: "${value}"`);
|
|
2215
2233
|
}
|
|
2216
2234
|
}
|
|
2217
|
-
function buildAssertedAuthHeaders(env) {
|
|
2218
|
-
const headers = {};
|
|
2219
|
-
const email = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_EMAIL`]);
|
|
2220
|
-
const name = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_NAME`]);
|
|
2221
|
-
if (email) headers[`X-Electric-Asserted-Email`] = email;
|
|
2222
|
-
if (name) headers[`X-Electric-Asserted-Name`] = name;
|
|
2223
|
-
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
2224
|
-
}
|
|
2225
2235
|
function parseAdditionalServerHeaders(env) {
|
|
2226
2236
|
const raw = readEnv(env, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
|
|
2227
2237
|
if (!raw) return void 0;
|
|
@@ -2255,7 +2265,7 @@ function hasHeader(headers, name) {
|
|
|
2255
2265
|
function resolveBuiltinAgentsEntrypointOptions(env = process.env, cwd = process.cwd()) {
|
|
2256
2266
|
const agentServerUrl = validateUrl(`agent server URL`, readRequiredEnv(env, [`ELECTRIC_AGENTS_SERVER_URL`, `ELECTRIC_AGENTS_BASE_URL`], `agent server base URL`));
|
|
2257
2267
|
const runnerId = readRequiredEnv(env, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`], `pull-wake runner id`);
|
|
2258
|
-
const serverHeaders = mergeHeaders(
|
|
2268
|
+
const serverHeaders = mergeHeaders(mergeElectricPrincipalHeader(parseAdditionalServerHeaders(env), readEnv(env, [`ELECTRIC_AGENTS_PRINCIPAL`])));
|
|
2259
2269
|
return {
|
|
2260
2270
|
agentServerUrl,
|
|
2261
2271
|
workingDirectory: readEnv(env, [`ELECTRIC_AGENTS_WORKING_DIRECTORY`, `WORKING_DIRECTORY`]) ?? cwd,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
//#region src/server-headers.ts
|
|
4
|
+
const ELECTRIC_PRINCIPAL_HEADER = `electric-principal`;
|
|
5
|
+
function mergeElectricPrincipalHeader(headers, principal) {
|
|
6
|
+
const merged = new Headers(headers);
|
|
7
|
+
const trimmedPrincipal = principal?.trim();
|
|
8
|
+
if (trimmedPrincipal !== void 0 && trimmedPrincipal.length > 0) merged.set(ELECTRIC_PRINCIPAL_HEADER, trimmedPrincipal);
|
|
9
|
+
const normalized = Object.fromEntries(merged.entries());
|
|
10
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
Object.defineProperty(exports, 'ELECTRIC_PRINCIPAL_HEADER', {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return ELECTRIC_PRINCIPAL_HEADER;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(exports, 'mergeElectricPrincipalHeader', {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () {
|
|
23
|
+
return mergeElectricPrincipalHeader;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/server-headers.ts
|
|
2
|
+
const ELECTRIC_PRINCIPAL_HEADER = `electric-principal`;
|
|
3
|
+
function mergeElectricPrincipalHeader(headers, principal) {
|
|
4
|
+
const merged = new Headers(headers);
|
|
5
|
+
const trimmedPrincipal = principal?.trim();
|
|
6
|
+
if (trimmedPrincipal !== void 0 && trimmedPrincipal.length > 0) merged.set(ELECTRIC_PRINCIPAL_HEADER, trimmedPrincipal);
|
|
7
|
+
const normalized = Object.fromEntries(merged.entries());
|
|
8
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { ELECTRIC_PRINCIPAL_HEADER, mergeElectricPrincipalHeader };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/server-headers.d.ts
|
|
2
|
+
declare const ELECTRIC_PRINCIPAL_HEADER = "electric-principal";
|
|
3
|
+
declare function mergeElectricPrincipalHeader(headers: HeadersInit | undefined, principal: string | undefined): Record<string, string> | undefined;
|
|
4
|
+
|
|
5
|
+
//#endregion
|
|
6
|
+
export { ELECTRIC_PRINCIPAL_HEADER, mergeElectricPrincipalHeader };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/server-headers.d.ts
|
|
2
|
+
declare const ELECTRIC_PRINCIPAL_HEADER = "electric-principal";
|
|
3
|
+
declare function mergeElectricPrincipalHeader(headers: HeadersInit | undefined, principal: string | undefined): Record<string, string> | undefined;
|
|
4
|
+
|
|
5
|
+
//#endregion
|
|
6
|
+
export { ELECTRIC_PRINCIPAL_HEADER, mergeElectricPrincipalHeader };
|
package/docs/index.md
CHANGED
|
@@ -60,7 +60,7 @@ The function that runs when an entity wakes. Receives a [`HandlerContext`](/docs
|
|
|
60
60
|
```ts
|
|
61
61
|
registry.define("support", {
|
|
62
62
|
async handler(ctx, wake) {
|
|
63
|
-
if (wake.type === "
|
|
63
|
+
if (wake.type === "inbox") {
|
|
64
64
|
ctx.useAgent({
|
|
65
65
|
systemPrompt: "You are a support agent.",
|
|
66
66
|
model: "claude-sonnet-4-5-20250929",
|
|
@@ -78,11 +78,11 @@ Events that trigger a handler invocation. Wake sources include incoming messages
|
|
|
78
78
|
|
|
79
79
|
```ts
|
|
80
80
|
async handler(ctx, wake) {
|
|
81
|
-
// wake.type — "
|
|
81
|
+
// wake.type — "inbox", "wake", etc.
|
|
82
82
|
// wake.source — who triggered the wake
|
|
83
83
|
// wake.payload — message content or wake data
|
|
84
84
|
|
|
85
|
-
if (wake.type === "
|
|
85
|
+
if (wake.type === "inbox") {
|
|
86
86
|
const userMessage = wake.payload
|
|
87
87
|
// handle incoming message
|
|
88
88
|
}
|
|
@@ -23,7 +23,7 @@ Every entity automatically has these 17 collections, populated by the runtime as
|
|
|
23
23
|
| `toolCalls` | `tool_call` | `ToolCall` | Tool call lifecycle |
|
|
24
24
|
| `reasoning` | `reasoning` | `Reasoning` | Reasoning block lifecycle |
|
|
25
25
|
| `errors` | `error` | `ErrorEvent` | Diagnostic errors |
|
|
26
|
-
| `inbox` | `
|
|
26
|
+
| `inbox` | `inbox` | `MessageReceived` | Inbound messages |
|
|
27
27
|
| `wakes` | `wake` | `WakeEntry` | Wake delivery records |
|
|
28
28
|
| `entityCreated` | `entity_created` | `EntityCreated` | Entity bootstrap metadata |
|
|
29
29
|
| `entityStopped` | `entity_stopped` | `EntityStopped` | Entity shutdown signal |
|
package/docs/reference/cli.md
CHANGED
|
@@ -20,6 +20,8 @@ npm install -g electric-ax
|
|
|
20
20
|
| -------------------------------- | ----------------------- | -------------------------------------------- |
|
|
21
21
|
| `ELECTRIC_AGENTS_URL` | `http://localhost:4437` | Server URL for entity commands and built-ins |
|
|
22
22
|
| `ELECTRIC_AGENTS_IDENTITY` | `user@hostname` | Sender identity for messages |
|
|
23
|
+
| `ELECTRIC_AGENTS_PRINCIPAL` | - | Optional principal key sent as `Electric-Principal` |
|
|
24
|
+
| `ELECTRIC_AGENTS_SERVER_HEADERS` | - | Optional JSON object of additional server headers |
|
|
23
25
|
| `ELECTRIC_AGENTS_PORT` | `4437` | Port used by `start` / `quickstart` |
|
|
24
26
|
| `ELECTRIC_AGENTS_BUILTIN_PORT` | `4448` | Webhook port for `start-builtin` |
|
|
25
27
|
| `ELECTRIC_AGENTS_COMPOSE_PROJECT` | `electric-agents` | Docker Compose project name |
|
|
@@ -30,7 +30,7 @@ type WakeEvent = {
|
|
|
30
30
|
| Field | Type | Description |
|
|
31
31
|
| ------------ | --------- | ------------------------------------------------------------------------ |
|
|
32
32
|
| `source` | `string` | URL or identifier of the stream that triggered the wake. |
|
|
33
|
-
| `type` | `string` | Wake type. Usually `"
|
|
33
|
+
| `type` | `string` | Wake type. Usually `"inbox"` or `"wake"`; fallback webhook events can use `triggerEvent` or `"message"`. See catalog. |
|
|
34
34
|
| `fromOffset` | `number` | Start offset of new events in the source stream. |
|
|
35
35
|
| `toOffset` | `number` | End offset (exclusive) of new events. |
|
|
36
36
|
| `eventCount` | `number` | Number of new events in this wake. |
|
|
@@ -40,9 +40,9 @@ type WakeEvent = {
|
|
|
40
40
|
|
|
41
41
|
## Wake-type catalog
|
|
42
42
|
|
|
43
|
-
Handlers usually see two values for `wake.type`. Direct inbox messages arrive as `"
|
|
43
|
+
Handlers usually see two values for `wake.type`. Direct inbox messages arrive as `"inbox"`. Most non-message triggers are flattened into `"wake"`, with the specifics carried on `wake.payload`. Low-level webhook fallbacks can surface `triggerEvent` directly, or `"message"` when no trigger event is provided.
|
|
44
44
|
|
|
45
|
-
### `"
|
|
45
|
+
### `"inbox"`
|
|
46
46
|
|
|
47
47
|
An external message landed in the entity's inbox — from `ctx.send()`, the CLI's `electric agents send`, or any direct `/send` HTTP call.
|
|
48
48
|
|
|
@@ -89,7 +89,7 @@ Inspect the payload to distinguish the sub-kind:
|
|
|
89
89
|
| Observed change | `ctx.observe(..., { wake: { on: 'change' } })` or `observe(db(...))` | `payload.changes` is non-empty |
|
|
90
90
|
| Shared-state change | `await ctx.observe(db(...), { wake: { on: 'change' } })` | `payload.changes` is non-empty, `payload.source` identifies the shared-state stream |
|
|
91
91
|
| Cron fired | A cron schedule entry on the entity's manifest | `payload.source` identifies the schedule; `payload.changes` is empty |
|
|
92
|
-
| Scheduled send | A `future_send` schedule fires | Arrives as `"
|
|
92
|
+
| Scheduled send | A `future_send` schedule fires | Arrives as `"inbox"` (not `"wake"`) — the schedule produces a message delivery |
|
|
93
93
|
| Timeout | `timeoutMs` on a `change` wake config elapsed with no changes | `payload.timeout === true`, `payload.changes` is empty |
|
|
94
94
|
|
|
95
95
|
For the narrative on how these are produced, see [Waking entities](../usage/waking-entities).
|
|
@@ -168,6 +168,8 @@ Environment variables:
|
|
|
168
168
|
| Variable | Description |
|
|
169
169
|
| -------------------------------- | ----------------------------------------------------- |
|
|
170
170
|
| `ELECTRIC_AGENTS_SERVER_URL` | Required coordinator server URL. |
|
|
171
|
+
| `ELECTRIC_AGENTS_PRINCIPAL` | Optional principal key sent as `Electric-Principal`. |
|
|
172
|
+
| `ELECTRIC_AGENTS_SERVER_HEADERS` | Optional JSON object of additional server headers. |
|
|
171
173
|
| `ELECTRIC_AGENTS_BUILTIN_BASE_URL` | Public webhook base URL for the built-in server. |
|
|
172
174
|
| `ELECTRIC_AGENTS_BUILTIN_HOST` | Bind host. |
|
|
173
175
|
| `ELECTRIC_AGENTS_BUILTIN_PORT` | Built-in server port. Defaults to `4448`. |
|
|
@@ -31,13 +31,13 @@ There are five things that can wake an entity:
|
|
|
31
31
|
|
|
32
32
|
### 1. An incoming message
|
|
33
33
|
|
|
34
|
-
Any external `/send` (via the CLI, HTTP, or another entity's `ctx.send()`) appends a `
|
|
34
|
+
Any external `/send` (via the CLI, HTTP, or another entity's `ctx.send()`) appends a `inbox` event to the entity's stream, which wakes the handler:
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
ctx.send("/assistant/peer", { text: "hello" })
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
The receiving handler sees `wake.type === "
|
|
40
|
+
The receiving handler sees `wake.type === "inbox"` and finds the payload on `wake.payload`.
|
|
41
41
|
|
|
42
42
|
### 2. A spawned child
|
|
43
43
|
|
|
@@ -97,7 +97,7 @@ The minimum useful pattern is to branch on `wake.type`:
|
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
99
|
async handler(ctx, wake) {
|
|
100
|
-
if (wake.type === "
|
|
100
|
+
if (wake.type === "inbox") {
|
|
101
101
|
// external input - reply, dispatch, etc.
|
|
102
102
|
ctx.useAgent({ ... })
|
|
103
103
|
await ctx.agent.run()
|
|
@@ -112,8 +112,8 @@ async handler(ctx, wake) {
|
|
|
112
112
|
|
|
113
113
|
Two wake types reach handlers directly:
|
|
114
114
|
|
|
115
|
-
- `"
|
|
116
|
-
- `"wake"` — a synthesised wake for anything else (child finished, collection change, cron, timeout). The specifics are on `wake.payload`. A future-send schedule delivers a message, so it arrives as `"
|
|
115
|
+
- `"inbox"` — an external message was delivered to this entity's inbox.
|
|
116
|
+
- `"wake"` — a synthesised wake for anything else (child finished, collection change, cron, timeout). The specifics are on `wake.payload`. A future-send schedule delivers a message, so it arrives as `"inbox"`.
|
|
117
117
|
|
|
118
118
|
For the full payload shape (`changes[]`, `finished_child`, `other_children`, `timeout`), see the [wake-type catalog](../reference/wake-event#wake-type-catalog) in the reference.
|
|
119
119
|
|
|
@@ -120,7 +120,7 @@ type WakeEvent = {
|
|
|
120
120
|
| Field | Description |
|
|
121
121
|
| ------------ | -------------------------------------------------------------- |
|
|
122
122
|
| `source` | The stream or entity that caused the wake. |
|
|
123
|
-
| `type` | The wake type: `"
|
|
123
|
+
| `type` | The wake type: `"inbox"` for inbox messages or `"wake"` for child completion, observed changes, cron, and timeouts. |
|
|
124
124
|
| `fromOffset` | Start offset of the events that triggered this wake. |
|
|
125
125
|
| `toOffset` | End offset of the events that triggered this wake. |
|
|
126
126
|
| `eventCount` | Number of new events since last wake. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-ax/agents",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Built-in Electric Agents runtimes such as Horton and worker",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
"default": "./dist/index.cjs"
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
+
"./server-headers": {
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/server-headers.d.ts",
|
|
31
|
+
"default": "./dist/server-headers.js"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./dist/server-headers.d.cts",
|
|
35
|
+
"default": "./dist/server-headers.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
28
38
|
"./package.json": "./package.json"
|
|
29
39
|
},
|
|
30
40
|
"dependencies": {
|
|
@@ -32,14 +42,14 @@
|
|
|
32
42
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
|
33
43
|
"@mariozechner/pi-ai": "^0.70.2",
|
|
34
44
|
"@sinclair/typebox": "^0.34.48",
|
|
35
|
-
"better-sqlite3": "^
|
|
45
|
+
"better-sqlite3": "^12.9.0",
|
|
36
46
|
"nanoid": "^3.3.11",
|
|
37
47
|
"pino": "^10.3.1",
|
|
38
48
|
"pino-pretty": "^13.0.0",
|
|
39
49
|
"sqlite-vec": "^0.1.9",
|
|
40
50
|
"zod": "^4.3.6",
|
|
41
|
-
"@electric-ax/agents-mcp": "0.2.
|
|
42
|
-
"@electric-ax/agents-runtime": "0.2.
|
|
51
|
+
"@electric-ax/agents-mcp": "0.2.2",
|
|
52
|
+
"@electric-ax/agents-runtime": "0.2.1"
|
|
43
53
|
},
|
|
44
54
|
"devDependencies": {
|
|
45
55
|
"@types/better-sqlite3": "^7.6.13",
|