@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.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/capability-validator.ts
1521
- function hostMatchesPattern(host, pattern) {
1522
- if (pattern === host) return true;
1523
- if (pattern === "*") return true;
1524
- if (pattern.startsWith("*.")) {
1525
- const suffix = pattern.slice(1);
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
- if (caps.fs_reach) {
1551
- const personalityRead = personality.fs_reach?.read ?? [];
1552
- const personalityWrite = personality.fs_reach?.write ?? [];
1553
- if (caps.fs_reach.read && caps.fs_reach.read !== "from-personality") {
1554
- for (const toolPath of caps.fs_reach.read) {
1555
- const covered = personalityRead.some((p) => toolPath === p || toolPath.startsWith(`${p}/`));
1556
- if (!covered) {
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
- if (caps.fs_reach.write && caps.fs_reach.write !== "from-personality") {
1566
- for (const toolPath of caps.fs_reach.write) {
1567
- const covered = personalityWrite.some(
1568
- (p) => toolPath === p || toolPath.startsWith(`${p}/`)
1569
- );
1570
- if (!covered) {
1571
- errors.push({
1572
- tool: tool.name,
1573
- capability: "fs_reach.write",
1574
- message: `path "${toolPath}" is not covered by personality fs_reach.write`
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
- return errors;
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
- constructor(backends, reducers) {
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 executeParallel(calls, ctx, allowedTools, filterOpts, turnAttachments) {
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 budget = Math.min(perCallBudget, entry.tool.maxResultChars ?? perCallBudget);
1788
- const toolCtx = { ...ctx, resultBudgetChars: budget };
1966
+ const cappedBudget = Math.min(perCallBudget, entry.tool.maxResultChars ?? perCallBudget);
1789
1967
  try {
1790
- if (needsBackends(entry.tool.capabilities) && this.backends) {
1791
- const resolved = resolveCapabilities(
1792
- entry.tool.name,
1793
- entry.tool.capabilities,
1794
- { sessionId: ctx.sessionId, personalityId: ctx.personalityId },
1795
- { ...this.backends, inboundAttachments: turnAttachments }
1796
- );
1797
- Object.assign(toolCtx, resolved);
1798
- }
1799
- const rawResult = await entry.tool.execute(call.args, toolCtx);
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 > budget) {
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, budget)}
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,