{"version":3,"file":"save-skill-tool.mjs","names":[],"sources":["../../../../../../../ai/src/skills/save-skill-tool.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ToolContract } from \"../tool/tool\";\nimport { tool } from \"../tool/tool\";\nimport type { SkillRecord } from \"./contracts/skill-record.type\";\n\n/** Validated input of the `saveSkill` tool. */\nexport type SaveSkillInput = {\n  name: string;\n  description: string;\n  body: string;\n  tags?: string[];\n};\n\n/** Result the `saveSkill` tool feeds back to the model. */\nexport type SaveSkillResult =\n  | { saved: true; name: string; status: \"candidate\" }\n  | { error: string };\n\n/**\n * Hand-built Standard Schema for the `saveSkill` input — dependency-free,\n * mirroring `loadSkillSchema`. Requires `name` / `description` / `body`;\n * `tags` is an optional string array.\n */\nfunction saveSkillSchema(): StandardSchemaV1<SaveSkillInput> {\n  return {\n    \"~standard\": {\n      version: 1,\n      vendor: \"warlock-ai-skills\",\n      validate: (value: unknown) => {\n        const candidate = value as\n          | { name?: unknown; description?: unknown; body?: unknown; tags?: unknown }\n          | null;\n\n        if (\n          !candidate ||\n          typeof candidate.name !== \"string\" ||\n          typeof candidate.description !== \"string\" ||\n          typeof candidate.body !== \"string\"\n        ) {\n          return {\n            issues: [\n              {\n                message:\n                  \"saveSkill input must be { name: string; description: string; body: string; tags?: string[] }\",\n              },\n            ],\n          };\n        }\n\n        if (\n          candidate.tags !== undefined &&\n          (!Array.isArray(candidate.tags) ||\n            !candidate.tags.every((tag) => typeof tag === \"string\"))\n        ) {\n          return { issues: [{ message: \"saveSkill `tags` must be a string[] when provided\" }] };\n        }\n\n        return {\n          value: {\n            name: candidate.name,\n            description: candidate.description,\n            body: candidate.body,\n            ...(candidate.tags !== undefined ? { tags: candidate.tags as string[] } : {}),\n          },\n        };\n      },\n    },\n  };\n}\n\n/** Dependencies the `saveSkill` tool closes over. */\nexport type SaveSkillToolDeps = {\n  /** Write an INERT candidate; the returned record is `type: \"candidate\"`. */\n  saveCandidate: (record: Omit<SkillRecord, \"version\" | \"type\">) => Promise<SkillRecord>;\n  /** Fired on a successful save (`type: \"saved\"`); errors swallowed by the sink wrapper. */\n  onSaved?: (record: SkillRecord) => void;\n};\n\n/**\n * Build the **Phase 2** `saveSkill` tool — exposed ONLY when a `review`\n * gate is configured. It writes an INERT `type: \"candidate\"` record via\n * `saveCandidate`. A candidate is filtered out of the catalog and from\n * preload — it can NEVER be injected until the default-DENY review gate\n * promotes it. So `saveSkill` alone can never turn the model's own output\n * into an injected instruction; promotion is a separate, gated step.\n *\n * `execute` never throws — a store write failure surfaces as an error\n * result so the run continues.\n */\nexport function saveSkillTool(deps: SaveSkillToolDeps): ToolContract<SaveSkillInput, SaveSkillResult> {\n  return tool<SaveSkillInput, SaveSkillResult>({\n    name: \"saveSkill\",\n    description:\n      \"Propose a new reusable skill. The skill is saved as an INERT candidate and is NOT used until a reviewer approves it — it will not affect the current run.\",\n    input: saveSkillSchema(),\n    execute: async (input) => {\n      try {\n        const record = await deps.saveCandidate({\n          name: input.name,\n          description: input.description,\n          body: input.body,\n          ...(input.tags !== undefined ? { tags: input.tags } : {}),\n        });\n\n        deps.onSaved?.(record);\n\n        return { saved: true, name: record.name, status: \"candidate\" };\n      } catch (error) {\n        return { error: error instanceof Error ? error.message : String(error) };\n      }\n    },\n  });\n}\n"],"mappings":";;;;;;;;AAuBA,SAAS,kBAAoD;CAC3D,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,WAAW,UAAmB;GAC5B,MAAM,YAAY;GAIlB,IACE,CAAC,aACD,OAAO,UAAU,SAAS,YAC1B,OAAO,UAAU,gBAAgB,YACjC,OAAO,UAAU,SAAS,UAE1B,OAAO,EACL,QAAQ,CACN,EACE,SACE,+FACJ,CACF,EACF;GAGF,IACE,UAAU,SAAS,WAClB,CAAC,MAAM,QAAQ,UAAU,IAAI,KAC5B,CAAC,UAAU,KAAK,OAAO,QAAQ,OAAO,QAAQ,QAAQ,IAExD,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,oDAAoD,CAAC,EAAE;GAGtF,OAAO,EACL,OAAO;IACL,MAAM,UAAU;IAChB,aAAa,UAAU;IACvB,MAAM,UAAU;IAChB,GAAI,UAAU,SAAS,SAAY,EAAE,MAAM,UAAU,KAAiB,IAAI,CAAC;GAC7E,EACF;EACF;CACF,EACF;AACF;;;;;;;;;;;;AAqBA,SAAgB,cAAc,MAAwE;CACpG,OAAO,KAAsC;EAC3C,MAAM;EACN,aACE;EACF,OAAO,gBAAgB;EACvB,SAAS,OAAO,UAAU;GACxB,IAAI;IACF,MAAM,SAAS,MAAM,KAAK,cAAc;KACtC,MAAM,MAAM;KACZ,aAAa,MAAM;KACnB,MAAM,MAAM;KACZ,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;IACzD,CAAC;IAED,KAAK,UAAU,MAAM;IAErB,OAAO;KAAE,OAAO;KAAM,MAAM,OAAO;KAAM,QAAQ;IAAY;GAC/D,SAAS,OAAO;IACd,OAAO,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;GACzE;EACF;CACF,CAAC;AACH"}