@ethosagent/core 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -0
- package/dist/index.js +122 -6
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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 = [
|
|
@@ -1568,11 +1590,22 @@ function mcpServerName(toolName) {
|
|
|
1568
1590
|
}
|
|
1569
1591
|
function passesFilter(entry, filterOpts) {
|
|
1570
1592
|
if (!filterOpts) return true;
|
|
1571
|
-
const { allowedMcpServers, allowedPlugins } = filterOpts;
|
|
1593
|
+
const { allowedMcpServers, allowedPlugins, allowedMcpTools } = filterOpts;
|
|
1594
|
+
const toolName = entry.tool.name;
|
|
1572
1595
|
if (allowedMcpServers !== void 0) {
|
|
1573
|
-
const server = mcpServerName(
|
|
1596
|
+
const server = mcpServerName(toolName);
|
|
1574
1597
|
if (server !== void 0 && !allowedMcpServers.includes(server)) return false;
|
|
1575
1598
|
}
|
|
1599
|
+
if (allowedMcpTools !== void 0) {
|
|
1600
|
+
const server = mcpServerName(toolName);
|
|
1601
|
+
if (server !== void 0) {
|
|
1602
|
+
const allowed = allowedMcpTools[server];
|
|
1603
|
+
if (allowed !== void 0) {
|
|
1604
|
+
const bareName = toolName.split("__").slice(2).join("__");
|
|
1605
|
+
if (!allowed.includes(bareName)) return false;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1576
1609
|
if (allowedPlugins !== void 0 && entry.pluginId !== void 0) {
|
|
1577
1610
|
if (!allowedPlugins.includes(entry.pluginId)) return false;
|
|
1578
1611
|
}
|
|
@@ -1859,6 +1892,8 @@ var AgentLoop = class {
|
|
|
1859
1892
|
requestDumpStore;
|
|
1860
1893
|
/** Phase 3 — team id stamped onto ToolContext when loop runs inside a team. */
|
|
1861
1894
|
teamId;
|
|
1895
|
+
/** Per-personality MCP tool policy from mcp.yaml (NOT on PersonalityConfig). */
|
|
1896
|
+
mcpPolicy;
|
|
1862
1897
|
/** Per-session accumulated spend in USD. Keyed by sessionKey. Reset via resetSessionCost(). */
|
|
1863
1898
|
sessionCosts = /* @__PURE__ */ new Map();
|
|
1864
1899
|
/** FW-28 — per-session mtime registry. Keyed by sessionKey → (absPath → record). */
|
|
@@ -1890,6 +1925,7 @@ var AgentLoop = class {
|
|
|
1890
1925
|
if (config.watcher) this.watcher = config.watcher;
|
|
1891
1926
|
if (config.clarifyBridge) this.clarifyBridge = config.clarifyBridge;
|
|
1892
1927
|
if (config.requestDumpStore) this.requestDumpStore = config.requestDumpStore;
|
|
1928
|
+
if (config.mcpPolicy) this.mcpPolicy = config.mcpPolicy;
|
|
1893
1929
|
this.contextEngines = config.contextEngines ?? new DefaultContextEngineRegistry();
|
|
1894
1930
|
}
|
|
1895
1931
|
/**
|
|
@@ -1998,11 +2034,19 @@ var AgentLoop = class {
|
|
|
1998
2034
|
model: effectiveModel,
|
|
1999
2035
|
source: modelSource
|
|
2000
2036
|
};
|
|
2001
|
-
const allowedTools = personality.toolset ?? void 0;
|
|
2037
|
+
const allowedTools = opts.toolsetOverride ?? personality.toolset ?? void 0;
|
|
2002
2038
|
const allowedPlugins = personality.plugins ?? [];
|
|
2039
|
+
const mcpServers = this.mcpPolicy?.servers;
|
|
2040
|
+
const allowedMcpTools = mcpServers ? Object.fromEntries(
|
|
2041
|
+
Object.entries(mcpServers).filter(([, v]) => v.tools !== void 0).map(([k, v]) => {
|
|
2042
|
+
const tools = v.tools;
|
|
2043
|
+
return [k, tools ?? []];
|
|
2044
|
+
})
|
|
2045
|
+
) : void 0;
|
|
2003
2046
|
const filterOpts = {
|
|
2004
2047
|
allowedMcpServers: personality.mcp_servers ?? [],
|
|
2005
|
-
allowedPlugins
|
|
2048
|
+
allowedPlugins,
|
|
2049
|
+
...allowedMcpTools && Object.keys(allowedMcpTools).length > 0 ? { allowedMcpTools } : {}
|
|
2006
2050
|
};
|
|
2007
2051
|
await this.hooks.fireVoid(
|
|
2008
2052
|
"session_start",
|
|
@@ -2027,7 +2071,7 @@ ${text}` : text;
|
|
|
2027
2071
|
const activeMemory = personality.memory?.provider ? await this.memoryProviders.get(personality.memory.provider)?.(
|
|
2028
2072
|
personality.memory.options
|
|
2029
2073
|
) ?? this.memory : this.memory;
|
|
2030
|
-
const memScopeId =
|
|
2074
|
+
const memScopeId = `personality:${personality.id}`;
|
|
2031
2075
|
const memCtx = {
|
|
2032
2076
|
scopeId: memScopeId,
|
|
2033
2077
|
sessionId,
|
|
@@ -2042,6 +2086,35 @@ ${text}` : text;
|
|
|
2042
2086
|
memSnapshot = { entries: hits.map((h) => ({ key: h.key, content: h.content })) };
|
|
2043
2087
|
}
|
|
2044
2088
|
}
|
|
2089
|
+
const userScopeId = opts.userId ? `user:${opts.userId}` : void 0;
|
|
2090
|
+
if (userScopeId) {
|
|
2091
|
+
const userCtx = {
|
|
2092
|
+
scopeId: userScopeId,
|
|
2093
|
+
sessionId,
|
|
2094
|
+
sessionKey,
|
|
2095
|
+
platform: this.platform,
|
|
2096
|
+
workingDir: this.workingDir
|
|
2097
|
+
};
|
|
2098
|
+
const userEntry = await activeMemory.read("USER.md", userCtx);
|
|
2099
|
+
if (userEntry?.content.trim()) {
|
|
2100
|
+
const userSnapshot = {
|
|
2101
|
+
entries: [{ key: "USER.md", content: userEntry.content }]
|
|
2102
|
+
};
|
|
2103
|
+
if (memSnapshot) {
|
|
2104
|
+
memSnapshot = { entries: [...userSnapshot.entries, ...memSnapshot.entries] };
|
|
2105
|
+
} else {
|
|
2106
|
+
memSnapshot = userSnapshot;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
if (memSnapshot) {
|
|
2111
|
+
memSnapshot = {
|
|
2112
|
+
entries: memSnapshot.entries.map((e) => ({
|
|
2113
|
+
key: e.key,
|
|
2114
|
+
content: sanitize(e.content)
|
|
2115
|
+
}))
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2045
2118
|
const promptCtx = {
|
|
2046
2119
|
sessionId,
|
|
2047
2120
|
sessionKey,
|
|
@@ -2447,8 +2520,8 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2447
2520
|
workingDir: this.workingDir,
|
|
2448
2521
|
agentId: opts.agentId,
|
|
2449
2522
|
personalityId: personality.id,
|
|
2450
|
-
memoryScope: personality.memoryScope,
|
|
2451
2523
|
memoryScopeId: memScopeId,
|
|
2524
|
+
...userScopeId ? { userScopeId } : {},
|
|
2452
2525
|
...this.teamId !== void 0 && { teamId: this.teamId },
|
|
2453
2526
|
...opts.dryRun ? { dryRun: true } : {},
|
|
2454
2527
|
currentTurn: turnCount,
|
|
@@ -2527,6 +2600,30 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2527
2600
|
continue;
|
|
2528
2601
|
}
|
|
2529
2602
|
const effectiveArgs = beforeResult.args ?? tc.args;
|
|
2603
|
+
const rejectError = checkMcpRejectArgs(this.mcpPolicy, tc.toolName, effectiveArgs);
|
|
2604
|
+
if (rejectError) {
|
|
2605
|
+
this.observability?.recordSafetyBlock({
|
|
2606
|
+
traceId,
|
|
2607
|
+
code: "tool_blocked",
|
|
2608
|
+
cause: rejectError
|
|
2609
|
+
});
|
|
2610
|
+
observe({ type: "tool_end", toolName: tc.toolName, ok: false });
|
|
2611
|
+
yield {
|
|
2612
|
+
type: "tool_end",
|
|
2613
|
+
toolCallId: tc.toolCallId,
|
|
2614
|
+
toolName: tc.toolName,
|
|
2615
|
+
ok: false,
|
|
2616
|
+
durationMs: 0,
|
|
2617
|
+
result: rejectError
|
|
2618
|
+
};
|
|
2619
|
+
prepped.push({
|
|
2620
|
+
toolCallId: tc.toolCallId,
|
|
2621
|
+
name: tc.toolName,
|
|
2622
|
+
args: effectiveArgs,
|
|
2623
|
+
rejected: rejectError
|
|
2624
|
+
});
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2530
2627
|
const spanId = this.observability?.startSpan({
|
|
2531
2628
|
traceId: traceId ?? "",
|
|
2532
2629
|
kind: "tool_call",
|
|
@@ -3036,6 +3133,25 @@ function extractFilePath(args) {
|
|
|
3036
3133
|
if (typeof a.cwd === "string" && a.cwd.length > 0) return a.cwd;
|
|
3037
3134
|
return void 0;
|
|
3038
3135
|
}
|
|
3136
|
+
function checkMcpRejectArgs(mcpPolicy, toolName, args) {
|
|
3137
|
+
const servers = mcpPolicy?.servers;
|
|
3138
|
+
if (!servers || !toolName.startsWith("mcp__")) return void 0;
|
|
3139
|
+
const firstSep = toolName.indexOf("__");
|
|
3140
|
+
const secondSep = toolName.indexOf("__", firstSep + 2);
|
|
3141
|
+
if (secondSep === -1) return void 0;
|
|
3142
|
+
const server = toolName.slice(firstSep + 2, secondSep);
|
|
3143
|
+
const bareTool = toolName.slice(secondSep + 2);
|
|
3144
|
+
const argRules = servers[server]?.reject_args?.[bareTool];
|
|
3145
|
+
if (!argRules) return void 0;
|
|
3146
|
+
const typedArgs = args;
|
|
3147
|
+
for (const [argName, forbiddenValues] of Object.entries(argRules)) {
|
|
3148
|
+
const value = typedArgs[argName];
|
|
3149
|
+
if (typeof value === "string" && forbiddenValues.includes(value)) {
|
|
3150
|
+
return `MCP policy: argument '${argName}' value '${value}' is rejected for tool '${bareTool}' on server '${server}'`;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
return void 0;
|
|
3154
|
+
}
|
|
3039
3155
|
function describeSource(toolName, args) {
|
|
3040
3156
|
if (!args || typeof args !== "object") return void 0;
|
|
3041
3157
|
const a = args;
|