@ethosagent/core 0.4.1 → 0.4.3
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 +122 -5
- package/dist/index.js +436 -78
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -321,6 +321,7 @@ function defaultAlwaysDeny() {
|
|
|
321
321
|
import { readFileSync } from "fs";
|
|
322
322
|
var ENV_TO_REF = {
|
|
323
323
|
ANTHROPIC_API_KEY: "providers/anthropic/apiKey",
|
|
324
|
+
AZURE_API_KEY: "providers/azure/apiKey",
|
|
324
325
|
OPENAI_API_KEY: "providers/openai/apiKey",
|
|
325
326
|
OPENROUTER_API_KEY: "providers/openrouter/apiKey",
|
|
326
327
|
GEMINI_API_KEY: "providers/gemini/apiKey",
|
|
@@ -354,6 +355,7 @@ import { createHash } from "crypto";
|
|
|
354
355
|
import { join, resolve, sep } from "path";
|
|
355
356
|
|
|
356
357
|
// ../storage-fs/src/fs-storage.ts
|
|
358
|
+
import { existsSync as fsExistsSync } from "fs";
|
|
357
359
|
import {
|
|
358
360
|
appendFile,
|
|
359
361
|
chmod,
|
|
@@ -663,6 +665,26 @@ var DefaultContextEngineRegistry = class {
|
|
|
663
665
|
}
|
|
664
666
|
};
|
|
665
667
|
|
|
668
|
+
// src/context-store.ts
|
|
669
|
+
var ContextStore = class {
|
|
670
|
+
store = /* @__PURE__ */ new Map();
|
|
671
|
+
get(key) {
|
|
672
|
+
return this.store.get(key);
|
|
673
|
+
}
|
|
674
|
+
set(key, value) {
|
|
675
|
+
this.store.set(key, value);
|
|
676
|
+
}
|
|
677
|
+
clear() {
|
|
678
|
+
this.store.clear();
|
|
679
|
+
}
|
|
680
|
+
asContextMethods() {
|
|
681
|
+
return {
|
|
682
|
+
getContext: (key) => this.get(key),
|
|
683
|
+
setContext: (key, value) => this.set(key, value)
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
666
688
|
// src/defaults/in-memory-session.ts
|
|
667
689
|
var InMemorySessionStore = class {
|
|
668
690
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -971,6 +993,101 @@ var DefaultHookRegistry = class {
|
|
|
971
993
|
}
|
|
972
994
|
};
|
|
973
995
|
|
|
996
|
+
// src/simple-completion.ts
|
|
997
|
+
var SimpleCompletionImpl = class {
|
|
998
|
+
constructor(provider, defaultModel, onUsage) {
|
|
999
|
+
this.provider = provider;
|
|
1000
|
+
this.defaultModel = defaultModel;
|
|
1001
|
+
this.onUsage = onUsage;
|
|
1002
|
+
}
|
|
1003
|
+
provider;
|
|
1004
|
+
defaultModel;
|
|
1005
|
+
onUsage;
|
|
1006
|
+
async complete(prompt, options) {
|
|
1007
|
+
const model = options?.model ?? this.defaultModel;
|
|
1008
|
+
let text = "";
|
|
1009
|
+
let inputTokens = 0;
|
|
1010
|
+
let outputTokens = 0;
|
|
1011
|
+
const stream = this.provider.complete([{ role: "user", content: prompt }], [], {
|
|
1012
|
+
system: options?.systemPrompt,
|
|
1013
|
+
maxTokens: options?.maxTokens ?? 1024,
|
|
1014
|
+
modelOverride: model !== this.provider.model ? model : void 0
|
|
1015
|
+
});
|
|
1016
|
+
for await (const chunk of stream) {
|
|
1017
|
+
if (chunk.type === "text_delta") text += chunk.text;
|
|
1018
|
+
if (chunk.type === "usage") {
|
|
1019
|
+
inputTokens += chunk.usage.inputTokens;
|
|
1020
|
+
outputTokens += chunk.usage.outputTokens;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
this.onUsage({ input: inputTokens, output: outputTokens });
|
|
1024
|
+
return text;
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// src/capability-validator.ts
|
|
1029
|
+
function hostMatchesPattern(host, pattern) {
|
|
1030
|
+
if (pattern === host) return true;
|
|
1031
|
+
if (pattern === "*") return true;
|
|
1032
|
+
if (pattern.startsWith("*.")) {
|
|
1033
|
+
const suffix = pattern.slice(1);
|
|
1034
|
+
return host.endsWith(suffix) && host.length > suffix.length;
|
|
1035
|
+
}
|
|
1036
|
+
return false;
|
|
1037
|
+
}
|
|
1038
|
+
function validateRegistration(tool, personality) {
|
|
1039
|
+
const caps = tool.capabilities;
|
|
1040
|
+
if (!caps) return [];
|
|
1041
|
+
const errors = [];
|
|
1042
|
+
if (caps.network) {
|
|
1043
|
+
const allowed = personality.safety?.network?.allow;
|
|
1044
|
+
if (allowed && allowed.length > 0) {
|
|
1045
|
+
for (const host of caps.network.allowedHosts) {
|
|
1046
|
+
if (host === "*") continue;
|
|
1047
|
+
const covered = allowed.some((pattern) => hostMatchesPattern(host, pattern));
|
|
1048
|
+
if (!covered) {
|
|
1049
|
+
errors.push({
|
|
1050
|
+
tool: tool.name,
|
|
1051
|
+
capability: "network",
|
|
1052
|
+
message: `host "${host}" is not in personality network allow list`
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (caps.fs_reach) {
|
|
1059
|
+
const personalityRead = personality.fs_reach?.read ?? [];
|
|
1060
|
+
const personalityWrite = personality.fs_reach?.write ?? [];
|
|
1061
|
+
if (caps.fs_reach.read && caps.fs_reach.read !== "from-personality") {
|
|
1062
|
+
for (const toolPath of caps.fs_reach.read) {
|
|
1063
|
+
const covered = personalityRead.some((p) => toolPath === p || toolPath.startsWith(`${p}/`));
|
|
1064
|
+
if (!covered) {
|
|
1065
|
+
errors.push({
|
|
1066
|
+
tool: tool.name,
|
|
1067
|
+
capability: "fs_reach.read",
|
|
1068
|
+
message: `path "${toolPath}" is not covered by personality fs_reach.read`
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (caps.fs_reach.write && caps.fs_reach.write !== "from-personality") {
|
|
1074
|
+
for (const toolPath of caps.fs_reach.write) {
|
|
1075
|
+
const covered = personalityWrite.some(
|
|
1076
|
+
(p) => toolPath === p || toolPath.startsWith(`${p}/`)
|
|
1077
|
+
);
|
|
1078
|
+
if (!covered) {
|
|
1079
|
+
errors.push({
|
|
1080
|
+
tool: tool.name,
|
|
1081
|
+
capability: "fs_reach.write",
|
|
1082
|
+
message: `path "${toolPath}" is not covered by personality fs_reach.write`
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return errors;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
974
1091
|
// src/scoped/scoped-attachments.ts
|
|
975
1092
|
var ScopedAttachmentsImpl = class {
|
|
976
1093
|
attachments;
|
|
@@ -1517,71 +1634,59 @@ function resolveCapabilities(toolName, capabilities, scopeIds, backends) {
|
|
|
1517
1634
|
return result;
|
|
1518
1635
|
}
|
|
1519
1636
|
|
|
1520
|
-
// src/
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
return host.endsWith(suffix) && host.length > suffix.length;
|
|
1527
|
-
}
|
|
1528
|
-
return false;
|
|
1529
|
-
}
|
|
1530
|
-
function validateRegistration(tool, personality) {
|
|
1531
|
-
const caps = tool.capabilities;
|
|
1532
|
-
if (!caps) return [];
|
|
1533
|
-
const errors = [];
|
|
1534
|
-
if (caps.network) {
|
|
1535
|
-
const allowed = personality.safety?.network?.allow;
|
|
1536
|
-
if (allowed && allowed.length > 0) {
|
|
1537
|
-
for (const host of caps.network.allowedHosts) {
|
|
1538
|
-
if (host === "*") continue;
|
|
1539
|
-
const covered = allowed.some((pattern) => hostMatchesPattern(host, pattern));
|
|
1540
|
-
if (!covered) {
|
|
1541
|
-
errors.push({
|
|
1542
|
-
tool: tool.name,
|
|
1543
|
-
capability: "network",
|
|
1544
|
-
message: `host "${host}" is not in personality network allow list`
|
|
1545
|
-
});
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1637
|
+
// src/local-tool-transport.ts
|
|
1638
|
+
var LocalToolTransport = class {
|
|
1639
|
+
constructor(lookup, backends, getLiveCtx) {
|
|
1640
|
+
this.lookup = lookup;
|
|
1641
|
+
this.backends = backends;
|
|
1642
|
+
this.getLiveCtx = getLiveCtx;
|
|
1549
1643
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
errors.push({
|
|
1558
|
-
tool: tool.name,
|
|
1559
|
-
capability: "fs_reach.read",
|
|
1560
|
-
message: `path "${toolPath}" is not covered by personality fs_reach.read`
|
|
1561
|
-
});
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1644
|
+
lookup;
|
|
1645
|
+
backends;
|
|
1646
|
+
getLiveCtx;
|
|
1647
|
+
async execute(request, signal) {
|
|
1648
|
+
const tool = this.lookup(request.name);
|
|
1649
|
+
if (!tool) {
|
|
1650
|
+
return { ok: false, error: `Tool '${request.name}' not found`, code: "not_available" };
|
|
1564
1651
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1652
|
+
const live = this.getLiveCtx?.();
|
|
1653
|
+
const ctx = {
|
|
1654
|
+
sessionId: request.sessionId,
|
|
1655
|
+
sessionKey: request.sessionKey,
|
|
1656
|
+
platform: request.platform,
|
|
1657
|
+
workingDir: request.workingDir,
|
|
1658
|
+
personalityId: request.personalityId,
|
|
1659
|
+
teamId: request.teamId,
|
|
1660
|
+
agentId: request.agentId,
|
|
1661
|
+
memoryScopeId: request.memoryScopeId,
|
|
1662
|
+
userScopeId: request.userScopeId,
|
|
1663
|
+
currentTurn: request.currentTurn,
|
|
1664
|
+
messageCount: request.messageCount,
|
|
1665
|
+
resultBudgetChars: request.resultBudgetChars,
|
|
1666
|
+
networkPolicy: request.networkPolicy,
|
|
1667
|
+
dryRun: request.dryRun,
|
|
1668
|
+
abortSignal: signal,
|
|
1669
|
+
emit: live?.emit ?? (() => {
|
|
1670
|
+
}),
|
|
1671
|
+
readMtimes: live?.readMtimes,
|
|
1672
|
+
storage: live?.storage
|
|
1673
|
+
};
|
|
1674
|
+
if (tool.capabilities && this.backends) {
|
|
1675
|
+
const resolved = resolveCapabilities(
|
|
1676
|
+
tool.name,
|
|
1677
|
+
tool.capabilities,
|
|
1678
|
+
{ sessionId: request.sessionId, personalityId: request.personalityId },
|
|
1679
|
+
{ ...this.backends, inboundAttachments: live?.inboundAttachments }
|
|
1680
|
+
);
|
|
1681
|
+
Object.assign(ctx, resolved);
|
|
1578
1682
|
}
|
|
1683
|
+
return tool.execute(request.args, ctx);
|
|
1579
1684
|
}
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1685
|
+
};
|
|
1582
1686
|
|
|
1583
1687
|
// src/tool-registry.ts
|
|
1584
1688
|
function needsBackends(caps) {
|
|
1689
|
+
if (!caps) return false;
|
|
1585
1690
|
return !!(caps.network || caps.secrets || caps.storage || caps.fs_reach || caps.process || caps.attachments);
|
|
1586
1691
|
}
|
|
1587
1692
|
function mcpServerName(toolName) {
|
|
@@ -1618,13 +1723,53 @@ function safeReduce(r, result, ctx) {
|
|
|
1618
1723
|
return result;
|
|
1619
1724
|
}
|
|
1620
1725
|
}
|
|
1726
|
+
var DEFAULT_CACHE_TTL_MS = 3e5;
|
|
1727
|
+
var MAX_CACHE_ENTRIES = 1e3;
|
|
1621
1728
|
var DefaultToolRegistry = class {
|
|
1622
1729
|
tools = /* @__PURE__ */ new Map();
|
|
1730
|
+
resultCache = /* @__PURE__ */ new Map();
|
|
1623
1731
|
backends;
|
|
1624
1732
|
reducers;
|
|
1625
|
-
|
|
1733
|
+
transport;
|
|
1734
|
+
// Per-turn live context — updated by executeParallel before dispatching.
|
|
1735
|
+
turnLiveCtx = { emit: () => {
|
|
1736
|
+
} };
|
|
1737
|
+
constructor(backends, reducers, transport) {
|
|
1626
1738
|
this.backends = backends;
|
|
1627
1739
|
this.reducers = reducers;
|
|
1740
|
+
this.transport = transport ?? new LocalToolTransport(
|
|
1741
|
+
(name) => this.tools.get(name)?.tool,
|
|
1742
|
+
backends,
|
|
1743
|
+
() => this.turnLiveCtx
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
cacheGet(tool, args, ctx) {
|
|
1747
|
+
if (!tool.cache) return null;
|
|
1748
|
+
const opts = tool.cache === true ? {} : tool.cache;
|
|
1749
|
+
const keyPart = opts.keyFn ? opts.keyFn(args) : JSON.stringify(args);
|
|
1750
|
+
const key = `${tool.name}:${ctx.sessionId}:${ctx.personalityId ?? ""}:${keyPart}`;
|
|
1751
|
+
const entry = this.resultCache.get(key);
|
|
1752
|
+
if (!entry) return null;
|
|
1753
|
+
if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
|
|
1754
|
+
this.resultCache.delete(key);
|
|
1755
|
+
return null;
|
|
1756
|
+
}
|
|
1757
|
+
return entry.result;
|
|
1758
|
+
}
|
|
1759
|
+
cacheSet(tool, args, result, ctx) {
|
|
1760
|
+
if (!tool.cache) return;
|
|
1761
|
+
const opts = tool.cache === true ? {} : tool.cache;
|
|
1762
|
+
const keyPart = opts.keyFn ? opts.keyFn(args) : JSON.stringify(args);
|
|
1763
|
+
const key = `${tool.name}:${ctx.sessionId}:${ctx.personalityId ?? ""}:${keyPart}`;
|
|
1764
|
+
const ttl = opts.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1765
|
+
const expiresAt = Date.now() + ttl;
|
|
1766
|
+
this.resultCache.set(key, { result, expiresAt });
|
|
1767
|
+
if (this.resultCache.size > MAX_CACHE_ENTRIES) {
|
|
1768
|
+
const oldest = this.resultCache.keys().next().value;
|
|
1769
|
+
if (oldest !== void 0) {
|
|
1770
|
+
this.resultCache.delete(oldest);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1628
1773
|
}
|
|
1629
1774
|
register(tool, opts) {
|
|
1630
1775
|
this.tools.set(tool.name, { tool, pluginId: opts?.pluginId });
|
|
@@ -1663,6 +1808,10 @@ var DefaultToolRegistry = class {
|
|
|
1663
1808
|
getForToolset(toolset) {
|
|
1664
1809
|
return this.getAvailable().filter((t) => t.toolset === toolset);
|
|
1665
1810
|
}
|
|
1811
|
+
/** v2.2 — Return the plugin id that registered a tool, if any. */
|
|
1812
|
+
getPluginId(name) {
|
|
1813
|
+
return this.tools.get(name)?.pluginId;
|
|
1814
|
+
}
|
|
1666
1815
|
toDefinitions(allowedTools, filterOpts) {
|
|
1667
1816
|
const entries = [...this.tools.values()].filter(
|
|
1668
1817
|
(e) => !e.tool.isAvailable || e.tool.isAvailable()
|
|
@@ -1715,8 +1864,34 @@ var DefaultToolRegistry = class {
|
|
|
1715
1864
|
// Runs all tool calls in parallel. Results are returned in input order.
|
|
1716
1865
|
// Budget is split evenly across parallel calls; each result is post-trimmed to budget.
|
|
1717
1866
|
// allowedTools + filterOpts enforce tool access at execution time (belt-and-suspenders).
|
|
1718
|
-
async
|
|
1867
|
+
async applyFilters(filters, tool, args, ctx, meta, execute) {
|
|
1868
|
+
const matching = filters.filter(
|
|
1869
|
+
(f) => (!f.toolName || (Array.isArray(f.toolName) ? f.toolName.includes(meta.toolName) : f.toolName === meta.toolName)) && (!f.toolset || tool.toolset === f.toolset)
|
|
1870
|
+
);
|
|
1871
|
+
let shortCircuit = null;
|
|
1872
|
+
for (const f of matching) {
|
|
1873
|
+
if (f.before) {
|
|
1874
|
+
const r = await f.before(args, ctx, meta);
|
|
1875
|
+
if (r !== null) {
|
|
1876
|
+
shortCircuit = r;
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
let result = shortCircuit ?? await execute();
|
|
1882
|
+
for (const f of matching) {
|
|
1883
|
+
if (f.after) result = await f.after(result, ctx, meta);
|
|
1884
|
+
}
|
|
1885
|
+
return result;
|
|
1886
|
+
}
|
|
1887
|
+
async executeParallel(calls, ctx, allowedTools, filterOpts, turnAttachments, filters) {
|
|
1719
1888
|
const perCallBudget = Math.floor(ctx.resultBudgetChars / Math.max(calls.length, 1));
|
|
1889
|
+
this.turnLiveCtx = {
|
|
1890
|
+
emit: ctx.emit,
|
|
1891
|
+
readMtimes: ctx.readMtimes,
|
|
1892
|
+
storage: ctx.storage,
|
|
1893
|
+
inboundAttachments: turnAttachments
|
|
1894
|
+
};
|
|
1720
1895
|
const results = await Promise.allSettled(
|
|
1721
1896
|
calls.map(async (call) => {
|
|
1722
1897
|
const entry = this.tools.get(call.name);
|
|
@@ -1765,6 +1940,10 @@ var DefaultToolRegistry = class {
|
|
|
1765
1940
|
}
|
|
1766
1941
|
};
|
|
1767
1942
|
}
|
|
1943
|
+
const cached = this.cacheGet(entry.tool, call.args, ctx);
|
|
1944
|
+
if (cached) {
|
|
1945
|
+
return { toolCallId: call.toolCallId, name: call.name, result: cached };
|
|
1946
|
+
}
|
|
1768
1947
|
if (needsBackends(entry.tool.capabilities) && !this.backends) {
|
|
1769
1948
|
return {
|
|
1770
1949
|
toolCallId: call.toolCallId,
|
|
@@ -1784,32 +1963,49 @@ var DefaultToolRegistry = class {
|
|
|
1784
1963
|
result: synthesizeDryRunResult2(call.name, call.args)
|
|
1785
1964
|
};
|
|
1786
1965
|
}
|
|
1787
|
-
const
|
|
1788
|
-
const toolCtx = { ...ctx, resultBudgetChars: budget };
|
|
1966
|
+
const cappedBudget = Math.min(perCallBudget, entry.tool.maxResultChars ?? perCallBudget);
|
|
1789
1967
|
try {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1968
|
+
const request = {
|
|
1969
|
+
toolCallId: call.toolCallId,
|
|
1970
|
+
name: call.name,
|
|
1971
|
+
args: call.args,
|
|
1972
|
+
sessionId: ctx.sessionId,
|
|
1973
|
+
sessionKey: ctx.sessionKey,
|
|
1974
|
+
platform: ctx.platform,
|
|
1975
|
+
workingDir: ctx.workingDir,
|
|
1976
|
+
personalityId: ctx.personalityId,
|
|
1977
|
+
teamId: ctx.teamId,
|
|
1978
|
+
agentId: ctx.agentId,
|
|
1979
|
+
memoryScopeId: ctx.memoryScopeId,
|
|
1980
|
+
userScopeId: ctx.userScopeId,
|
|
1981
|
+
currentTurn: ctx.currentTurn,
|
|
1982
|
+
messageCount: ctx.messageCount,
|
|
1983
|
+
resultBudgetChars: cappedBudget,
|
|
1984
|
+
networkPolicy: ctx.networkPolicy,
|
|
1985
|
+
dryRun: ctx.dryRun
|
|
1986
|
+
};
|
|
1987
|
+
const rawResult = filters && filters.length > 0 ? await this.applyFilters(
|
|
1988
|
+
filters,
|
|
1989
|
+
entry.tool,
|
|
1990
|
+
call.args,
|
|
1991
|
+
ctx,
|
|
1992
|
+
{ toolName: call.name, toolCallId: call.toolCallId },
|
|
1993
|
+
() => this.transport.execute(request, ctx.abortSignal)
|
|
1994
|
+
) : await this.transport.execute(request, ctx.abortSignal);
|
|
1800
1995
|
const reducer = this.reducers?.get(call.name);
|
|
1801
1996
|
const result = reducer ? safeReduce(reducer, rawResult, { args: call.args, turnCount: ctx.currentTurn ?? 0 }) : rawResult;
|
|
1802
|
-
if (result.ok && result.value.length >
|
|
1997
|
+
if (result.ok && result.value.length > cappedBudget) {
|
|
1803
1998
|
return {
|
|
1804
1999
|
toolCallId: call.toolCallId,
|
|
1805
2000
|
name: call.name,
|
|
1806
2001
|
result: {
|
|
1807
2002
|
ok: true,
|
|
1808
|
-
value: `${result.value.slice(0,
|
|
2003
|
+
value: `${result.value.slice(0, cappedBudget)}
|
|
1809
2004
|
[truncated \u2014 ${result.value.length} chars total]`
|
|
1810
2005
|
}
|
|
1811
2006
|
};
|
|
1812
2007
|
}
|
|
2008
|
+
this.cacheSet(entry.tool, call.args, result, ctx);
|
|
1813
2009
|
return { toolCallId: call.toolCallId, name: call.name, result };
|
|
1814
2010
|
} catch (err) {
|
|
1815
2011
|
return {
|
|
@@ -1852,7 +2048,12 @@ var KNOWN_AGENT_EVENT_TYPES = [
|
|
|
1852
2048
|
"done",
|
|
1853
2049
|
"context_meta",
|
|
1854
2050
|
"run_start",
|
|
1855
|
-
"dry_run_summary"
|
|
2051
|
+
"dry_run_summary",
|
|
2052
|
+
"tool_approval_required",
|
|
2053
|
+
"tool_approval_response",
|
|
2054
|
+
"evaluators_complete",
|
|
2055
|
+
"credential_required",
|
|
2056
|
+
"notification_received"
|
|
1856
2057
|
];
|
|
1857
2058
|
function isKnownAgentEvent(event) {
|
|
1858
2059
|
return KNOWN_AGENT_EVENT_TYPES.includes(event.type);
|
|
@@ -1894,10 +2095,16 @@ var AgentLoop = class {
|
|
|
1894
2095
|
teamId;
|
|
1895
2096
|
/** Per-personality MCP tool policy from mcp.yaml (NOT on PersonalityConfig). */
|
|
1896
2097
|
mcpPolicy;
|
|
2098
|
+
/** v2.2 — Callback to emit per-tool invocation metrics to the diagnostic store. */
|
|
2099
|
+
onToolMetric;
|
|
2100
|
+
/** v2.2 — Pre-turn credential check callback. */
|
|
2101
|
+
credentialCheck;
|
|
1897
2102
|
/** Per-session accumulated spend in USD. Keyed by sessionKey. Reset via resetSessionCost(). */
|
|
1898
2103
|
sessionCosts = /* @__PURE__ */ new Map();
|
|
1899
2104
|
/** FW-28 — per-session mtime registry. Keyed by sessionKey → (absPath → record). */
|
|
1900
2105
|
sessionReadMtimes = /* @__PURE__ */ new Map();
|
|
2106
|
+
/** v2: per-run key/value store threaded into ToolContext for plugin communication. */
|
|
2107
|
+
contextStore = new ContextStore();
|
|
1901
2108
|
constructor(config) {
|
|
1902
2109
|
this.llm = config.llm;
|
|
1903
2110
|
this.tools = config.tools ?? new DefaultToolRegistry();
|
|
@@ -1926,6 +2133,8 @@ var AgentLoop = class {
|
|
|
1926
2133
|
if (config.clarifyBridge) this.clarifyBridge = config.clarifyBridge;
|
|
1927
2134
|
if (config.requestDumpStore) this.requestDumpStore = config.requestDumpStore;
|
|
1928
2135
|
if (config.mcpPolicy) this.mcpPolicy = config.mcpPolicy;
|
|
2136
|
+
if (config.onToolMetric) this.onToolMetric = config.onToolMetric;
|
|
2137
|
+
if (config.credentialCheck) this.credentialCheck = config.credentialCheck;
|
|
1929
2138
|
this.contextEngines = config.contextEngines ?? new DefaultContextEngineRegistry();
|
|
1930
2139
|
}
|
|
1931
2140
|
/**
|
|
@@ -2038,7 +2247,8 @@ var AgentLoop = class {
|
|
|
2038
2247
|
const allowedPlugins = personality.plugins ?? [];
|
|
2039
2248
|
const mcpServers = this.mcpPolicy?.servers;
|
|
2040
2249
|
const allowedMcpTools = mcpServers ? Object.fromEntries(
|
|
2041
|
-
Object.entries(mcpServers).filter(([, v]) => v.tools !== void 0).map(([k, v]) => {
|
|
2250
|
+
Object.entries(mcpServers).filter(([, v]) => v.tools !== void 0 || v.enabled === false).map(([k, v]) => {
|
|
2251
|
+
if (v.enabled === false) return [k, []];
|
|
2042
2252
|
const tools = v.tools;
|
|
2043
2253
|
return [k, tools ?? []];
|
|
2044
2254
|
})
|
|
@@ -2058,6 +2268,26 @@ var AgentLoop = class {
|
|
|
2058
2268
|
},
|
|
2059
2269
|
allowedPlugins
|
|
2060
2270
|
);
|
|
2271
|
+
if (this.credentialCheck) {
|
|
2272
|
+
const missing = await this.credentialCheck(sessionKey, text);
|
|
2273
|
+
if (missing) {
|
|
2274
|
+
if (traceId) this.observability?.endTrace(traceId, "error");
|
|
2275
|
+
this.observability?.flush();
|
|
2276
|
+
yield {
|
|
2277
|
+
type: "credential_required",
|
|
2278
|
+
pluginId: missing.pluginId,
|
|
2279
|
+
credentialKey: missing.credentialKey,
|
|
2280
|
+
kind: missing.kind,
|
|
2281
|
+
label: missing.label,
|
|
2282
|
+
description: missing.description,
|
|
2283
|
+
authUrl: missing.authUrl,
|
|
2284
|
+
sessionKey,
|
|
2285
|
+
pendingUserMessage: text
|
|
2286
|
+
};
|
|
2287
|
+
yield { type: "done", text: "", turnCount: 0 };
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2061
2291
|
const attachmentAnnotation = buildAttachmentAnnotation(opts.attachments ?? []);
|
|
2062
2292
|
const annotatedText = attachmentAnnotation ? `${attachmentAnnotation}
|
|
2063
2293
|
${text}` : text;
|
|
@@ -2151,6 +2381,8 @@ ${text}` : text;
|
|
|
2151
2381
|
if (promptCtx.meta && Object.keys(promptCtx.meta).length > 0) {
|
|
2152
2382
|
yield { type: "context_meta", data: promptCtx.meta };
|
|
2153
2383
|
}
|
|
2384
|
+
const rawSkills = promptCtx.meta?.skillFilesUsed;
|
|
2385
|
+
const activeSkillFiles = Array.isArray(rawSkills) && rawSkills.every((s) => typeof s === "string") ? rawSkills : void 0;
|
|
2154
2386
|
if (memSnapshot && memSnapshot.entries.length > 0) {
|
|
2155
2387
|
const blocks = [];
|
|
2156
2388
|
const orderHints = {
|
|
@@ -2513,6 +2745,7 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2513
2745
|
sessionMtimes = /* @__PURE__ */ new Map();
|
|
2514
2746
|
this.sessionReadMtimes.set(sessionKey, sessionMtimes);
|
|
2515
2747
|
}
|
|
2748
|
+
this.contextStore.clear();
|
|
2516
2749
|
const toolCtxBase = {
|
|
2517
2750
|
sessionId,
|
|
2518
2751
|
sessionKey,
|
|
@@ -2538,7 +2771,12 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2538
2771
|
resultBudgetChars: this.resultBudgetChars,
|
|
2539
2772
|
readMtimes: sessionMtimes,
|
|
2540
2773
|
...scopedStorage ? { storage: scopedStorage } : {},
|
|
2541
|
-
...personality.safety?.network ? { networkPolicy: personality.safety.network } : {}
|
|
2774
|
+
...personality.safety?.network ? { networkPolicy: personality.safety.network } : {},
|
|
2775
|
+
...this.contextStore.asContextMethods(),
|
|
2776
|
+
llm: new SimpleCompletionImpl(this.llm, effectiveModel, ({ input, output }) => {
|
|
2777
|
+
llmInputTokens += input;
|
|
2778
|
+
llmOutputTokens += output;
|
|
2779
|
+
})
|
|
2542
2780
|
};
|
|
2543
2781
|
const prepped = [];
|
|
2544
2782
|
const spanIds = /* @__PURE__ */ new Map();
|
|
@@ -2600,6 +2838,30 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2600
2838
|
continue;
|
|
2601
2839
|
}
|
|
2602
2840
|
const effectiveArgs = beforeResult.args ?? tc.args;
|
|
2841
|
+
const enabledError = checkMcpEnabled(this.mcpPolicy, tc.toolName);
|
|
2842
|
+
if (enabledError) {
|
|
2843
|
+
this.observability?.recordSafetyBlock({
|
|
2844
|
+
traceId,
|
|
2845
|
+
code: "tool_blocked",
|
|
2846
|
+
cause: enabledError
|
|
2847
|
+
});
|
|
2848
|
+
observe({ type: "tool_end", toolName: tc.toolName, ok: false });
|
|
2849
|
+
yield {
|
|
2850
|
+
type: "tool_end",
|
|
2851
|
+
toolCallId: tc.toolCallId,
|
|
2852
|
+
toolName: tc.toolName,
|
|
2853
|
+
ok: false,
|
|
2854
|
+
durationMs: 0,
|
|
2855
|
+
result: enabledError
|
|
2856
|
+
};
|
|
2857
|
+
prepped.push({
|
|
2858
|
+
toolCallId: tc.toolCallId,
|
|
2859
|
+
name: tc.toolName,
|
|
2860
|
+
args: effectiveArgs,
|
|
2861
|
+
rejected: enabledError
|
|
2862
|
+
});
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2603
2865
|
const rejectError = checkMcpRejectArgs(this.mcpPolicy, tc.toolName, effectiveArgs);
|
|
2604
2866
|
if (rejectError) {
|
|
2605
2867
|
this.observability?.recordSafetyBlock({
|
|
@@ -2632,6 +2894,15 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2632
2894
|
obsConfig
|
|
2633
2895
|
});
|
|
2634
2896
|
spanIds.set(tc.toolCallId, spanId ?? "");
|
|
2897
|
+
const reqApprovalTool = this.tools.get(tc.toolName);
|
|
2898
|
+
if (reqApprovalTool?.requiresApproval) {
|
|
2899
|
+
yield {
|
|
2900
|
+
type: "tool_approval_required",
|
|
2901
|
+
toolCallId: tc.toolCallId,
|
|
2902
|
+
toolName: tc.toolName,
|
|
2903
|
+
args: effectiveArgs
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2635
2906
|
observe({ type: "tool_start", toolName: tc.toolName, args: effectiveArgs });
|
|
2636
2907
|
yield {
|
|
2637
2908
|
type: "tool_start",
|
|
@@ -2659,6 +2930,42 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2659
2930
|
opts.attachments
|
|
2660
2931
|
) : [];
|
|
2661
2932
|
const execResultMap = new Map(execResults.map((r) => [r.toolCallId, r]));
|
|
2933
|
+
const directResult = execResults.find((r) => {
|
|
2934
|
+
const t = this.tools.get(r.name);
|
|
2935
|
+
return r.result.ok && t?.returnDirect;
|
|
2936
|
+
});
|
|
2937
|
+
if (directResult?.result.ok) {
|
|
2938
|
+
for (const p of prepped) {
|
|
2939
|
+
const execResult = execResultMap.get(p.toolCallId);
|
|
2940
|
+
const result = p.rejected ? { ok: false, error: p.rejected, code: "execution_failed" } : execResult?.result ?? {
|
|
2941
|
+
ok: false,
|
|
2942
|
+
error: "Tool result missing",
|
|
2943
|
+
code: "execution_failed"
|
|
2944
|
+
};
|
|
2945
|
+
await this.session.appendMessage({
|
|
2946
|
+
sessionId,
|
|
2947
|
+
role: "tool_result",
|
|
2948
|
+
content: result.ok ? result.value : result.error,
|
|
2949
|
+
toolCallId: p.toolCallId,
|
|
2950
|
+
toolName: p.name
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
for (const r of execResults) {
|
|
2954
|
+
yield {
|
|
2955
|
+
type: "tool_end",
|
|
2956
|
+
toolCallId: r.toolCallId,
|
|
2957
|
+
toolName: r.name,
|
|
2958
|
+
ok: r.result.ok,
|
|
2959
|
+
durationMs: Date.now() - startedAt,
|
|
2960
|
+
result: r.result.ok ? r.result.value : r.result.error
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
fullText = directResult.result.value;
|
|
2964
|
+
if (traceId) this.observability?.endTrace(traceId, "ok");
|
|
2965
|
+
this.observability?.flush();
|
|
2966
|
+
yield { type: "done", text: fullText, turnCount };
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2662
2969
|
if (opts.dryRun) {
|
|
2663
2970
|
for (const input of execInputs) {
|
|
2664
2971
|
dryRunPlan.push({
|
|
@@ -2741,6 +3048,19 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2741
3048
|
},
|
|
2742
3049
|
allowedPlugins
|
|
2743
3050
|
);
|
|
3051
|
+
if (this.onToolMetric) {
|
|
3052
|
+
const pluginId = this.tools.getPluginId?.(p.name);
|
|
3053
|
+
if (pluginId) {
|
|
3054
|
+
this.onToolMetric({
|
|
3055
|
+
pluginId,
|
|
3056
|
+
toolName: p.name,
|
|
3057
|
+
ok: result.ok,
|
|
3058
|
+
durationMs,
|
|
3059
|
+
sessionId,
|
|
3060
|
+
turnId: String(turnCount)
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
2744
3064
|
const touchedPath = extractFilePath(p.args);
|
|
2745
3065
|
if (touchedPath !== void 0) {
|
|
2746
3066
|
await this.hooks.fireVoid(
|
|
@@ -2826,7 +3146,8 @@ ${rendered.slice(-MEMORY_MAX_CHARS)}`;
|
|
|
2826
3146
|
successfulToolCalls,
|
|
2827
3147
|
totalToolCalls,
|
|
2828
3148
|
toolNames: [...toolNameCounts.keys()],
|
|
2829
|
-
initialPrompt: text
|
|
3149
|
+
initialPrompt: text,
|
|
3150
|
+
activeSkillFiles
|
|
2830
3151
|
},
|
|
2831
3152
|
allowedPlugins
|
|
2832
3153
|
);
|
|
@@ -3152,6 +3473,18 @@ function checkMcpRejectArgs(mcpPolicy, toolName, args) {
|
|
|
3152
3473
|
}
|
|
3153
3474
|
return void 0;
|
|
3154
3475
|
}
|
|
3476
|
+
function checkMcpEnabled(mcpPolicy, toolName) {
|
|
3477
|
+
const servers = mcpPolicy?.servers;
|
|
3478
|
+
if (!servers || !toolName.startsWith("mcp__")) return void 0;
|
|
3479
|
+
const firstSep = toolName.indexOf("__");
|
|
3480
|
+
const secondSep = toolName.indexOf("__", firstSep + 2);
|
|
3481
|
+
if (secondSep === -1) return void 0;
|
|
3482
|
+
const server = toolName.slice(firstSep + 2, secondSep);
|
|
3483
|
+
if (servers[server]?.enabled === false) {
|
|
3484
|
+
return `MCP policy: server '${server}' is disabled for this personality`;
|
|
3485
|
+
}
|
|
3486
|
+
return void 0;
|
|
3487
|
+
}
|
|
3155
3488
|
function describeSource(toolName, args) {
|
|
3156
3489
|
if (!args || typeof args !== "object") return void 0;
|
|
3157
3490
|
const a = args;
|
|
@@ -3619,6 +3952,27 @@ var LastWriteWinsPolicy = class {
|
|
|
3619
3952
|
}
|
|
3620
3953
|
};
|
|
3621
3954
|
|
|
3955
|
+
// src/notification-router.ts
|
|
3956
|
+
var DefaultNotificationRouter = class {
|
|
3957
|
+
adapters = /* @__PURE__ */ new Map();
|
|
3958
|
+
async route(pluginId, opts) {
|
|
3959
|
+
if (opts.sessionKey === "*") return;
|
|
3960
|
+
const adapter = this.adapters.get(opts.sessionKey);
|
|
3961
|
+
if (!adapter) return;
|
|
3962
|
+
if (opts.startTurn) {
|
|
3963
|
+
await adapter.injectUserMessage(opts.message);
|
|
3964
|
+
} else {
|
|
3965
|
+
await adapter.send(opts.message, opts.payload);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
register(sessionKey, adapter) {
|
|
3969
|
+
this.adapters.set(sessionKey, adapter);
|
|
3970
|
+
}
|
|
3971
|
+
deregister(sessionKey) {
|
|
3972
|
+
this.adapters.delete(sessionKey);
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3975
|
+
|
|
3622
3976
|
// src/path-boundary.ts
|
|
3623
3977
|
import { resolve as resolve5, sep as sep2 } from "path";
|
|
3624
3978
|
function assertWithinBase(base, target) {
|
|
@@ -4017,10 +4371,12 @@ export {
|
|
|
4017
4371
|
ClarifyBusyError,
|
|
4018
4372
|
ClarifyNoSurfaceError,
|
|
4019
4373
|
ClarifyTimedOutNoDefaultError,
|
|
4374
|
+
ContextStore,
|
|
4020
4375
|
DefaultContextEngineRegistry,
|
|
4021
4376
|
DefaultHookRegistry,
|
|
4022
4377
|
DefaultLLMProviderRegistry,
|
|
4023
4378
|
DefaultMemoryProviderRegistry,
|
|
4379
|
+
DefaultNotificationRouter,
|
|
4024
4380
|
DefaultPersonalityRegistry,
|
|
4025
4381
|
DefaultToolRegistry,
|
|
4026
4382
|
DefaultToolResultReducerRegistry,
|
|
@@ -4032,6 +4388,7 @@ export {
|
|
|
4032
4388
|
KNOWN_AGENT_EVENT_TYPES,
|
|
4033
4389
|
LastWriteWinsPolicy,
|
|
4034
4390
|
LazyOnDemandPolicy,
|
|
4391
|
+
LocalToolTransport,
|
|
4035
4392
|
MemoryConflictError2 as MemoryConflictError,
|
|
4036
4393
|
NoopMemoryProvider,
|
|
4037
4394
|
PluginRegistry,
|
|
@@ -4041,6 +4398,7 @@ export {
|
|
|
4041
4398
|
ScopedProcessImpl,
|
|
4042
4399
|
ScopedSecretsImpl,
|
|
4043
4400
|
SemanticSummaryEngine,
|
|
4401
|
+
SimpleCompletionImpl,
|
|
4044
4402
|
SsrfError,
|
|
4045
4403
|
applyTemporalDecay,
|
|
4046
4404
|
assertWithinBase,
|