{"version":3,"file":"planner.mjs","names":[],"sources":["../../../../../../../ai/src/planner/planner.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { PlannerConfig } from \"../contracts/planner/planner-config.type\";\nimport type {\n  PlannerExecuteOptions,\n  PlannerResumeOptions,\n} from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerResult } from \"../contracts/planner/planner-result.type\";\nimport type { PlannerContract } from \"../contracts/planner/planner.contract\";\nimport { PlannerFailedError } from \"../errors\";\nimport { buildPlanSystemPrompt } from \"./plan-prompt\";\nimport { PlannerRun } from \"./planner-run\";\nimport { computeSignature } from \"./signature\";\nimport { loadPlannerSnapshotForResume } from \"./snapshot\";\n\nconst LOG_MODULE = \"ai.planner\";\n\n/**\n * `ai.planner(config)` — construct a {@link PlannerContract}.\n *\n * Validates the config at author time (throws {@link PlannerFailedError}\n * on a bad shape), builds (or adopts) the plan-generation agent, computes\n * a stable structural signature, and returns an instance satisfying\n * `ExecutableContract` so the planner composes into supervisors,\n * orchestrators, and outer agents through the same uniform surface.\n *\n * At `execute(goal)` the planner asks its LLM for an ordered plan over\n * the registered `capabilities`, then executes that plan step-by-step\n * through each capability's own `execute()` — reusing the existing\n * executable machinery rather than forking it — and returns the unified\n * `{ data, report, usage, error }` envelope with `report.type ===\n * \"planner\"`.\n *\n * @example\n * const research = ai.planner({\n *   name: \"research-assistant\",\n *   model: ai.openai.model({ name: \"gpt-4o\" }),\n *   capabilities: [\n *     { name: \"search\", description: \"Search the web\", executable: searchAgent },\n *     { name: \"write\", description: \"Draft a summary\", executable: writerAgent },\n *   ],\n *   maxSteps: 6,\n * });\n *\n * const { data, report } = await research.execute(\"Compare React vs Vue in 2026\");\n */\nexport function planner<TOutput = unknown>(\n  config: PlannerConfig<TOutput>,\n): PlannerContract<TOutput> {\n  validateConfig(config);\n\n  const maxSteps = config.maxSteps ?? 10;\n  const capabilities = new Map<string, PlannerCapability>();\n\n  for (const capability of config.capabilities) {\n    capabilities.set(capability.name, capability);\n  }\n\n  const signature = computeSignature(config.name, config.capabilities);\n  const planningAgent = resolvePlanningAgent(config, maxSteps);\n\n  async function execute(\n    goal: string,\n    options?: PlannerExecuteOptions<TOutput>,\n  ): Promise<PlannerResult<TOutput>> {\n    log.debug(LOG_MODULE, \"execute\", \"Planner run starting\", {\n      name: config.name,\n      capabilities: capabilities.size,\n    });\n\n    return new PlannerRun<TOutput>({\n      config,\n      capabilities,\n      maxSteps,\n      signature,\n      planningAgent,\n      goal,\n      options,\n    }).run();\n  }\n\n  async function resume(\n    runId: string,\n    options?: PlannerResumeOptions<TOutput>,\n  ): Promise<PlannerResult<TOutput>> {\n    // Load the persisted snapshot and run the drift check (throws\n    // PlannerDriftError on a structural mismatch unless `{ force: true }`).\n    const snapshot = await loadPlannerSnapshotForResume({\n      durable: config.durable,\n      plannerName: config.name,\n      signature,\n      runId,\n      options: options as PlannerResumeOptions<unknown> | undefined,\n    });\n\n    return new PlannerRun<TOutput>({\n      config,\n      capabilities,\n      maxSteps,\n      signature,\n      planningAgent,\n      goal: snapshot.goal,\n      options: { ...options, runId } as PlannerExecuteOptions<TOutput>,\n      resumeFrom: snapshot,\n    }).run();\n  }\n\n  return {\n    name: config.name,\n    signature,\n    execute,\n    resume,\n  };\n}\n\n/**\n * Resolve the plan-generation agent: either adopt the dev's `planner`\n * agent, or build an internal one from `model` with the generated\n * plan-system-prompt baked on. The plan output schema is supplied\n * per-call in {@link PlannerRun}, so it isn't baked here.\n *\n * **`maxSteps` and BYO planners.** In `model` mode the cap is woven\n * into the generated plan-system-prompt *and* the per-call plan schema\n * (`steps.maxItems`). In `planner` (BYO) mode the dev owns the prompt,\n * so the cap is communicated only through that same per-call schema —\n * and, regardless of mode, {@link PlannerRun} truncates any over-long\n * plan to `skipped` at execution time, so the cap is always enforced.\n */\nfunction resolvePlanningAgent<TOutput>(\n  config: PlannerConfig<TOutput>,\n  maxSteps: number,\n): AgentContract<unknown> {\n  if (config.planner) {\n    return config.planner;\n  }\n\n  const systemPrompt = buildPlanSystemPrompt(\n    config.capabilities,\n    maxSteps,\n    config.systemPrompt,\n    config.dag === true,\n  );\n\n  return agent({\n    name: `${config.name}-planner`,\n    description: \"Generates an ordered execution plan over the planner's capabilities.\",\n    model: config.model!,\n    systemPrompt,\n    maxTrips: 1,\n  });\n}\n\n/**\n * Factory-time validation. Surfaces every violation as a typed\n * {@link PlannerFailedError} tagged `authoring: true`, mirroring the\n * supervisor/orchestrator authoring-error convention.\n */\nfunction validateConfig<TOutput>(config: PlannerConfig<TOutput>): void {\n  if (!config.name || typeof config.name !== \"string\") {\n    throw new PlannerFailedError(\"ai.planner: `name` is required and must be a string\", {\n      context: { authoring: true },\n    });\n  }\n\n  const hasModel = config.model !== undefined;\n  const hasPlanner = config.planner !== undefined;\n\n  if (!hasModel && !hasPlanner) {\n    throw new PlannerFailedError(\n      `ai.planner(\"${config.name}\"): one of \\`model\\` or \\`planner\\` is required`,\n      { context: { authoring: true } },\n    );\n  }\n\n  if (hasModel && hasPlanner) {\n    throw new PlannerFailedError(\n      `ai.planner(\"${config.name}\"): \\`model\\` and \\`planner\\` are mutually exclusive — configure exactly one`,\n      { context: { authoring: true } },\n    );\n  }\n\n  if (!Array.isArray(config.capabilities) || config.capabilities.length === 0) {\n    throw new PlannerFailedError(\n      `ai.planner(\"${config.name}\"): at least one capability is required`,\n      { context: { authoring: true } },\n    );\n  }\n\n  const seen = new Set<string>();\n\n  for (const capability of config.capabilities) {\n    if (!capability || typeof capability.name !== \"string\" || capability.name.length === 0) {\n      throw new PlannerFailedError(\n        `ai.planner(\"${config.name}\"): every capability needs a non-empty \\`name\\``,\n        { context: { authoring: true } },\n      );\n    }\n\n    if (typeof capability.description !== \"string\" || capability.description.length === 0) {\n      throw new PlannerFailedError(\n        `ai.planner(\"${config.name}\"): capability \"${capability.name}\" needs a \\`description\\``,\n        { context: { authoring: true } },\n      );\n    }\n\n    if (!capability.executable || typeof capability.executable.execute !== \"function\") {\n      throw new PlannerFailedError(\n        `ai.planner(\"${config.name}\"): capability \"${capability.name}\" needs an \\`executable\\` with an execute() method`,\n        { context: { authoring: true } },\n      );\n    }\n\n    if (seen.has(capability.name)) {\n      throw new PlannerFailedError(\n        `ai.planner(\"${config.name}\"): duplicate capability name \"${capability.name}\"`,\n        { context: { authoring: true } },\n      );\n    }\n\n    seen.add(capability.name);\n  }\n\n  if (config.maxSteps !== undefined && config.maxSteps < 1) {\n    throw new PlannerFailedError(`ai.planner(\"${config.name}\"): \\`maxSteps\\` must be >= 1`, {\n      context: { authoring: true, maxSteps: config.maxSteps },\n    });\n  }\n}\n"],"mappings":";;;;;;;;;;AAiBA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnB,SAAgB,QACd,QAC0B;CAC1B,eAAe,MAAM;CAErB,MAAM,WAAW,OAAO,YAAY;CACpC,MAAM,+BAAe,IAAI,IAA+B;CAExD,KAAK,MAAM,cAAc,OAAO,cAC9B,aAAa,IAAI,WAAW,MAAM,UAAU;CAG9C,MAAM,YAAY,iBAAiB,OAAO,MAAM,OAAO,YAAY;CACnE,MAAM,gBAAgB,qBAAqB,QAAQ,QAAQ;CAE3D,eAAe,QACb,MACA,SACiC;EACjC,IAAI,MAAM,YAAY,WAAW,wBAAwB;GACvD,MAAM,OAAO;GACb,cAAc,aAAa;EAC7B,CAAC;EAED,OAAO,IAAI,WAAoB;GAC7B;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,CAAC,IAAI;CACT;CAEA,eAAe,OACb,OACA,SACiC;EAGjC,MAAM,WAAW,MAAM,6BAA6B;GAClD,SAAS,OAAO;GAChB,aAAa,OAAO;GACpB;GACA;GACS;EACX,CAAC;EAED,OAAO,IAAI,WAAoB;GAC7B;GACA;GACA;GACA;GACA;GACA,MAAM,SAAS;GACf,SAAS;IAAE,GAAG;IAAS;GAAM;GAC7B,YAAY;EACd,CAAC,CAAC,CAAC,IAAI;CACT;CAEA,OAAO;EACL,MAAM,OAAO;EACb;EACA;EACA;CACF;AACF;;;;;;;;;;;;;;AAeA,SAAS,qBACP,QACA,UACwB;CACxB,IAAI,OAAO,SACT,OAAO,OAAO;CAGhB,MAAM,eAAe,sBACnB,OAAO,cACP,UACA,OAAO,cACP,OAAO,QAAQ,IACjB;CAEA,OAAO,MAAM;EACX,MAAM,GAAG,OAAO,KAAK;EACrB,aAAa;EACb,OAAO,OAAO;EACd;EACA,UAAU;CACZ,CAAC;AACH;;;;;;AAOA,SAAS,eAAwB,QAAsC;CACrE,IAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,SAAS,UACzC,MAAM,IAAI,mBAAmB,uDAAuD,EAClF,SAAS,EAAE,WAAW,KAAK,EAC7B,CAAC;CAGH,MAAM,WAAW,OAAO,UAAU;CAClC,MAAM,aAAa,OAAO,YAAY;CAEtC,IAAI,CAAC,YAAY,CAAC,YAChB,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,kDAC3B,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;CAGF,IAAI,YAAY,YACd,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,+EAC3B,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;CAGF,IAAI,CAAC,MAAM,QAAQ,OAAO,YAAY,KAAK,OAAO,aAAa,WAAW,GACxE,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,0CAC3B,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;CAGF,MAAM,uBAAO,IAAI,IAAY;CAE7B,KAAK,MAAM,cAAc,OAAO,cAAc;EAC5C,IAAI,CAAC,cAAc,OAAO,WAAW,SAAS,YAAY,WAAW,KAAK,WAAW,GACnF,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,kDAC3B,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;EAGF,IAAI,OAAO,WAAW,gBAAgB,YAAY,WAAW,YAAY,WAAW,GAClF,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,kBAAkB,WAAW,KAAK,4BAC7D,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;EAGF,IAAI,CAAC,WAAW,cAAc,OAAO,WAAW,WAAW,YAAY,YACrE,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,kBAAkB,WAAW,KAAK,qDAC7D,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;EAGF,IAAI,KAAK,IAAI,WAAW,IAAI,GAC1B,MAAM,IAAI,mBACR,eAAe,OAAO,KAAK,iCAAiC,WAAW,KAAK,IAC5E,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,CACjC;EAGF,KAAK,IAAI,WAAW,IAAI;CAC1B;CAEA,IAAI,OAAO,aAAa,UAAa,OAAO,WAAW,GACrD,MAAM,IAAI,mBAAmB,eAAe,OAAO,KAAK,gCAAgC,EACtF,SAAS;EAAE,WAAW;EAAM,UAAU,OAAO;CAAS,EACxD,CAAC;AAEL"}