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 plannerMetaSchema = z.object({ senseName: z.string().describe("kebab-case sense name from the plan"), }); export type PlannerMeta = z.infer; export function plannerPrompt({ threadId }: { threadId: string }): string { return `You are planning a new Nerve sense. Read the workflow thread for the user's request: \`nerve thread ${threadId}\` Read the nerve-dev skill for sense conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` Also look at existing senses in the \`senses/\` directory for patterns. Pick a good kebab-case name for this sense. Produce a PLAN (not code) in markdown: ## Sense Design ### Name — kebab-case ### Fields — name, type (integer/real/text), description ### Compute Logic — step-by-step, specific Node.js APIs or shell commands ### Trigger Config — group, interval, throttle, timeout Output ONLY the plan. Be precise and implementation-ready.`; } export function createPlannerRole(adapter: AgentFn, extract: LlmExtractorConfig): Role { return createRole( adapter, async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }), plannerMetaSchema, extract, ); }