{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/deferredSkills/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { Worker } from 'node:worker_threads';\n\nimport {\n  type BuildSystemPromptOptions,\n  formatSkillsForPrompt,\n  getAgentDir,\n  loadSkills,\n  type Skill,\n  stripFrontmatter,\n} from '@earendil-works/pi-coding-agent';\n\nexport interface DeferredSkillSnapshot {\n  skills: Skill[];\n  diagnostics: string[];\n}\n\nconst PI_PACKAGE = '@earendil-works/pi-coding-agent';\nconst PACKAGE_MANIFEST = 'package.json';\nconst SKILL_WORKER_SOURCE = `\nconst { parentPort, workerData } = require('node:worker_threads');\nvoid (async () => {\n  try {\n    const { loadSkills } = await import(workerData.piModuleUrl);\n    parentPort.postMessage({ ok: true, result: loadSkills(workerData.loadOptions) });\n  } catch (error) {\n    parentPort.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) });\n  }\n})();\n`;\n\nfunction installedPiModuleUrl(): string {\n  const anchors = [process.argv[1], fileURLToPath(import.meta.url), path.join(process.cwd(), PACKAGE_MANIFEST)];\n  for (const anchor of anchors) {\n    if (!anchor) continue;\n    let directory = path.dirname(path.resolve(anchor));\n    while (true) {\n      const packageRoot = path.join(directory, 'node_modules', PI_PACKAGE);\n      const manifestPath = path.join(packageRoot, PACKAGE_MANIFEST);\n      try {\n        const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as { main?: unknown };\n        if (typeof manifest.main === 'string') {\n          const entry = path.resolve(packageRoot, manifest.main);\n          if (fs.existsSync(entry)) return pathToFileURL(entry).href;\n        }\n      } catch {\n        // This anchor does not expose Pi at this level; continue up its module chain.\n      }\n      const parent = path.dirname(directory);\n      if (parent === directory) break;\n      directory = parent;\n    }\n  }\n  throw new Error(`Cannot resolve ${PI_PACKAGE} from the DoomPi or host module trees`);\n}\n\ninterface SkillWorkerResult {\n  ok: boolean;\n  result?: { skills: Skill[]; diagnostics: Array<{ path?: string; message: string }> };\n  error?: string;\n}\n\nexport interface DeferredSkillLoaderOptions {\n  cwd: string;\n  skillPaths: readonly string[];\n  agentDir?: string;\n  piModuleUrl?: string;\n  schedule?: (load: () => void) => void;\n  load?: (options: { cwd: string; agentDir: string; skillPaths: string[]; includeDefaults: boolean }) => {\n    skills: Skill[];\n    diagnostics: Array<{ path?: string; message: string }>;\n  };\n}\n\n/**\n * One session's Doom-selected skill inventory.\n *\n * Construction is deliberately cheap. `start()` schedules the synchronous Pi\n * walker after session_start returns, while `ready()` is the single promise the\n * first input awaits before Pi expands `/skill:name`.\n */\nexport class DeferredSkillLoader {\n  readonly #options: DeferredSkillLoaderOptions;\n  #promise: Promise<DeferredSkillSnapshot> | undefined;\n\n  constructor(options: DeferredSkillLoaderOptions) {\n    this.#options = options;\n  }\n\n  start(): Promise<DeferredSkillSnapshot> {\n    if (this.#promise) return this.#promise;\n\n    if (!this.#options.schedule && !this.#options.load) {\n      this.#promise = this.#loadInWorker();\n      return this.#promise;\n    }\n\n    const schedule = this.#options.schedule ?? ((load: () => void) => setImmediate(load));\n    this.#promise = new Promise((resolve) => {\n      const fail = (error: unknown): void => {\n        resolve(this.#failure(error));\n      };\n      try {\n        schedule(() => {\n          try {\n            resolve(this.#load());\n          } catch (error) {\n            fail(error);\n          }\n        });\n      } catch (error) {\n        fail(error);\n      }\n    });\n    return this.#promise;\n  }\n\n  ready(): Promise<DeferredSkillSnapshot> {\n    return this.start();\n  }\n\n  #loadOptions(): { cwd: string; agentDir: string; skillPaths: string[]; includeDefaults: boolean } {\n    return {\n      cwd: this.#options.cwd,\n      agentDir: this.#options.agentDir ?? getAgentDir(),\n      skillPaths: [...this.#options.skillPaths],\n      includeDefaults: false,\n    };\n  }\n\n  #snapshot(result: {\n    skills: Skill[];\n    diagnostics: Array<{ path?: string; message: string }>;\n  }): DeferredSkillSnapshot {\n    return {\n      skills: result.skills,\n      diagnostics: result.diagnostics.map(\n        (diagnostic) => `${diagnostic.path ?? this.#options.cwd}: ${diagnostic.message}`,\n      ),\n    };\n  }\n\n  #failure(error: unknown): DeferredSkillSnapshot {\n    return {\n      skills: [],\n      diagnostics: [`${this.#options.cwd}: ${error instanceof Error ? error.message : String(error)}`],\n    };\n  }\n\n  #load(): DeferredSkillSnapshot {\n    return this.#snapshot((this.#options.load ?? loadSkills)(this.#loadOptions()));\n  }\n\n  #loadInWorker(): Promise<DeferredSkillSnapshot> {\n    return new Promise((resolve) => {\n      let settled = false;\n      const settle = (snapshot: DeferredSkillSnapshot): void => {\n        if (settled) return;\n        settled = true;\n        resolve(snapshot);\n      };\n      let worker: Worker;\n      try {\n        worker = new Worker(SKILL_WORKER_SOURCE, {\n          eval: true,\n          workerData: {\n            piModuleUrl: this.#options.piModuleUrl ?? installedPiModuleUrl(),\n            loadOptions: this.#loadOptions(),\n          },\n        });\n      } catch (error) {\n        settle(this.#failure(error));\n        return;\n      }\n      worker.unref();\n      worker.once('message', (message: SkillWorkerResult) => {\n        if (!message.ok || !message.result) {\n          settle(this.#failure(message.error ?? 'Skill worker failed'));\n          return;\n        }\n        settle(this.#snapshot(message.result));\n      });\n      worker.once('error', (error) => settle(this.#failure(error)));\n      worker.once('exit', (code) => {\n        if (code !== 0) settle(this.#failure(`Skill worker exited with code ${code}`));\n      });\n    });\n  }\n}\n\n/** Appends only the inventory Pi would append for these deferred skills. */\nexport function buildPromptWithDeferredSkills(\n  systemPrompt: string,\n  options: BuildSystemPromptOptions,\n  skills: Skill[],\n): string {\n  if (options.selectedTools && !options.selectedTools.includes('read')) return systemPrompt;\n  return `${systemPrompt}${formatSkillsForPrompt(skills)}`;\n}\n\n/** Expands a deferred `/skill:name` before Pi consults its synchronous inventory. */\nexport function expandDeferredSkillCommand(text: string, skills: readonly Skill[]): string {\n  if (!text.startsWith('/skill:')) return text;\n  const spaceIndex = text.indexOf(' ');\n  const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n  const skill = skills.find((candidate) => candidate.name === skillName);\n  if (!skill) return text;\n\n  try {\n    const args = spaceIndex === -1 ? '' : text.slice(spaceIndex + 1).trim();\n    const body = stripFrontmatter(fs.readFileSync(skill.filePath, 'utf8')).trim();\n    const skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n    return args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n  } catch {\n    // Pi uses the same literal-text fallback if a skill disappears between\n    // discovery and submission; the agent can still respond to the command.\n    return text;\n  }\n}\n"],"mappings":"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"}