{"version":3,"file":"skills.mjs","names":[],"sources":["../../src/schemas/skills.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport { Type } from 'typebox';\nimport { Check, Errors } from 'typebox/value';\n\n/** Provider-owned Cordis service for live skill-directory contributions. */\nexport const DOOM_SKILL_SOURCES_SERVICE = 'doom/skill-sources';\n\n/**\n * The custom session entry journaled once Pi has rebuilt its resource catalog\n * for a reload.\n *\n * A reload is the only thing that changes which skills exist, and Pi publishes\n * nothing for it: the whole wire record of a `/domains` switch is the single\n * `response:prompt` for the command that triggered it. An RPC client therefore\n * has no way to learn that the `get_commands` answer it cached, which is what\n * the web cockpit completes `$` from, now describes the previous domains.\n *\n * This entry is that missing signal. It rides Pi's `entry_appended` frames the\n * way the minor-mode catalog projection does, so it needs no new protocol, and\n * a custom entry with no registered renderer is invisible in the TUI.\n */\nexport const DOOM_RESOURCE_CATALOG_ENTRY_TYPE = 'doom-resource-catalog';\n\n/**\n * The payload of a resource-catalog entry.\n *\n * Arrival is the signal; a client re-reads the catalog rather than diffing\n * this. The revision only distinguishes one rebuild from the next in a journal\n * a human is reading.\n */\nexport interface ResourceCatalogProjection {\n  readonly version: 1;\n  readonly revision: number;\n}\n\nconst MAX_GENERATION_LENGTH = 256;\n\nexport const SkillSourceNameSchema = Type.String({\n  minLength: 1,\n  maxLength: 128,\n  pattern: '^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$',\n});\n\nexport const SkillSourceContributionSchema = Type.Object(\n  {\n    source: SkillSourceNameSchema,\n    /** Absolute directories, the same ones the source hands `resources_discover`. */\n    directories: Type.Array(Type.String({ minLength: 1 })),\n  },\n  { additionalProperties: false },\n);\n\nexport interface SkillSourceContribution {\n  readonly source: string;\n  readonly directories: readonly string[];\n}\n\nexport interface SkillSourceContributionHandle {\n  readonly source: string;\n  readonly generation: string;\n  dispose(): void;\n}\n\n/** Direct registrar/query/subscription seam owned by doompi-skill. */\nexport interface DoomSkillSourcesService {\n  readonly generation: string;\n  register(contribution: SkillSourceContribution): SkillSourceContributionHandle;\n  list(): readonly SkillSourceContribution[];\n  subscribe(listener: (contributions: readonly SkillSourceContribution[]) => void): () => void;\n  dispose(): void;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n  interface Context {\n    'doom/skill-sources': DoomSkillSourcesService;\n  }\n}\n\ninterface RegisteredSkillSource {\n  readonly token: symbol;\n  readonly contribution: SkillSourceContribution;\n}\n\nfunction cloneContribution(contribution: SkillSourceContribution): SkillSourceContribution {\n  return { source: contribution.source, directories: [...contribution.directories] };\n}\n\nfunction validateContribution(contribution: SkillSourceContribution): SkillSourceContribution {\n  if (Check(SkillSourceContributionSchema, contribution)) return cloneContribution(contribution);\n  const [first] = Errors(SkillSourceContributionSchema, contribution);\n  throw new TypeError(\n    `Invalid Doom skill-source contribution${first ? ` at ${first.instancePath || '/'}: ${first.message}` : ''}.`,\n  );\n}\n\n/** Creates the provider-owned skill-source registry for one Cordis session. */\nexport function createDoomSkillSourcesService(generation: string): DoomSkillSourcesService {\n  if (generation.length === 0 || generation.length > MAX_GENERATION_LENGTH) {\n    throw new TypeError('Doom skill sources require a valid generation.');\n  }\n\n  const registrations = new Map<string, RegisteredSkillSource>();\n  const listeners = new Set<(contributions: readonly SkillSourceContribution[]) => void>();\n  let sequence = 0;\n  let disposed = false;\n\n  const ensureActive = (): void => {\n    if (disposed) throw new Error('The Doom skill-sources service is disposed.');\n  };\n  const list = (): readonly SkillSourceContribution[] =>\n    [...registrations.values()]\n      .map(({ contribution }) => cloneContribution(contribution))\n      .sort((left, right) => left.source.localeCompare(right.source));\n  const notify = (): void => {\n    const contributions = list();\n    for (const listener of listeners) listener(contributions);\n  };\n\n  const service: DoomSkillSourcesService = {\n    generation,\n    register(contribution) {\n      ensureActive();\n      const validated = validateContribution(contribution);\n      sequence += 1;\n      const token = Symbol(validated.source);\n      const registrationGeneration = `${generation}:skill-source:${sequence}`;\n      registrations.set(validated.source, { token, contribution: validated });\n      notify();\n      let registrationDisposed = false;\n      return Object.freeze({\n        source: validated.source,\n        generation: registrationGeneration,\n        dispose() {\n          if (registrationDisposed) return;\n          registrationDisposed = true;\n          if (registrations.get(validated.source)?.token !== token) return;\n          registrations.delete(validated.source);\n          notify();\n        },\n      });\n    },\n    list,\n    subscribe(listener) {\n      ensureActive();\n      listeners.add(listener);\n      return () => listeners.delete(listener);\n    },\n    dispose() {\n      if (disposed) return;\n      disposed = true;\n      registrations.clear();\n      listeners.clear();\n    },\n  };\n  return Object.freeze(service);\n}\n\nexport function readDoomSkillSourcesService(context: Context): DoomSkillSourcesService | undefined {\n  return context.get(DOOM_SKILL_SOURCES_SERVICE) as DoomSkillSourcesService | undefined;\n}\n\nexport function requireDoomSkillSourcesService(context: Context): DoomSkillSourcesService {\n  const service = readDoomSkillSourcesService(context);\n  if (!service) throw new Error('Doom skill sources are unavailable. Load @agimon-ai/doompi-skill.');\n  return service;\n}\n"],"mappings":"iFAKA,MAAa,EAA6B,qBAgB7B,EAAmC,wBAgBnC,EAAwB,EAAK,OAAO,CAC/C,UAAW,EACX,UAAW,IACX,QAAS,kCACX,CAAC,EAEY,EAAgC,EAAK,OAChD,CACE,OAAQ,EAER,YAAa,EAAK,MAAM,EAAK,OAAO,CAAE,UAAW,CAAE,CAAC,CAAC,CACvD,EACA,CAAE,qBAAsB,EAAM,CAChC,EAiCA,SAAS,EAAkB,EAAgE,CACzF,MAAO,CAAE,OAAQ,EAAa,OAAQ,YAAa,CAAC,GAAG,EAAa,WAAW,CAAE,CACnF,CAEA,SAAS,EAAqB,EAAgE,CAC5F,GAAI,EAAM,EAA+B,CAAY,EAAG,OAAO,EAAkB,CAAY,EAC7F,GAAM,CAAC,GAAS,EAAO,EAA+B,CAAY,EAClE,MAAU,UACR,yCAAyC,EAAQ,OAAO,EAAM,cAAgB,IAAI,IAAI,EAAM,UAAY,GAAG,EAC7G,CACF,CAGA,SAAgB,EAA8B,EAA6C,CACzF,GAAI,EAAW,SAAW,GAAK,EAAW,OAAS,IACjD,MAAU,UAAU,gDAAgD,EAGtE,IAAM,EAAgB,IAAI,IACpB,EAAY,IAAI,IAClB,EAAW,EACX,EAAW,GAET,MAA2B,CAC/B,GAAI,EAAU,MAAU,MAAM,6CAA6C,CAC7E,EACM,MACJ,CAAC,GAAG,EAAc,OAAO,CAAC,CAAC,CACxB,KAAK,CAAE,kBAAmB,EAAkB,CAAY,CAAC,CAAC,CAC1D,MAAM,EAAM,IAAU,EAAK,OAAO,cAAc,EAAM,MAAM,CAAC,EAC5D,MAAqB,CACzB,IAAM,EAAgB,EAAK,EAC3B,IAAK,IAAM,KAAY,EAAW,EAAS,CAAa,CAC1D,EAsCA,OAAO,OAAO,OAAO,CAnCnB,aACA,SAAS,EAAc,CACrB,EAAa,EACb,IAAM,EAAY,EAAqB,CAAY,EACnD,GAAY,EACZ,IAAM,EAAQ,OAAO,EAAU,MAAM,EAC/B,EAAyB,GAAG,EAAW,gBAAgB,IAC7D,EAAc,IAAI,EAAU,OAAQ,CAAE,QAAO,aAAc,CAAU,CAAC,EACtE,EAAO,EACP,IAAI,EAAuB,GAC3B,OAAO,OAAO,OAAO,CACnB,OAAQ,EAAU,OAClB,WAAY,EACZ,SAAU,CACJ,IACJ,EAAuB,GACnB,EAAc,IAAI,EAAU,MAAM,CAAC,EAAE,QAAU,IACnD,EAAc,OAAO,EAAU,MAAM,EACrC,EAAO,GACT,CACF,CAAC,CACH,EACA,OACA,UAAU,EAAU,CAGlB,OAFA,EAAa,EACb,EAAU,IAAI,CAAQ,MACT,EAAU,OAAO,CAAQ,CACxC,EACA,SAAU,CACJ,IACJ,EAAW,GACX,EAAc,MAAM,EACpB,EAAU,MAAM,EAClB,CAEyB,CAAC,CAC9B,CAEA,SAAgB,EAA4B,EAAuD,CACjG,OAAO,EAAQ,IAAI,CAA0B,CAC/C,CAEA,SAAgB,EAA+B,EAA2C,CACxF,IAAM,EAAU,EAA4B,CAAO,EACnD,GAAI,CAAC,EAAS,MAAU,MAAM,mEAAmE,EACjG,OAAO,CACT"}