@ethosagent/core 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/index.d.ts +15 -0
- package/dist/index.js +161 -6
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -326,6 +326,12 @@ interface AgentLoopConfig {
|
|
|
326
326
|
* reports `CLARIFY_NO_SURFACE` and the agent falls back to plain prose.
|
|
327
327
|
*/
|
|
328
328
|
clarifyBridge?: ClarifyBridge;
|
|
329
|
+
/**
|
|
330
|
+
* Per-personality MCP tool policy loaded from mcp.yaml. NOT part of
|
|
331
|
+
* PersonalityConfig (frozen schema). Passed through from wiring so
|
|
332
|
+
* AgentLoop can build per-tool MCP allowlists in filterOpts.
|
|
333
|
+
*/
|
|
334
|
+
mcpPolicy?: _ethosagent_types.McpPolicy;
|
|
329
335
|
/**
|
|
330
336
|
* Optional request dump store. When provided, AgentLoop appends a full
|
|
331
337
|
* record of each LLM request/response for offline analysis and debugging.
|
|
@@ -396,8 +402,15 @@ interface RunOptions {
|
|
|
396
402
|
* Consumed once; does not persist across runs.
|
|
397
403
|
*/
|
|
398
404
|
tierOverride?: _ethosagent_types.ModelTierName;
|
|
405
|
+
/** Opaque user id (from IdentityMap). When present, USER.md is read from `user:<userId>` scope. */
|
|
406
|
+
userId?: string;
|
|
399
407
|
dryRun?: boolean;
|
|
400
408
|
dryRunMaxToolCalls?: number;
|
|
409
|
+
/**
|
|
410
|
+
* Override the personality's toolset for this run. Used by cron to exclude
|
|
411
|
+
* the `cron` tool from cron-spawned sessions (recursion guard).
|
|
412
|
+
*/
|
|
413
|
+
toolsetOverride?: string[];
|
|
401
414
|
}
|
|
402
415
|
declare class AgentLoop {
|
|
403
416
|
private readonly llm;
|
|
@@ -434,6 +447,8 @@ declare class AgentLoop {
|
|
|
434
447
|
private readonly requestDumpStore?;
|
|
435
448
|
/** Phase 3 — team id stamped onto ToolContext when loop runs inside a team. */
|
|
436
449
|
private readonly teamId?;
|
|
450
|
+
/** Per-personality MCP tool policy from mcp.yaml (NOT on PersonalityConfig). */
|
|
451
|
+
private readonly mcpPolicy?;
|
|
437
452
|
/** Per-session accumulated spend in USD. Keyed by sessionKey. Reset via resetSessionCost(). */
|
|
438
453
|
private readonly sessionCosts;
|
|
439
454
|
/** FW-28 — per-session mtime registry. Keyed by sessionKey → (absPath → record). */
|
package/dist/index.js
CHANGED
|
@@ -182,6 +182,28 @@ function resolveDowngradedTools(spec) {
|
|
|
182
182
|
}
|
|
183
183
|
var DOWNGRADE_REJECTION_MESSAGE = "Tool blocked: an `outputIsUntrusted` tool just read external content. Dangerous tools are paused for the next turn or two. Send a new user message to clear, or re-run after acknowledging the prior content.";
|
|
184
184
|
|
|
185
|
+
// ../safety/injection/src/prompt-sanitize.ts
|
|
186
|
+
var INJECTION_PATTERNS = [
|
|
187
|
+
/ignore\s+(all\s+)?(previous|prior|above)\s+instructions/i,
|
|
188
|
+
/disregard\s+(your|all|any|the)\s+(previous|prior|above|system|original)\s+/i,
|
|
189
|
+
/you\s+are\s+now\s+(a|an|the)\s+/i,
|
|
190
|
+
/forget\s+(everything|all)\s+(you|above|prior)/i,
|
|
191
|
+
/override\s+(your|all|the)\s+(instructions|rules|constraints|guidelines)/i,
|
|
192
|
+
/new\s+(system\s+)?prompt\s*:/i,
|
|
193
|
+
/\[SYSTEM\]/i,
|
|
194
|
+
/<\s*system\s*>/i
|
|
195
|
+
];
|
|
196
|
+
function sanitize(content) {
|
|
197
|
+
const lines = content.split("\n");
|
|
198
|
+
const cleaned = lines.map((line) => {
|
|
199
|
+
if (INJECTION_PATTERNS.some((re) => re.test(line))) {
|
|
200
|
+
return "[line removed by injection guard]";
|
|
201
|
+
}
|
|
202
|
+
return line;
|
|
203
|
+
});
|
|
204
|
+
return cleaned.join("\n");
|
|
205
|
+
}
|
|
206
|
+
|
|
185
207
|
// ../safety/injection/src/sanitize.ts
|
|
186
208
|
var PLACEHOLDER = "[STRIPPED-TEMPLATE-TOKEN]";
|
|
187
209
|
var TEMPLATE_TOKEN_PATTERNS = [
|
|
@@ -299,6 +321,7 @@ function defaultAlwaysDeny() {
|
|
|
299
321
|
import { readFileSync } from "fs";
|
|
300
322
|
var ENV_TO_REF = {
|
|
301
323
|
ANTHROPIC_API_KEY: "providers/anthropic/apiKey",
|
|
324
|
+
AZURE_API_KEY: "providers/azure/apiKey",
|
|
302
325
|
OPENAI_API_KEY: "providers/openai/apiKey",
|
|
303
326
|
OPENROUTER_API_KEY: "providers/openrouter/apiKey",
|
|
304
327
|
GEMINI_API_KEY: "providers/gemini/apiKey",
|
|
@@ -1560,6 +1583,7 @@ function validateRegistration(tool, personality) {
|
|
|
1560
1583
|
|
|
1561
1584
|
// src/tool-registry.ts
|
|
1562
1585
|
function needsBackends(caps) {
|
|
1586
|
+
if (!caps) return false;
|
|
1563
1587
|
return !!(caps.network || caps.secrets || caps.storage || caps.fs_reach || caps.process || caps.attachments);
|
|
1564
1588
|
}
|
|
1565
1589
|
function mcpServerName(toolName) {
|
|
@@ -1568,11 +1592,22 @@ function mcpServerName(toolName) {
|
|
|
1568
1592
|
}
|
|
1569
1593
|
function passesFilter(entry, filterOpts) {
|
|
1570
1594
|
if (!filterOpts) return true;
|
|
1571
|
-
const { allowedMcpServers, allowedPlugins } = filterOpts;
|
|
1595
|
+
const { allowedMcpServers, allowedPlugins, allowedMcpTools } = filterOpts;
|
|
1596
|
+
const toolName = entry.tool.name;
|
|
1572
1597
|
if (allowedMcpServers !== void 0) {
|
|
1573
|
-
const server = mcpServerName(
|
|
1598
|
+
const server = mcpServerName(toolName);
|
|
1574
1599
|
if (server !== void 0 && !allowedMcpServers.includes(server)) return false;
|
|
1575
1600
|
}
|
|
1601
|
+
if (allowedMcpTools !== void 0) {
|
|
1602
|
+
const server = mcpServerName(toolName);
|
|
1603
|
+
if (server !== void 0) {
|
|
1604
|
+
const allowed = allowedMcpTools[server];
|
|
1605
|
+
if (allowed !== void 0) {
|
|
1606
|
+
const bareName = toolName.split("__").slice(2).join("__");
|
|
1607
|
+
if (!allowed.includes(bareName)) return false;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1576
1611
|
if (allowedPlugins !== void 0 && entry.pluginId !== void 0) {
|
|
1577
1612
|
if (!allowedPlugins.includes(entry.pluginId)) return false;
|
|
1578
1613
|
}
|
|
@@ -1859,6 +1894,8 @@ var AgentLoop = class {
|
|
|
1859
1894
|
requestDumpStore;
|
|
1860
1895
|
/** Phase 3 — team id stamped onto ToolContext when loop runs inside a team. */
|
|
1861
1896
|
teamId;
|
|
1897
|
+
/** Per-personality MCP tool policy from mcp.yaml (NOT on PersonalityConfig). */
|
|
1898
|
+
mcpPolicy;
|
|
1862
1899
|
/** Per-session accumulated spend in USD. Keyed by sessionKey. Reset via resetSessionCost(). */
|
|
1863
1900
|
sessionCosts = /* @__PURE__ */ new Map();
|
|
1864
1901
|
/** FW-28 — per-session mtime registry. Keyed by sessionKey → (absPath → record). */
|
|
@@ -1890,6 +1927,7 @@ var AgentLoop = class {
|
|
|
1890
1927
|
if (config.watcher) this.watcher = config.watcher;
|
|
1891
1928
|
if (config.clarifyBridge) this.clarifyBridge = config.clarifyBridge;
|
|
1892
1929
|
if (config.requestDumpStore) this.requestDumpStore = config.requestDumpStore;
|
|
1930
|
+
if (config.mcpPolicy) this.mcpPolicy = config.mcpPolicy;
|
|
1893
1931
|
this.contextEngines = config.contextEngines ?? new DefaultContextEngineRegistry();
|
|
1894
1932
|
}
|
|
1895
1933
|
/**
|
|
@@ -1998,11 +2036,20 @@ var AgentLoop = class {
|
|
|
1998
2036
|
model: effectiveModel,
|
|
1999
2037
|
source: modelSource
|
|
2000
2038
|
};
|
|
2001
|
-
const allowedTools = personality.toolset ?? void 0;
|
|
2039
|
+
const allowedTools = opts.toolsetOverride ?? personality.toolset ?? void 0;
|
|
2002
2040
|
const allowedPlugins = personality.plugins ?? [];
|
|
2041
|
+
const mcpServers = this.mcpPolicy?.servers;
|
|
2042
|
+
const allowedMcpTools = mcpServers ? Object.fromEntries(
|
|
2043
|
+
Object.entries(mcpServers).filter(([, v]) => v.tools !== void 0 || v.enabled === false).map(([k, v]) => {
|
|
2044
|
+
if (v.enabled === false) return [k, []];
|
|
2045
|
+
const tools = v.tools;
|
|
2046
|
+
return [k, tools ?? []];
|
|
2047
|
+
})
|
|
2048
|
+
) : void 0;
|
|
2003
2049
|
const filterOpts = {
|
|
2004
2050
|
allowedMcpServers: personality.mcp_servers ?? [],
|
|
2005
|
-
allowedPlugins
|
|
2051
|
+
allowedPlugins,
|
|
2052
|
+
...allowedMcpTools && Object.keys(allowedMcpTools).length > 0 ? { allowedMcpTools } : {}
|
|
2006
2053
|
};
|
|
2007
2054
|
await this.hooks.fireVoid(
|
|
2008
2055
|
"session_start",
|
|
@@ -2027,7 +2074,7 @@ ${text}` : text;
|
|
|
2027
2074
|
const activeMemory = personality.memory?.provider ? await this.memoryProviders.get(personality.memory.provider)?.(
|
|
2028
2075
|
personality.memory.options
|
|
2029
2076
|
) ?? this.memory : this.memory;
|
|
2030
|
-
const memScopeId =
|
|
2077
|
+
const memScopeId = `personality:${personality.id}`;
|
|
2031
2078
|
const memCtx = {
|
|
2032
2079
|
scopeId: memScopeId,
|
|
2033
2080
|
sessionId,
|
|
@@ -2042,6 +2089,35 @@ ${text}` : text;
|
|
|
2042
2089
|
memSnapshot = { entries: hits.map((h) => ({ key: h.key, content: h.content })) };
|
|
2043
2090
|
}
|
|
2044
2091
|
}
|
|
2092
|
+
const userScopeId = opts.userId ? `user:${opts.userId}` : void 0;
|
|
2093
|
+
if (userScopeId) {
|
|
2094
|
+
const userCtx = {
|
|
2095
|
+
scopeId: userScopeId,
|
|
2096
|
+
sessionId,
|
|
2097
|
+
sessionKey,
|
|
2098
|
+
platform: this.platform,
|
|
2099
|
+
workingDir: this.workingDir
|
|
2100
|
+
};
|
|
2101
|
+
const userEntry = await activeMemory.read("USER.md", userCtx);
|
|
2102
|
+
if (userEntry?.content.trim()) {
|
|
2103
|
+
const userSnapshot = {
|
|
2104
|
+
entries: [{ key: "USER.md", content: userEntry.content }]
|
|
2105
|
+
};
|
|
2106
|
+
if (memSnapshot) {
|
|
2107
|
+
memSnapshot = { entries: [...userSnapshot.entries, ...memSnapshot.entries] };
|
|
2108
|
+
} else {
|
|
2109
|
+
memSnapshot = userSnapshot;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
if (memSnapshot) {
|
|
2114
|
+
memSnapshot = {
|
|
2115
|
+
entries: memSnapshot.entries.map((e) => ({
|
|
2116
|
+
key: e.key,
|
|
2117
|
+
content: sanitize(e.content)
|
|
2118
|
+
}))
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2045
2121
|
const promptCtx = {
|
|
2046
2122
|
sessionId,
|
|
2047
2123
|
sessionKey,
|
|
@@ -2447,8 +2523,8 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2447
2523
|
workingDir: this.workingDir,
|
|
2448
2524
|
agentId: opts.agentId,
|
|
2449
2525
|
personalityId: personality.id,
|
|
2450
|
-
memoryScope: personality.memoryScope,
|
|
2451
2526
|
memoryScopeId: memScopeId,
|
|
2527
|
+
...userScopeId ? { userScopeId } : {},
|
|
2452
2528
|
...this.teamId !== void 0 && { teamId: this.teamId },
|
|
2453
2529
|
...opts.dryRun ? { dryRun: true } : {},
|
|
2454
2530
|
currentTurn: turnCount,
|
|
@@ -2527,6 +2603,54 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2527
2603
|
continue;
|
|
2528
2604
|
}
|
|
2529
2605
|
const effectiveArgs = beforeResult.args ?? tc.args;
|
|
2606
|
+
const enabledError = checkMcpEnabled(this.mcpPolicy, tc.toolName);
|
|
2607
|
+
if (enabledError) {
|
|
2608
|
+
this.observability?.recordSafetyBlock({
|
|
2609
|
+
traceId,
|
|
2610
|
+
code: "tool_blocked",
|
|
2611
|
+
cause: enabledError
|
|
2612
|
+
});
|
|
2613
|
+
observe({ type: "tool_end", toolName: tc.toolName, ok: false });
|
|
2614
|
+
yield {
|
|
2615
|
+
type: "tool_end",
|
|
2616
|
+
toolCallId: tc.toolCallId,
|
|
2617
|
+
toolName: tc.toolName,
|
|
2618
|
+
ok: false,
|
|
2619
|
+
durationMs: 0,
|
|
2620
|
+
result: enabledError
|
|
2621
|
+
};
|
|
2622
|
+
prepped.push({
|
|
2623
|
+
toolCallId: tc.toolCallId,
|
|
2624
|
+
name: tc.toolName,
|
|
2625
|
+
args: effectiveArgs,
|
|
2626
|
+
rejected: enabledError
|
|
2627
|
+
});
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
const rejectError = checkMcpRejectArgs(this.mcpPolicy, tc.toolName, effectiveArgs);
|
|
2631
|
+
if (rejectError) {
|
|
2632
|
+
this.observability?.recordSafetyBlock({
|
|
2633
|
+
traceId,
|
|
2634
|
+
code: "tool_blocked",
|
|
2635
|
+
cause: rejectError
|
|
2636
|
+
});
|
|
2637
|
+
observe({ type: "tool_end", toolName: tc.toolName, ok: false });
|
|
2638
|
+
yield {
|
|
2639
|
+
type: "tool_end",
|
|
2640
|
+
toolCallId: tc.toolCallId,
|
|
2641
|
+
toolName: tc.toolName,
|
|
2642
|
+
ok: false,
|
|
2643
|
+
durationMs: 0,
|
|
2644
|
+
result: rejectError
|
|
2645
|
+
};
|
|
2646
|
+
prepped.push({
|
|
2647
|
+
toolCallId: tc.toolCallId,
|
|
2648
|
+
name: tc.toolName,
|
|
2649
|
+
args: effectiveArgs,
|
|
2650
|
+
rejected: rejectError
|
|
2651
|
+
});
|
|
2652
|
+
continue;
|
|
2653
|
+
}
|
|
2530
2654
|
const spanId = this.observability?.startSpan({
|
|
2531
2655
|
traceId: traceId ?? "",
|
|
2532
2656
|
kind: "tool_call",
|
|
@@ -3036,6 +3160,37 @@ function extractFilePath(args) {
|
|
|
3036
3160
|
if (typeof a.cwd === "string" && a.cwd.length > 0) return a.cwd;
|
|
3037
3161
|
return void 0;
|
|
3038
3162
|
}
|
|
3163
|
+
function checkMcpRejectArgs(mcpPolicy, toolName, args) {
|
|
3164
|
+
const servers = mcpPolicy?.servers;
|
|
3165
|
+
if (!servers || !toolName.startsWith("mcp__")) return void 0;
|
|
3166
|
+
const firstSep = toolName.indexOf("__");
|
|
3167
|
+
const secondSep = toolName.indexOf("__", firstSep + 2);
|
|
3168
|
+
if (secondSep === -1) return void 0;
|
|
3169
|
+
const server = toolName.slice(firstSep + 2, secondSep);
|
|
3170
|
+
const bareTool = toolName.slice(secondSep + 2);
|
|
3171
|
+
const argRules = servers[server]?.reject_args?.[bareTool];
|
|
3172
|
+
if (!argRules) return void 0;
|
|
3173
|
+
const typedArgs = args;
|
|
3174
|
+
for (const [argName, forbiddenValues] of Object.entries(argRules)) {
|
|
3175
|
+
const value = typedArgs[argName];
|
|
3176
|
+
if (typeof value === "string" && forbiddenValues.includes(value)) {
|
|
3177
|
+
return `MCP policy: argument '${argName}' value '${value}' is rejected for tool '${bareTool}' on server '${server}'`;
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
return void 0;
|
|
3181
|
+
}
|
|
3182
|
+
function checkMcpEnabled(mcpPolicy, toolName) {
|
|
3183
|
+
const servers = mcpPolicy?.servers;
|
|
3184
|
+
if (!servers || !toolName.startsWith("mcp__")) return void 0;
|
|
3185
|
+
const firstSep = toolName.indexOf("__");
|
|
3186
|
+
const secondSep = toolName.indexOf("__", firstSep + 2);
|
|
3187
|
+
if (secondSep === -1) return void 0;
|
|
3188
|
+
const server = toolName.slice(firstSep + 2, secondSep);
|
|
3189
|
+
if (servers[server]?.enabled === false) {
|
|
3190
|
+
return `MCP policy: server '${server}' is disabled for this personality`;
|
|
3191
|
+
}
|
|
3192
|
+
return void 0;
|
|
3193
|
+
}
|
|
3039
3194
|
function describeSource(toolName, args) {
|
|
3040
3195
|
if (!args || typeof args !== "object") return void 0;
|
|
3041
3196
|
const a = args;
|