Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-skills-compose.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/eve/src/compiler/normalize-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -338,6 +339,7 @@ describe("compileAgentManifest", () => {

expect(compiled.tools.map((tool) => tool.name)).toEqual([
"bash",
"load_skill",
"read_file",
"todo",
"web_fetch",
Expand Down
6 changes: 6 additions & 0 deletions packages/eve/src/context/providers/skill-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ContextKey } from "#context/key.js";
import type { ResolvedSkillDefinition } from "#runtime/types.js";

export const AuthoredSkillsKey = new ContextKey<readonly ResolvedSkillDefinition[]>(
"eve.authoredSkills",
);
15 changes: 15 additions & 0 deletions packages/eve/src/context/providers/skill.ts
Original file line number Diff line number Diff line change
@@ -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<readonly ResolvedSkillDefinition[]> =
{
key: AuthoredSkillsKey,

create(ctx) {
const agent = ctx.get(BundleKey)?.graph.root.agent;
if (agent === undefined) return undefined;
return { value: agent.skills };
},
};
2 changes: 2 additions & 0 deletions packages/eve/src/context/run-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -13,6 +14,7 @@ import { sessionProvider } from "#context/providers/session.js";
*/
const frameworkProviders: readonly FrameworkContextProvider<any>[] = [
sessionProvider,
authoredSkillsProvider,
connectionProvider,
sandboxProvider,
];
Expand Down
42 changes: 42 additions & 0 deletions packages/eve/src/execution/node-step.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
15 changes: 8 additions & 7 deletions packages/eve/src/execution/node-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,20 +280,21 @@ 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 {
approvalKey: def.approvalKey,
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,
Expand All @@ -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},
Expand All @@ -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 (
Expand Down
2 changes: 2 additions & 0 deletions packages/eve/src/framework-sources/registry.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 },
Expand Down
1 change: 1 addition & 0 deletions packages/eve/src/framework-sources/tools/load_skill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { loadSkill as default } from "#public/tools/defaults.js";
5 changes: 2 additions & 3 deletions packages/eve/src/public/tools/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down Expand Up @@ -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;
20 changes: 4 additions & 16 deletions packages/eve/src/runtime/framework-tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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,
Expand Down Expand Up @@ -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;
}

/**
Expand Down
Loading
Loading