@crewhaus/eval-runner 0.1.3 → 0.1.5

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/src/wire-once.ts DELETED
@@ -1,204 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- /**
4
- * Build the agent stack ONCE per eval run, then share across samples.
5
- *
6
- * This factors the runRun logic from apps/cli/src/index.ts so the CLI's
7
- * `crewhaus run` and the eval runner's `crewhaus eval` use the same
8
- * tool/hook/skill/MCP/sub-agent wiring. Single source of truth for the
9
- * "what is the full agent stack from an IR" question.
10
- *
11
- * The MCP host (if any) is shared across all eval samples — re-spinning
12
- * stdio MCP servers per sample for 200 samples would burn ~30s in process
13
- * startup and exceed the T7 SLO. The trade-off is documented: eval-runner
14
- * assumes the agent's MCP usage is read-mostly. An `isolateMcpPerSample`
15
- * escape hatch is reserved for a future where this matters.
16
- */
17
- import type { SubAgentDefinition } from "@crewhaus/agent-context-isolation";
18
- import { type HookDef, loadHooks } from "@crewhaus/hooks-engine";
19
- import type { IrV0 } from "@crewhaus/ir";
20
- import { createLogger } from "@crewhaus/logging";
21
- import { McpHost } from "@crewhaus/mcp-host";
22
- import {
23
- BUILTIN_DEFAULT_RULES,
24
- PermissionConfigError,
25
- type RuleSet,
26
- parsePermissionsConfig,
27
- tagRules,
28
- } from "@crewhaus/permission-engine";
29
- import { type SkillRef, createSkillTool, discoverSkills } from "@crewhaus/skills-registry";
30
- import { type SlashCommand, loadCommands } from "@crewhaus/slash-commands";
31
- import { spawnSubAgent } from "@crewhaus/sub-agent-spawner";
32
- import { type RegisteredTool, ToolCatalog } from "@crewhaus/tool-catalog";
33
- import { registerMcpServer } from "@crewhaus/tool-mcp";
34
- import { createTaskTool } from "@crewhaus/tool-task";
35
- import { RunnerError } from "./errors";
36
-
37
- type SpawnSubAgentFn = typeof spawnSubAgent;
38
-
39
- export type SharedAgentDeps = {
40
- readonly tools: ReadonlyArray<RegisteredTool>;
41
- readonly hooks: ReadonlyArray<HookDef>;
42
- readonly skills: ReadonlyArray<SkillRef>;
43
- readonly slashCommands: ReadonlyMap<string, SlashCommand>;
44
- readonly subAgents?: ReadonlyMap<string, SubAgentDefinition>;
45
- readonly spawnSubAgent?: SpawnSubAgentFn;
46
- readonly permissionRules: RuleSet;
47
- readonly mcpHost?: McpHost;
48
- readonly model: string;
49
- readonly instructions: string;
50
- readonly sessionName: string;
51
- readonly sessionTarget: string;
52
- };
53
-
54
- const logger = createLogger({ bindings: { module: "eval-runner.wire" } });
55
-
56
- export async function wireRunOnce(ir: IrV0, opts: { cwd?: string } = {}): Promise<SharedAgentDeps> {
57
- const cwd = opts.cwd ?? process.cwd();
58
-
59
- // Tools.
60
- let tools: RegisteredTool[] = [];
61
- if (ir.tools.length > 0) {
62
- await applyToolConfigs(ir.tools, ir.toolConfigs);
63
- const toolMap = await loadToolMap();
64
- tools = ir.tools.map((name) => {
65
- const tool = toolMap[name];
66
- if (!tool) {
67
- const known = Object.keys(toolMap).sort().join(", ");
68
- throw new RunnerError(`unknown tool "${name}" — known tools: ${known}`);
69
- }
70
- return tool;
71
- });
72
- }
73
-
74
- // MCP servers (shared across samples).
75
- let mcpHost: McpHost | undefined;
76
- if (Object.keys(ir.mcp_servers).length > 0) {
77
- const host = new McpHost({ logger });
78
- mcpHost = host;
79
- for (const [name, cfg] of Object.entries(ir.mcp_servers)) host.addServer(name, cfg);
80
- const tempCatalog = new ToolCatalog();
81
- for (const t of tools) tempCatalog.register(t);
82
- await Promise.all(
83
- Object.keys(ir.mcp_servers).map((name) => registerMcpServer(host, name, tempCatalog)),
84
- );
85
- tools = tempCatalog.list().slice();
86
- }
87
-
88
- // Permission rules.
89
- const permissionRules = buildRuleSet(ir.permissions.rules, cwd);
90
-
91
- // Hooks / skills / slash-commands.
92
- const [hooks, skills, slashCommands] = await Promise.all([
93
- loadHooks({ cwd }),
94
- discoverSkills({ cwd }),
95
- loadCommands({ cwd }),
96
- ]);
97
- if (skills.length > 0) tools.push(createSkillTool(skills));
98
-
99
- // Sub-agents.
100
- let subAgents: ReadonlyMap<string, SubAgentDefinition> | undefined;
101
- if (ir.subAgents.length > 0) {
102
- subAgents = new Map(
103
- ir.subAgents.map((d) => [
104
- d.name,
105
- {
106
- name: d.name,
107
- description: d.description,
108
- instructions: d.instructions,
109
- tools: d.tools,
110
- ...(d.model !== undefined ? { model: d.model } : {}),
111
- permissions: d.permissions,
112
- inherit_bypass: d.inheritBypass,
113
- } satisfies SubAgentDefinition,
114
- ]),
115
- );
116
- tools.push(createTaskTool({ subAgents }));
117
- }
118
-
119
- return {
120
- tools,
121
- hooks,
122
- skills,
123
- slashCommands,
124
- permissionRules,
125
- model: ir.agent.model,
126
- instructions: ir.agent.instructions,
127
- sessionName: ir.name,
128
- sessionTarget: ir.target,
129
- ...(subAgents !== undefined ? { subAgents, spawnSubAgent } : {}),
130
- ...(mcpHost !== undefined ? { mcpHost } : {}),
131
- };
132
- }
133
-
134
- async function loadToolMap(): Promise<Record<string, RegisteredTool>> {
135
- const [fs, bash, todo, web, image, fetchPkg] = await Promise.all([
136
- import("@crewhaus/tool-fs"),
137
- import("@crewhaus/tool-bash"),
138
- import("@crewhaus/tool-todo"),
139
- import("@crewhaus/tool-web"),
140
- import("@crewhaus/tool-image"),
141
- import("@crewhaus/tool-fetch"),
142
- ]);
143
- return {
144
- read: fs.read,
145
- write: fs.write,
146
- edit: fs.edit,
147
- glob: fs.glob,
148
- grep: fs.grep,
149
- bash: bash.bash,
150
- todoWrite: todo.todoWrite,
151
- webFetch: web.webFetch,
152
- webSearch: web.webSearch,
153
- readImage: image.readImage,
154
- fetch: fetchPkg.fetch,
155
- };
156
- }
157
-
158
- async function applyToolConfigs(
159
- toolNames: readonly string[],
160
- toolConfigs: Readonly<Record<string, unknown>>,
161
- ): Promise<void> {
162
- const used = new Set(toolNames);
163
- if (used.has("fetch") && toolConfigs["fetch"] !== undefined) {
164
- const { registerFetchConfig } = await import("@crewhaus/tool-fetch");
165
- registerFetchConfig(toolConfigs["fetch"] as Parameters<typeof registerFetchConfig>[0]);
166
- }
167
- if (used.has("webFetch") && toolConfigs["webFetch"] !== undefined) {
168
- const { registerWebFetchConfig } = await import("@crewhaus/tool-web");
169
- registerWebFetchConfig(toolConfigs["webFetch"] as Parameters<typeof registerWebFetchConfig>[0]);
170
- }
171
- }
172
-
173
- function buildRuleSet(
174
- yamlRules: ReadonlyArray<{ type: "alwaysAllow" | "alwaysDeny" | "alwaysAsk"; pattern: string }>,
175
- cwd: string,
176
- ): RuleSet {
177
- let settings: RuleSet["settings"] = [];
178
- const settingsPath = join(cwd, ".crewhaus", "settings.json");
179
- if (existsSync(settingsPath)) {
180
- let raw: unknown;
181
- try {
182
- raw = JSON.parse(readFileSync(settingsPath, "utf-8"));
183
- } catch (err) {
184
- throw new RunnerError(`failed to parse ${settingsPath}: ${(err as Error).message}`, err);
185
- }
186
- const root = (raw as { permissions?: unknown }).permissions;
187
- if (root !== undefined) {
188
- try {
189
- const parsed = parsePermissionsConfig(root, "settings");
190
- settings = tagRules(parsed.rules, "settings");
191
- } catch (err) {
192
- if (err instanceof PermissionConfigError) throw new RunnerError(err.message, err);
193
- throw err;
194
- }
195
- }
196
- }
197
- return {
198
- flag: [],
199
- settings,
200
- yaml: tagRules(yamlRules, "yaml"),
201
- hooks: [],
202
- builtin: BUILTIN_DEFAULT_RULES,
203
- };
204
- }