import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; export const testerMetaSchema = z.object({ passed: z.boolean().describe("true if all e2e checks passed"), }); export type TesterMeta = z.infer; export function testerPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string { return `You are testing a newly created Nerve sense end-to-end. **IMPORTANT: The Nerve workspace is at \`${nerveRoot}\`. All paths below are relative to this directory. Always \`cd ${nerveRoot}\` first.** Read the workflow thread for context: \`nerve thread ${threadId}\` Read the nerve-dev skill for expected file structure: \`cat ${nerveRoot}/node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` Verify the full lifecycle in this order: 1. **File check** — all required sense files exist: - \`senses//src/index.ts\` - \`senses//src/schema.ts\` - \`senses//migrations/\` - \`senses//package.json\` 2. **Build** — run inside the sense directory: \`\`\` cd ${nerveRoot}/senses/ && pnpm install --no-cache && pnpm build \`\`\` Must produce \`index.js\` at sense root without errors. 3. **Config check** — \`nerve validate\` passes, confirming nerve.yaml is valid. 4. **Sense list** — \`nerve sense list\` shows the sense. 5. **Trigger** — \`nerve sense trigger \` completes without error. 6. **Query** — \`nerve sense query \` — retry up to 20s until rows appear. If any step fails, include the relevant error output. Output a clear summary: what you checked, what passed, what failed, and why.`; } export function createTesterRole( adapter: AgentFn, extract: LlmExtractorConfig, nerveRoot: string, ): Role { return createRole( adapter, async (start: StartStep) => testerPrompt({ threadId: start.meta.threadId, nerveRoot }), testerMetaSchema, extract, ); }