diff --git a/.changeset/calm-skills-compose.md b/.changeset/calm-skills-compose.md new file mode 100644 index 0000000000..1c08b87230 --- /dev/null +++ b/.changeset/calm-skills-compose.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Compile the default `load_skill` tool as an ordinary agent source. The tool now reads the active node's skills from runtime context, so application overrides use the same path-based composition rules without a framework-only closure factory. diff --git a/packages/eve/src/compiler/compose-framework-sources.test.ts b/packages/eve/src/compiler/compose-framework-sources.test.ts index e0d5b8cf68..aa947613fc 100644 --- a/packages/eve/src/compiler/compose-framework-sources.test.ts +++ b/packages/eve/src/compiler/compose-framework-sources.test.ts @@ -42,6 +42,7 @@ describe("composeFrameworkSources", () => { expect(result.manifest.tools.map((tool) => tool.logicalPath)).toEqual([ "tools/bash.ts", "tools/connection_search.ts", + "tools/load_skill.ts", "tools/read_file.ts", "tools/todo.ts", "tools/web_fetch.ts", diff --git a/packages/eve/src/compiler/normalize-manifest.test.ts b/packages/eve/src/compiler/normalize-manifest.test.ts index bccfe586aa..1d81faedf6 100644 --- a/packages/eve/src/compiler/normalize-manifest.test.ts +++ b/packages/eve/src/compiler/normalize-manifest.test.ts @@ -311,6 +311,7 @@ describe("compileAgentManifest", () => { expect(compiled.webSearchProvider).toBe("exa"); expect(compiled.tools.map((tool) => tool.name)).toEqual([ "bash", + "load_skill", "read_file", "todo", "web_fetch", @@ -338,6 +339,7 @@ describe("compileAgentManifest", () => { expect(compiled.tools.map((tool) => tool.name)).toEqual([ "bash", + "load_skill", "read_file", "todo", "web_fetch", diff --git a/packages/eve/src/context/providers/skill-key.ts b/packages/eve/src/context/providers/skill-key.ts new file mode 100644 index 0000000000..2665d8c0b0 --- /dev/null +++ b/packages/eve/src/context/providers/skill-key.ts @@ -0,0 +1,6 @@ +import { ContextKey } from "#context/key.js"; +import type { ResolvedSkillDefinition } from "#runtime/types.js"; + +export const AuthoredSkillsKey = new ContextKey( + "eve.authoredSkills", +); diff --git a/packages/eve/src/context/providers/skill.ts b/packages/eve/src/context/providers/skill.ts new file mode 100644 index 0000000000..f8cb99d986 --- /dev/null +++ b/packages/eve/src/context/providers/skill.ts @@ -0,0 +1,15 @@ +import type { FrameworkContextProvider } from "#context/provider.js"; +import { AuthoredSkillsKey } from "#context/providers/skill-key.js"; +import { BundleKey } from "#runtime/sessions/runtime-context-keys.js"; +import type { ResolvedSkillDefinition } from "#runtime/types.js"; + +export const authoredSkillsProvider: FrameworkContextProvider = + { + key: AuthoredSkillsKey, + + create(ctx) { + const agent = ctx.get(BundleKey)?.graph.root.agent; + if (agent === undefined) return undefined; + return { value: agent.skills }; + }, + }; diff --git a/packages/eve/src/context/run-step.ts b/packages/eve/src/context/run-step.ts index efaeac6c98..973a8e67c6 100644 --- a/packages/eve/src/context/run-step.ts +++ b/packages/eve/src/context/run-step.ts @@ -4,6 +4,7 @@ import type { FrameworkContextProvider } from "#context/provider.js"; import { connectionProvider } from "#context/providers/connection.js"; import { sandboxProvider } from "#context/providers/sandbox.js"; import { sessionProvider } from "#context/providers/session.js"; +import { authoredSkillsProvider } from "#context/providers/skill.js"; /** * Framework providers in dependency order. @@ -13,6 +14,7 @@ import { sessionProvider } from "#context/providers/session.js"; */ const frameworkProviders: readonly FrameworkContextProvider[] = [ sessionProvider, + authoredSkillsProvider, connectionProvider, sandboxProvider, ]; diff --git a/packages/eve/src/execution/node-step.test.ts b/packages/eve/src/execution/node-step.test.ts index 5cfb115958..5f85e4f1b9 100644 --- a/packages/eve/src/execution/node-step.test.ts +++ b/packages/eve/src/execution/node-step.test.ts @@ -237,6 +237,48 @@ function createNoopRuntime(): Runtime { } describe("createNodeHarnessTools", () => { + it("classifies source-composed load_skill from binding ownership", async () => { + const definition = { + description: "Load a skill.", + execute: async () => "loaded", + inputSchema: toInputSchema({ type: "object" }), + logicalPath: "tools/load_skill.ts", + name: "load_skill", + sourceId: "eve.framework-defaults:tools/load_skill.ts", + sourceKind: "module" as const, + sourceOwner: { feature: "eve.framework-defaults", kind: "framework" as const }, + }; + const toolRegistry = await createRuntimeToolRegistry({ tools: [definition] }); + const tools = createNodeHarnessTools({ + node: createTestNode(createTestTurnAgent({ tools: toolRegistry.preparedTools }), { + toolRegistry, + }), + }); + + expect(tools.get("load_skill")?.frameworkAction).toBe("load-skill"); + }); + + it("keeps an application load_skill override as an ordinary tool", async () => { + const definition = { + description: "Application skill loader.", + execute: async () => "custom", + inputSchema: toInputSchema({ type: "object" }), + logicalPath: "tools/load_skill.ts", + name: "load_skill", + sourceId: "tools/load_skill.ts", + sourceKind: "module" as const, + sourceOwner: { kind: "application" as const }, + }; + const toolRegistry = await createRuntimeToolRegistry({ tools: [definition] }); + const tools = createNodeHarnessTools({ + node: createTestNode(createTestTurnAgent({ tools: toolRegistry.preparedTools }), { + toolRegistry, + }), + }); + + expect(tools.get("load_skill")?.frameworkAction).toBeUndefined(); + }); + it("guides the model to split large tasks across parallel agent calls", () => { const agentTool = createNodeHarnessTools({ node: createTestNode() }).get("agent"); diff --git a/packages/eve/src/execution/node-step.ts b/packages/eve/src/execution/node-step.ts index eb40d0ceeb..992aa5b9ce 100644 --- a/packages/eve/src/execution/node-step.ts +++ b/packages/eve/src/execution/node-step.ts @@ -280,7 +280,8 @@ function resolveHarnessToolDefinition(input: { } const def = registeredTool.definition; - const isFrameworkTool = def.sourceId.startsWith("eve:"); + const isNativeFrameworkTool = def.sourceOwner === undefined && def.sourceId.startsWith("eve:"); + const isFrameworkOwned = def.sourceOwner?.kind === "framework" || isNativeFrameworkTool; const rawExecute = def.execute; return { @@ -288,12 +289,12 @@ function resolveHarnessToolDefinition(input: { description: def.description, execution: def.execution, execute: resolveAuthoredExecute({ - isFrameworkTool, + isNativeFrameworkTool, rawExecute, scope: def.name, }), frameworkAction: - isFrameworkTool && def.name === LOAD_SKILL_TOOL_NAME ? "load-skill" : undefined, + isFrameworkOwned && def.name === LOAD_SKILL_TOOL_NAME ? "load-skill" : undefined, inputSchema: def.inputSchema ?? UNSPECIFIED_INPUT_SCHEMA, name: def.name, approval: def.approval, @@ -305,7 +306,7 @@ function resolveHarnessToolDefinition(input: { /** * Selects the harness-facing `execute` for one authored tool. * - * - Framework tools (`eve:` source) run their `execute` verbatim — they + * - Native framework tools run their `execute` verbatim — they * manage their own context and never receive an authored * {@link ToolContext}. * - Authored tools are wrapped by {@link createToolExecuteWithAuth}, @@ -314,15 +315,15 @@ function resolveHarnessToolDefinition(input: { * - Tools without `execute` (provider-managed) stay `undefined`. */ function resolveAuthoredExecute(input: { - readonly isFrameworkTool: boolean; + readonly isNativeFrameworkTool: boolean; readonly rawExecute: ResolvedToolDefinition["execute"]; readonly scope: string; }): HarnessToolDefinition["execute"] { - const { isFrameworkTool, rawExecute, scope } = input; + const { isNativeFrameworkTool, rawExecute, scope } = input; if (rawExecute === undefined) { return undefined; } - if (isFrameworkTool) { + if (isNativeFrameworkTool) { return rawExecute; } const authored = rawExecute as ( diff --git a/packages/eve/src/framework-sources/registry.ts b/packages/eve/src/framework-sources/registry.ts index 707bce0750..8581bd880f 100644 --- a/packages/eve/src/framework-sources/registry.ts +++ b/packages/eve/src/framework-sources/registry.ts @@ -1,5 +1,6 @@ import * as bash from "./tools/bash.js"; import * as connectionSearch from "./tools/connection_search.js"; +import * as loadSkill from "./tools/load_skill.js"; import * as readFile from "./tools/read_file.js"; import * as sandbox from "./sandbox.js"; import * as todo from "./tools/todo.js"; @@ -15,6 +16,7 @@ const frameworkAgentSource = defineProgrammaticAgentSource({ { logicalPath: "sandbox.ts", namespace: sandbox }, { logicalPath: "tools/bash.ts", namespace: bash }, { logicalPath: "tools/connection_search.ts", namespace: connectionSearch }, + { logicalPath: "tools/load_skill.ts", namespace: loadSkill }, { logicalPath: "tools/read_file.ts", namespace: readFile }, { logicalPath: "tools/todo.ts", namespace: todo }, { logicalPath: "tools/web_fetch.ts", namespace: webFetch }, diff --git a/packages/eve/src/framework-sources/tools/load_skill.ts b/packages/eve/src/framework-sources/tools/load_skill.ts new file mode 100644 index 0000000000..b54f0535a5 --- /dev/null +++ b/packages/eve/src/framework-sources/tools/load_skill.ts @@ -0,0 +1 @@ +export { loadSkill as default } from "#public/tools/defaults.js"; diff --git a/packages/eve/src/public/tools/defaults.ts b/packages/eve/src/public/tools/defaults.ts index 97479c818b..0586447bb1 100644 --- a/packages/eve/src/public/tools/defaults.ts +++ b/packages/eve/src/public/tools/defaults.ts @@ -3,7 +3,7 @@ * values so authors can spread, wrap, or patch them inside their own * `agent/tools/*.ts` files. */ -import { SKILL_TOOL_DEFINITION } from "#runtime/framework-tools/skill.js"; +import { loadSkillToolDefinition } from "#runtime/framework-tools/skill.js"; import { TODO_INPUT_SCHEMA, TODO_OUTPUT_SCHEMA, @@ -16,7 +16,6 @@ import { } from "#runtime/framework-tools/web-fetch.js"; import { executeWebFetchTool, type WebFetchInput } from "#execution/web-fetch/tool.js"; import type { ToolDefinition } from "#public/definitions/tool.js"; -import { toPublicToolDefinition } from "#public/tools/internal.js"; import { defineBashTool } from "#public/tools/define-bash-tool.js"; import { defineGlobTool } from "#public/tools/define-glob-tool.js"; import { defineGrepTool } from "#public/tools/define-grep-tool.js"; @@ -146,4 +145,4 @@ export const todo: ToolDefinition = { * framework does not surface skill descriptions to the model, so the model has * nothing to load. */ -export const loadSkill: ToolDefinition = toPublicToolDefinition(SKILL_TOOL_DEFINITION); +export const loadSkill: ToolDefinition = loadSkillToolDefinition; diff --git a/packages/eve/src/runtime/framework-tools/index.ts b/packages/eve/src/runtime/framework-tools/index.ts index c0b12a7402..d3930aa578 100644 --- a/packages/eve/src/runtime/framework-tools/index.ts +++ b/packages/eve/src/runtime/framework-tools/index.ts @@ -4,10 +4,7 @@ import { BASH_TOOL_DEFINITION } from "#runtime/framework-tools/bash.js"; import { GLOB_TOOL_DEFINITION } from "#runtime/framework-tools/glob.js"; import { GREP_TOOL_DEFINITION } from "#runtime/framework-tools/grep.js"; import { READ_FILE_TOOL_DEFINITION } from "#runtime/framework-tools/read-file.js"; -import { - createSkillToolDefinition, - SKILL_TOOL_DEFINITION, -} from "#runtime/framework-tools/skill.js"; +import { SKILL_TOOL_DEFINITION } from "#runtime/framework-tools/skill.js"; import { TASK_TOOL_DEFINITIONS } from "#runtime/framework-tools/tasks.js"; import { TODO_TOOL_DEFINITION } from "#runtime/framework-tools/todo.js"; import { WEB_FETCH_TOOL_DEFINITION } from "#runtime/framework-tools/web-fetch.js"; @@ -20,7 +17,7 @@ export { ReadFileStateKey } from "#runtime/framework-tools/file-state.js"; export type { TodoItem, TodoState } from "#runtime/framework-tools/todo.js"; export { TodoStateKey } from "#runtime/framework-tools/todo.js"; -import type { ResolvedSkillDefinition, ResolvedToolDefinition } from "#runtime/types.js"; +import type { ResolvedToolDefinition } from "#runtime/types.js"; const REGISTERED_FRAMEWORK_TOOLS: readonly ResolvedToolDefinition[] = [ ASK_QUESTION_TOOL_DEFINITION, @@ -51,17 +48,8 @@ const ALL_FRAMEWORK_TOOLS: readonly ResolvedToolDefinition[] = [ * * Source-composed dynamic tools are not represented in this legacy catalog. */ -export function getFrameworkToolDefinitions(config?: { - readonly authoredSkills?: readonly ResolvedSkillDefinition[]; -}): readonly ResolvedToolDefinition[] { - const authoredSkills = config?.authoredSkills; - if (authoredSkills === undefined) return REGISTERED_FRAMEWORK_TOOLS; - - return REGISTERED_FRAMEWORK_TOOLS.map((definition) => - definition.name === SKILL_TOOL_DEFINITION.name - ? createSkillToolDefinition(authoredSkills) - : definition, - ); +export function getFrameworkToolDefinitions(): readonly ResolvedToolDefinition[] { + return REGISTERED_FRAMEWORK_TOOLS; } /** diff --git a/packages/eve/src/runtime/framework-tools/skill.test.ts b/packages/eve/src/runtime/framework-tools/skill.test.ts index fa728ab233..055a1216ae 100644 --- a/packages/eve/src/runtime/framework-tools/skill.test.ts +++ b/packages/eve/src/runtime/framework-tools/skill.test.ts @@ -3,38 +3,41 @@ import { describe, expect, it, vi } from "vitest"; import { ContextContainer, contextStorage } from "#context/container.js"; import { DynamicSkillManifestKey, SandboxKey } from "#context/keys.js"; import { ConnectionRegistryKey } from "#context/providers/connection-key.js"; +import { AuthoredSkillsKey } from "#context/providers/skill-key.js"; import { mockSandbox } from "#internal/testing/mocks/mock-sandbox.js"; import type { ConnectionRegistry } from "#runtime/connections/types.js"; -import { - createSkillToolDefinition, - SKILL_TOOL_DEFINITION, -} from "#runtime/framework-tools/skill.js"; +import { loadSkill } from "#public/tools/defaults.js"; import { createSandboxSkillHandle } from "#runtime/skills/sandbox-access.js"; import type { ResolvedSkillDefinition } from "#runtime/types.js"; -function skillToolExecutor(skills: readonly ResolvedSkillDefinition[] = []) { - const execute = createSkillToolDefinition(skills).execute; +function skillToolExecutor() { + const execute = loadSkill.execute; if (execute === undefined) throw new Error("load_skill tool is missing an execute function"); return execute; } -describe("SKILL_TOOL_DEFINITION", () => { +function setAuthoredSkills( + ctx: ContextContainer, + skills: readonly ResolvedSkillDefinition[] = [], +): void { + ctx.set(AuthoredSkillsKey, skills); +} + +describe("loadSkill", () => { it("describes when skill loading should be used", () => { - expect(SKILL_TOOL_DEFINITION.description).toContain( - "request clearly matches a listed skill description", - ); - expect(SKILL_TOOL_DEFINITION.description).toContain( + expect(loadSkill.description).toContain("request clearly matches a listed skill description"); + expect(loadSkill.description).toContain( "Loading adds the skill instructions to the current turn.", ); - expect(SKILL_TOOL_DEFINITION.description).toContain("Available skills block"); - expect(SKILL_TOOL_DEFINITION.description).not.toContain("connection_search"); + expect(loadSkill.description).toContain("Available skills block"); + expect(loadSkill.description).not.toContain("connection_search"); }); }); describe("load_skill executor", () => { it("loads an authored markdown skill when no sandbox context is available", async () => { const ctx = new ContextContainer(); - const execute = skillToolExecutor([ + setAuthoredSkills(ctx, [ { description: "Research a topic systematically", logicalPath: "skills/research.md", @@ -52,11 +55,10 @@ describe("load_skill executor", () => { sourceKind: "markdown", }, ]); + const execute = skillToolExecutor(); await expect( - contextStorage.run(ctx, () => - execute({ skill: "research" }, { messages: [], toolCallId: "call_1" }), - ), + contextStorage.run(ctx, () => execute({ skill: "research" }, {} as never)), ).resolves.toBe("# Research\n\nFollow the evidence.\n"); }); @@ -74,7 +76,7 @@ describe("load_skill executor", () => { }; const ctx = new ContextContainer(); ctx.set(SandboxKey, access); - const execute = skillToolExecutor([ + setAuthoredSkills(ctx, [ { assetsPath: "/authored/skills/incident-response/assets", description: "Run the full incident response procedure", @@ -91,11 +93,10 @@ describe("load_skill executor", () => { sourceKind: "skill-package", }, ]); + const execute = skillToolExecutor(); await expect( - contextStorage.run(ctx, () => - execute({ skill: "incident-response" }, { messages: [], toolCallId: "call_2" }), - ), + contextStorage.run(ctx, () => execute({ skill: "incident-response" }, {} as never)), ).resolves.toBe( "# Incident response\n\nConsult `references/services/api/owners.md` when needed.\n", ); @@ -116,10 +117,7 @@ describe("load_skill executor", () => { }); const ctx = new ContextContainer(); ctx.set(SandboxKey, sandbox.access); - ctx.set(DynamicSkillManifestKey, { - policy: [{ description: "Apply the dynamic policy", name: "policy" }], - }); - const execute = skillToolExecutor([ + setAuthoredSkills(ctx, [ { description: "Apply the static policy", logicalPath: "skills/policy.md", @@ -129,17 +127,20 @@ describe("load_skill executor", () => { sourceKind: "markdown", }, ]); + ctx.set(DynamicSkillManifestKey, { + policy: [{ description: "Apply the dynamic policy", name: "policy" }], + }); + const execute = skillToolExecutor(); await expect( - contextStorage.run(ctx, () => - execute({ skill: "policy" }, { messages: [], toolCallId: "call_dynamic" }), - ), + contextStorage.run(ctx, () => execute({ skill: "policy" }, {} as never)), ).resolves.toBe("# Dynamic policy\n"); }); it("surfaces dynamic skill names when the requested id is missing", async () => { const ctx = new ContextContainer(); ctx.set(SandboxKey, mockSandbox().access); + setAuthoredSkills(ctx); ctx.set(DynamicSkillManifestKey, { custom: [ { description: "Talk like a dog", name: "custom__talk-like-a-dog" }, @@ -149,9 +150,7 @@ describe("load_skill executor", () => { const execute = skillToolExecutor(); await expect( - contextStorage.run(ctx, () => - execute({ skill: "talk-like-a-dog" }, { messages: [], toolCallId: "call_1" }), - ), + contextStorage.run(ctx, () => execute({ skill: "talk-like-a-dog" }, {} as never)), ).rejects.toThrow("Available skills: custom__bark, custom__talk-like-a-dog."); }); @@ -168,12 +167,11 @@ describe("load_skill executor", () => { const ctx = new ContextContainer(); ctx.set(SandboxKey, mockSandbox().access); ctx.set(ConnectionRegistryKey, registry); + setAuthoredSkills(ctx); const execute = skillToolExecutor(); await expect( - contextStorage.run(ctx, () => - execute({ skill: "linear" }, { messages: [], toolCallId: "call_1" }), - ), + contextStorage.run(ctx, () => execute({ skill: "linear" }, {} as never)), ).rejects.toThrow( '"linear" is an installed connection, not a skill. Use connection_search with connection "linear" to find its tools.', ); diff --git a/packages/eve/src/runtime/framework-tools/skill.ts b/packages/eve/src/runtime/framework-tools/skill.ts index d75f8f0992..2148a3540b 100644 --- a/packages/eve/src/runtime/framework-tools/skill.ts +++ b/packages/eve/src/runtime/framework-tools/skill.ts @@ -3,13 +3,15 @@ import { z } from "#compiled/zod/index.js"; import { loadContext } from "#context/container.js"; import { DynamicSkillManifestKey, SandboxKey } from "#context/keys.js"; import { ConnectionRegistryKey } from "#context/providers/connection-key.js"; +import { AuthoredSkillsKey } from "#context/providers/skill-key.js"; +import type { ToolDefinition } from "#public/definitions/tool.js"; import { loadSkillFromSandbox } from "#runtime/skills/sandbox-access.js"; -import type { ResolvedSkillDefinition, ResolvedToolDefinition } from "#runtime/types.js"; +import type { ResolvedToolDefinition } from "#runtime/types.js"; /** * Typed input accepted by {@link executeLoadSkillTool}. */ -type LoadSkillInput = z.infer; +export type LoadSkillInput = z.infer; /** * Executes the `load_skill` tool. @@ -18,11 +20,9 @@ type LoadSkillInput = z.infer; * Active dynamic skills take precedence and remain sandbox-backed because * their full package content is currently materialized there at runtime. */ -async function executeLoadSkillTool( - args: LoadSkillInput, - authoredSkills: readonly ResolvedSkillDefinition[], -): Promise { +export async function executeLoadSkillTool(args: LoadSkillInput): Promise { const ctx = loadContext(); + const authoredSkills = ctx.require(AuthoredSkillsKey); const { skill } = args; const dynamicSkillNames = availableDynamicSkillNames(ctx); const availableSkills = [ @@ -75,41 +75,34 @@ function formatSkillNotFoundError(skill: string, availableSkills: readonly strin return `No skill named "${skill}".${hint}`; } -// --------------------------------------------------------------------------- -// Tool definition -// --------------------------------------------------------------------------- - export const SKILL_INPUT_SCHEMA = z.strictObject({ skill: z.string().describe("Available skill name or id."), }); export const SKILL_OUTPUT_SCHEMA = z.string(); -const SKILL_TOOL_METADATA = { +export const loadSkillToolDefinition: ToolDefinition = { description: [ "Load the full instructions for one available skill by name or id.", "Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill.", "Loading adds the skill instructions to the current turn.", 'Choose the "skill" value from the Available skills block.', ].join(" "), + execute: async (input) => executeLoadSkillTool(input as LoadSkillInput), inputSchema: SKILL_INPUT_SCHEMA, - logicalPath: "eve:framework/load-skill", - name: "load_skill", outputSchema: SKILL_OUTPUT_SCHEMA, - sourceId: "eve:load-skill-tool", - sourceKind: "module" as const, }; /** - * Creates a node-specific `load_skill` definition with authored skills bound - * into its executor. + * Transitional runtime-catalog projection. Source-composed manifests replace + * this entry by canonical path; legacy in-memory graph fixtures still use it. */ -export function createSkillToolDefinition( - authoredSkills: readonly ResolvedSkillDefinition[], -): ResolvedToolDefinition { - return { - ...SKILL_TOOL_METADATA, - execute: (input) => executeLoadSkillTool(input as LoadSkillInput, authoredSkills), - }; -} - -export const SKILL_TOOL_DEFINITION = createSkillToolDefinition([]); +export const SKILL_TOOL_DEFINITION: ResolvedToolDefinition = { + description: loadSkillToolDefinition.description, + execute: (input) => executeLoadSkillTool(input as LoadSkillInput), + inputSchema: SKILL_INPUT_SCHEMA, + logicalPath: "eve:framework/load-skill", + name: "load_skill", + outputSchema: SKILL_OUTPUT_SCHEMA, + sourceId: "eve:load-skill-tool", + sourceKind: "module", +}; diff --git a/packages/eve/src/runtime/resolve-agent-graph.ts b/packages/eve/src/runtime/resolve-agent-graph.ts index d3eb4c2d2c..933a1c54a2 100644 --- a/packages/eve/src/runtime/resolve-agent-graph.ts +++ b/packages/eve/src/runtime/resolve-agent-graph.ts @@ -142,9 +142,7 @@ async function resolveRuntimeAgentNode( moduleMap: input.moduleMap, nodeId: input.nodeId, }); - const frameworkTools = getFrameworkToolDefinitions({ - authoredSkills: agent.skills, - }); + const frameworkTools = getFrameworkToolDefinitions(); const frameworkToolNames = new Set(frameworkTools.map((t) => t.name)); const allFrameworkToolNames = getAllFrameworkToolNames(); diff --git a/packages/eve/src/runtime/resolve-agent.ts b/packages/eve/src/runtime/resolve-agent.ts index d307163e76..e731681b70 100644 --- a/packages/eve/src/runtime/resolve-agent.ts +++ b/packages/eve/src/runtime/resolve-agent.ts @@ -65,7 +65,12 @@ export async function resolveAgent(input: ResolveAgentInput): Promise - resolveToolDefinition(toolDefinition, input.moduleMap, input.nodeId), + resolveToolDefinition( + toolDefinition, + input.moduleMap, + input.nodeId, + input.manifest.bindings[toolDefinition.sourceId]?.owner, + ), ), ); const resolvedDynamicInstructionsResolvers = await Promise.all( diff --git a/packages/eve/src/runtime/resolve-tool.ts b/packages/eve/src/runtime/resolve-tool.ts index 29bfc48261..c5f2cd0b2c 100644 --- a/packages/eve/src/runtime/resolve-tool.ts +++ b/packages/eve/src/runtime/resolve-tool.ts @@ -1,4 +1,5 @@ import type { CompiledToolDefinition } from "#compiler/manifest.js"; +import type { AgentSourceOwner } from "#compiler/module-binding.js"; import type { CompiledModuleMap } from "#compiler/module-map.js"; import { expectFunction, expectObjectRecord } from "#internal/authored-module.js"; import { normalizeApproval } from "#internal/authored-definition/approval.js"; @@ -21,6 +22,7 @@ export async function resolveToolDefinition( definition: CompiledToolDefinition, moduleMap: CompiledModuleMap, nodeId: string | undefined, + sourceOwner?: AgentSourceOwner, ): Promise { try { const resolvedExportValue = await loadResolvedModuleExport({ @@ -67,6 +69,7 @@ export async function resolveToolDefinition( outputSchema, sourceId: definition.sourceId, sourceKind: "module", + sourceOwner, ...extractOptionalHooks(resolvedRecord, definition), }; } catch (error) { diff --git a/packages/eve/src/runtime/types.ts b/packages/eve/src/runtime/types.ts index 3b7f87e80c..9d4633955a 100644 --- a/packages/eve/src/runtime/types.ts +++ b/packages/eve/src/runtime/types.ts @@ -20,6 +20,7 @@ import type { } from "#runtime/connections/types.js"; import type { OpenAPISpecSource } from "#public/definitions/connections/openapi.js"; import type { CompiledWorkspaceResourceRoot } from "#compiler/manifest.js"; +import type { AgentSourceOwner } from "#compiler/module-binding.js"; import type { WorkspaceRuntimeSpec } from "#runtime/workspace/types.js"; import type { JsonObject } from "#shared/json.js"; import type { Optional } from "#shared/optional.js"; @@ -161,6 +162,8 @@ export type ResolvedToolDefinition = Readonly< > > & ResolvedModuleSourceRef & { + /** Compiler-recorded owner of the source that won this logical tool slot. */ + readonly sourceOwner?: AgentSourceOwner; /** * Validated runtime input schema. Compiled and durable JSON Schemas are * rehydrated before entering this runtime-owned definition.