@agimon-ai/po-skill 0.0.0-alpha.2 → 0.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/catalog.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
3
- let node_fs = require("node:fs");
4
- node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
5
3
  let node_path = require("node:path");
6
4
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
7
5
  let _agimon_ai_po_config_domains = require("@agimon-ai/po-config/domains");
6
+ let _agimon_ai_po_core_nodePlatform = require("@agimon-ai/po-core/nodePlatform");
7
+ let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
8
8
  let _agimon_ai_po_domain_plugins = require("@agimon-ai/po-domain/plugins");
9
9
  let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
10
10
  //#region src/services/skillCatalog/index.ts
@@ -116,11 +116,14 @@ async function buildSkillCatalog(options) {
116
116
  skills
117
117
  });
118
118
  }
119
+ const fs = (0, _agimon_ai_po_core_nodePlatform.createNodePlatform)().env(repoRoot);
120
+ const context = _agimon_ai_po_core_platform.BACKGROUND_CONTEXT;
119
121
  const pluginOwners = [];
120
- const domainPlugins = await (0, _agimon_ai_po_domain_plugins.materializePluginEntries)((0, _agimon_ai_po_config_domains.resolvePluginEntries)(repoRoot, (0, _agimon_ai_po_config_domains.listDomainNames)(repoRoot), []));
122
+ const domainPlugins = await (0, _agimon_ai_po_domain_plugins.materializePluginEntries)(await (0, _agimon_ai_po_config_domains.resolvePluginEntriesAsync)(fs, repoRoot, await (0, _agimon_ai_po_config_domains.listDomainNamesAsync)(fs, repoRoot, context), [], context));
121
123
  for (const entry of domainPlugins) {
122
124
  const directory = node_path.default.join(entry.directory, SKILLS_DIRECTORY);
123
- if (!node_fs.default.existsSync(directory)) continue;
125
+ const present = await fs.exists(directory, context);
126
+ if (!present.ok || !present.value) continue;
124
127
  const owner = node_path.default.basename(entry.directory);
125
128
  const skills = collect(directory, "plugins", owner, isActive, loaded, diagnostics, entry.skillDiscovery);
126
129
  if (skills.length > 0) pluginOwners.push({
@@ -167,7 +170,7 @@ async function buildSkillCatalog(options) {
167
170
  }
168
171
  let bodyTokens = 0;
169
172
  for (const skill of loaded) try {
170
- bodyTokens += countTokens(node_fs.default.readFileSync(skill.filePath, "utf8"));
173
+ bodyTokens += countTokens((0, _agimon_ai_po_core_platform.getOrThrow)(await fs.readTextFile(skill.filePath, context)));
171
174
  } catch (error) {
172
175
  diagnostics.push(`${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);
173
176
  }
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.cjs","names":["path","loadSkillsFromDir","materializePluginEntries","resolvePluginEntries","listDomainNames","fs","formatSkillsForPrompt"],"sources":["../src/services/skillCatalog/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { listDomainNames, type PluginSkillDiscovery, resolvePluginEntries } from '@agimon-ai/po-config/domains';\nimport type { SkillSourceContribution } from '@agimon-ai/po-core/skills';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\n\nconst SKILLS_DIRECTORY = 'skills';\nconst SHARED_SKILLS_OWNER = '.claude/skills';\n\nexport type SkillGroupKey = 'extensions' | 'help' | 'plugins' | 'default';\n\nexport interface SkillEntry {\n name: string;\n description: string;\n filePath: string;\n baseDir: string;\n group: SkillGroupKey;\n /** Package name for an extension, plugin directory name, or `.claude/skills`. */\n owner: string;\n modelInvocable: boolean;\n /**\n * What removing this skill would save from the `<available_skills>` block.\n *\n * Marginal rather than proportional: the block has fixed framing that no\n * single skill pays for, so the useful figure is what actually goes away.\n * Bodies are excluded because they load on demand and are not a resting cost.\n */\n promptTokens?: number;\n}\n\nexport interface SkillOwnerNode {\n owner: string;\n skills: SkillEntry[];\n}\n\nexport interface SkillGroup {\n key: SkillGroupKey;\n label: string;\n owners: SkillOwnerNode[];\n}\n\nexport interface SkillCatalog {\n groups: SkillGroup[];\n skillCount: number;\n /** Tokens the `<available_skills>` prompt block costs on every request. */\n promptTokens: number;\n /** Tokens every loaded SKILL.md costs in full, if all of them were read. */\n bodyTokens: number;\n /** Frontmatter and read failures, surfaced instead of thrown. */\n diagnostics: string[];\n}\n\nexport interface SkillCatalogOptions {\n repoRoot: string;\n /** Exact files the current selection resolved to, from `HarnessState.skillDirectories`. */\n activeSkillDirectories: readonly string[];\n extensionSources: readonly SkillSourceContribution[];\n /** Activation-gated wrappers already accepted by the merged active-skill view. */\n helpSkills?: readonly Skill[];\n /** Bounded Help activation and collision diagnostics. */\n helpDiagnostics?: readonly string[];\n}\n\n/**\n * The tokenizer, loaded on first use rather than at import.\n *\n * `gpt-tokenizer` carries its whole vocabulary and costs about a tenth of a\n * second to load. This module is reachable from the skills extension, which Pi\n * loads at session start, so a top-level import would charge every session for\n * a surface most of them never open.\n */\nlet tokenizer: Promise<typeof import('gpt-tokenizer')> | undefined;\n\nexport async function counter(): Promise<(text: string) => number> {\n tokenizer ??= import('gpt-tokenizer');\n return (await tokenizer).countTokens;\n}\n\n/**\n * Matches a directory against the active set.\n *\n * A domain that lists a plugin subset puts individual skill directories in the\n * set rather than the plugin's `skills` root, so containment has to be checked\n * both ways: the set may hold an ancestor of the directory, or a descendant of\n * it.\n */\nfunction createActiveMatcher(directories: readonly string[]): (directory: string) => boolean {\n const active = directories.map((directory) => path.resolve(directory));\n return (directory) => {\n const resolved = path.resolve(directory);\n return active.some(\n (entry) =>\n entry === resolved || entry.startsWith(`${resolved}${path.sep}`) || resolved.startsWith(`${entry}${path.sep}`),\n );\n };\n}\n\nfunction collect(\n directory: string,\n group: SkillGroupKey,\n owner: string,\n isActive: (directory: string) => boolean,\n loaded: Skill[],\n diagnostics: string[],\n discovery: PluginSkillDiscovery = 'recursive',\n): SkillEntry[] {\n const result = loadSkillsFromDir({ dir: directory, source: owner });\n for (const diagnostic of result.diagnostics) {\n diagnostics.push(`${diagnostic.path ?? directory}: ${diagnostic.message}`);\n }\n // Checked per skill, not per root: a subset domain selects some of a plugin's\n // skills and leaves the rest on disk, unloaded and therefore not listed.\n const root = path.resolve(directory);\n const kept = result.skills.filter((skill) => {\n const skillDirectory = path.dirname(skill.filePath);\n if (discovery === 'direct-children' && path.dirname(skillDirectory) !== root) return false;\n return isActive(skillDirectory);\n });\n // Kept in their loaded form as well, because the prompt-block cost is measured\n // by handing them back to Pi's own formatter.\n loaded.push(...kept);\n return kept\n .map((skill) => ({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group,\n owner,\n modelInvocable: !skill.disableModelInvocation,\n }))\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction sortOwners(owners: SkillOwnerNode[]): SkillOwnerNode[] {\n return owners.sort((left, right) => left.owner.localeCompare(right.owner));\n}\n\nfunction collectHelpSkills(skills: readonly Skill[], loaded: Skill[], diagnostics: string[]): SkillOwnerNode[] {\n const occupiedNames = new Set(loaded.map((skill) => skill.name));\n const owners = new Map<string, SkillEntry[]>();\n const candidates = [...skills].sort(\n (left, right) =>\n left.sourceInfo.source.localeCompare(right.sourceInfo.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n for (const skill of candidates) {\n if (occupiedNames.has(skill.name)) {\n diagnostics.push(\n `Help ${skill.sourceInfo.source} [HELP_SKILL_COLLISION]: '${skill.name}' was ignored because a normal skill wins.`,\n );\n continue;\n }\n occupiedNames.add(skill.name);\n loaded.push(skill);\n const owner = skill.sourceInfo.source;\n const entries = owners.get(owner) ?? [];\n entries.push({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group: 'help',\n owner,\n modelInvocable: !skill.disableModelInvocation,\n });\n owners.set(owner, entries);\n }\n return sortOwners(\n [...owners.entries()].map(([owner, entries]) => ({\n owner,\n skills: entries.sort((left, right) => left.name.localeCompare(right.name)),\n })),\n );\n}\n\n/**\n * Every skill this session actually loaded, grouped by where it comes from,\n * priced in tokens.\n *\n * Discovery runs over `loadSkillsFromDir` from Pi rather than the harness's own\n * walker so the listing matches what Pi would load, including its ignore-file\n * handling; the same skills are then handed to Pi's `formatSkillsForPrompt` so\n * the always-on figure is the real prompt block rather than a reconstruction of\n * it.\n */\nexport async function buildSkillCatalog(options: SkillCatalogOptions): Promise<SkillCatalog> {\n const { repoRoot } = options;\n const isActive = createActiveMatcher(options.activeSkillDirectories);\n const diagnostics: string[] = [...(options.helpDiagnostics ?? [])];\n const loaded: Skill[] = [];\n\n const extensionOwners: SkillOwnerNode[] = [];\n for (const entry of options.extensionSources) {\n const skills = entry.directories.flatMap((directory) =>\n collect(directory, 'extensions', entry.source, () => true, loaded, diagnostics),\n );\n if (skills.length > 0) extensionOwners.push({ owner: entry.source, skills });\n }\n\n const pluginOwners: SkillOwnerNode[] = [];\n const domainPlugins = await materializePluginEntries(resolvePluginEntries(repoRoot, listDomainNames(repoRoot), []));\n for (const entry of domainPlugins) {\n const directory = path.join(entry.directory, SKILLS_DIRECTORY);\n if (!fs.existsSync(directory)) continue;\n const owner = path.basename(entry.directory);\n const skills = collect(directory, 'plugins', owner, isActive, loaded, diagnostics, entry.skillDiscovery);\n if (skills.length > 0) pluginOwners.push({ owner, skills });\n }\n\n const sharedDirectory = path.join(repoRoot, '.claude', SKILLS_DIRECTORY);\n const sharedSkills = collect(sharedDirectory, 'default', SHARED_SKILLS_OWNER, isActive, loaded, diagnostics);\n const defaultOwners = sharedSkills.length > 0 ? [{ owner: SHARED_SKILLS_OWNER, skills: sharedSkills }] : [];\n const helpOwners = collectHelpSkills(options.helpSkills ?? [], loaded, diagnostics);\n\n const groups: SkillGroup[] = [\n { key: 'extensions', label: 'extensions', owners: sortOwners(extensionOwners) },\n { key: 'help', label: 'Help', owners: helpOwners },\n { key: 'plugins', label: 'plugins', owners: sortOwners(pluginOwners) },\n { key: 'default', label: 'default', owners: defaultOwners },\n ];\n\n const countTokens = await counter();\n const prompt = formatSkillsForPrompt(loaded);\n const framingTokens = countTokens(formatSkillsForPrompt([]));\n const marginal = new Map<string, number>();\n for (const skill of loaded) {\n marginal.set(skill.name, Math.max(0, countTokens(formatSkillsForPrompt([skill])) - framingTokens));\n }\n for (const group of groups) {\n for (const owner of group.owners) {\n for (const skill of owner.skills) {\n const tokens = marginal.get(skill.name);\n if (tokens !== undefined) skill.promptTokens = tokens;\n }\n }\n }\n let bodyTokens = 0;\n for (const skill of loaded) {\n try {\n bodyTokens += countTokens(fs.readFileSync(skill.filePath, 'utf8'));\n } catch (error) {\n diagnostics.push(`${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n return {\n groups,\n skillCount: loaded.length,\n promptTokens: prompt ? countTokens(prompt) : 0,\n bodyTokens,\n diagnostics,\n };\n}\n\n/** The skills a name resolves to, used to decide what `enter` can invoke. */\nexport function findSkill(catalog: SkillCatalog, name: string): SkillEntry | undefined {\n for (const group of catalog.groups) {\n for (const owner of group.owners) {\n const match = owner.skills.find((skill) => skill.name === name);\n if (match) return match;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"catalog.cjs","names":["path","loadSkillsFromDir","createNodePlatform","BACKGROUND_CONTEXT","materializePluginEntries","resolvePluginEntriesAsync","listDomainNamesAsync","formatSkillsForPrompt","getOrThrow"],"sources":["../src/services/skillCatalog/index.ts"],"sourcesContent":["import path from 'node:path';\n\nimport {\n listDomainNamesAsync,\n type PluginSkillDiscovery,\n resolvePluginEntriesAsync,\n} from '@agimon-ai/po-config/domains';\nimport { createNodePlatform } from '@agimon-ai/po-core/nodePlatform';\nimport { BACKGROUND_CONTEXT, getOrThrow } from '@agimon-ai/po-core/platform';\nimport type { SkillSourceContribution } from '@agimon-ai/po-core/skills';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\n\nconst SKILLS_DIRECTORY = 'skills';\nconst SHARED_SKILLS_OWNER = '.claude/skills';\n\nexport type SkillGroupKey = 'extensions' | 'help' | 'plugins' | 'default';\n\nexport interface SkillEntry {\n name: string;\n description: string;\n filePath: string;\n baseDir: string;\n group: SkillGroupKey;\n /** Package name for an extension, plugin directory name, or `.claude/skills`. */\n owner: string;\n modelInvocable: boolean;\n /**\n * What removing this skill would save from the `<available_skills>` block.\n *\n * Marginal rather than proportional: the block has fixed framing that no\n * single skill pays for, so the useful figure is what actually goes away.\n * Bodies are excluded because they load on demand and are not a resting cost.\n */\n promptTokens?: number;\n}\n\nexport interface SkillOwnerNode {\n owner: string;\n skills: SkillEntry[];\n}\n\nexport interface SkillGroup {\n key: SkillGroupKey;\n label: string;\n owners: SkillOwnerNode[];\n}\n\nexport interface SkillCatalog {\n groups: SkillGroup[];\n skillCount: number;\n /** Tokens the `<available_skills>` prompt block costs on every request. */\n promptTokens: number;\n /** Tokens every loaded SKILL.md costs in full, if all of them were read. */\n bodyTokens: number;\n /** Frontmatter and read failures, surfaced instead of thrown. */\n diagnostics: string[];\n}\n\nexport interface SkillCatalogOptions {\n repoRoot: string;\n /** Exact files the current selection resolved to, from `HarnessState.skillDirectories`. */\n activeSkillDirectories: readonly string[];\n extensionSources: readonly SkillSourceContribution[];\n /** Activation-gated wrappers already accepted by the merged active-skill view. */\n helpSkills?: readonly Skill[];\n /** Bounded Help activation and collision diagnostics. */\n helpDiagnostics?: readonly string[];\n}\n\n/**\n * The tokenizer, loaded on first use rather than at import.\n *\n * `gpt-tokenizer` carries its whole vocabulary and costs about a tenth of a\n * second to load. This module is reachable from the skills extension, which Pi\n * loads at session start, so a top-level import would charge every session for\n * a surface most of them never open.\n */\nlet tokenizer: Promise<typeof import('gpt-tokenizer')> | undefined;\n\nexport async function counter(): Promise<(text: string) => number> {\n tokenizer ??= import('gpt-tokenizer');\n return (await tokenizer).countTokens;\n}\n\n/**\n * Matches a directory against the active set.\n *\n * A domain that lists a plugin subset puts individual skill directories in the\n * set rather than the plugin's `skills` root, so containment has to be checked\n * both ways: the set may hold an ancestor of the directory, or a descendant of\n * it.\n */\nfunction createActiveMatcher(directories: readonly string[]): (directory: string) => boolean {\n const active = directories.map((directory) => path.resolve(directory));\n return (directory) => {\n const resolved = path.resolve(directory);\n return active.some(\n (entry) =>\n entry === resolved || entry.startsWith(`${resolved}${path.sep}`) || resolved.startsWith(`${entry}${path.sep}`),\n );\n };\n}\n\nfunction collect(\n directory: string,\n group: SkillGroupKey,\n owner: string,\n isActive: (directory: string) => boolean,\n loaded: Skill[],\n diagnostics: string[],\n discovery: PluginSkillDiscovery = 'recursive',\n): SkillEntry[] {\n const result = loadSkillsFromDir({ dir: directory, source: owner });\n for (const diagnostic of result.diagnostics) {\n diagnostics.push(`${diagnostic.path ?? directory}: ${diagnostic.message}`);\n }\n // Checked per skill, not per root: a subset domain selects some of a plugin's\n // skills and leaves the rest on disk, unloaded and therefore not listed.\n const root = path.resolve(directory);\n const kept = result.skills.filter((skill) => {\n const skillDirectory = path.dirname(skill.filePath);\n if (discovery === 'direct-children' && path.dirname(skillDirectory) !== root) return false;\n return isActive(skillDirectory);\n });\n // Kept in their loaded form as well, because the prompt-block cost is measured\n // by handing them back to Pi's own formatter.\n loaded.push(...kept);\n return kept\n .map((skill) => ({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group,\n owner,\n modelInvocable: !skill.disableModelInvocation,\n }))\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction sortOwners(owners: SkillOwnerNode[]): SkillOwnerNode[] {\n return owners.sort((left, right) => left.owner.localeCompare(right.owner));\n}\n\nfunction collectHelpSkills(skills: readonly Skill[], loaded: Skill[], diagnostics: string[]): SkillOwnerNode[] {\n const occupiedNames = new Set(loaded.map((skill) => skill.name));\n const owners = new Map<string, SkillEntry[]>();\n const candidates = [...skills].sort(\n (left, right) =>\n left.sourceInfo.source.localeCompare(right.sourceInfo.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n for (const skill of candidates) {\n if (occupiedNames.has(skill.name)) {\n diagnostics.push(\n `Help ${skill.sourceInfo.source} [HELP_SKILL_COLLISION]: '${skill.name}' was ignored because a normal skill wins.`,\n );\n continue;\n }\n occupiedNames.add(skill.name);\n loaded.push(skill);\n const owner = skill.sourceInfo.source;\n const entries = owners.get(owner) ?? [];\n entries.push({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group: 'help',\n owner,\n modelInvocable: !skill.disableModelInvocation,\n });\n owners.set(owner, entries);\n }\n return sortOwners(\n [...owners.entries()].map(([owner, entries]) => ({\n owner,\n skills: entries.sort((left, right) => left.name.localeCompare(right.name)),\n })),\n );\n}\n\n/**\n * Every skill this session actually loaded, grouped by where it comes from,\n * priced in tokens.\n *\n * Discovery runs over `loadSkillsFromDir` from Pi rather than the harness's own\n * walker so the listing matches what Pi would load, including its ignore-file\n * handling; the same skills are then handed to Pi's `formatSkillsForPrompt` so\n * the always-on figure is the real prompt block rather than a reconstruction of\n * it.\n */\nexport async function buildSkillCatalog(options: SkillCatalogOptions): Promise<SkillCatalog> {\n const { repoRoot } = options;\n const isActive = createActiveMatcher(options.activeSkillDirectories);\n const diagnostics: string[] = [...(options.helpDiagnostics ?? [])];\n const loaded: Skill[] = [];\n\n const extensionOwners: SkillOwnerNode[] = [];\n for (const entry of options.extensionSources) {\n const skills = entry.directories.flatMap((directory) =>\n collect(directory, 'extensions', entry.source, () => true, loaded, diagnostics),\n );\n if (skills.length > 0) extensionOwners.push({ owner: entry.source, skills });\n }\n\n // Pi's skill walker below is Node-only, so this catalog reads through the local Node host.\n const fs = createNodePlatform().env(repoRoot);\n const context = BACKGROUND_CONTEXT;\n const pluginOwners: SkillOwnerNode[] = [];\n const domainPlugins = await materializePluginEntries(\n await resolvePluginEntriesAsync(fs, repoRoot, await listDomainNamesAsync(fs, repoRoot, context), [], context),\n );\n for (const entry of domainPlugins) {\n const directory = path.join(entry.directory, SKILLS_DIRECTORY);\n const present = await fs.exists(directory, context);\n if (!present.ok || !present.value) continue;\n const owner = path.basename(entry.directory);\n const skills = collect(directory, 'plugins', owner, isActive, loaded, diagnostics, entry.skillDiscovery);\n if (skills.length > 0) pluginOwners.push({ owner, skills });\n }\n\n const sharedDirectory = path.join(repoRoot, '.claude', SKILLS_DIRECTORY);\n const sharedSkills = collect(sharedDirectory, 'default', SHARED_SKILLS_OWNER, isActive, loaded, diagnostics);\n const defaultOwners = sharedSkills.length > 0 ? [{ owner: SHARED_SKILLS_OWNER, skills: sharedSkills }] : [];\n const helpOwners = collectHelpSkills(options.helpSkills ?? [], loaded, diagnostics);\n\n const groups: SkillGroup[] = [\n { key: 'extensions', label: 'extensions', owners: sortOwners(extensionOwners) },\n { key: 'help', label: 'Help', owners: helpOwners },\n { key: 'plugins', label: 'plugins', owners: sortOwners(pluginOwners) },\n { key: 'default', label: 'default', owners: defaultOwners },\n ];\n\n const countTokens = await counter();\n const prompt = formatSkillsForPrompt(loaded);\n const framingTokens = countTokens(formatSkillsForPrompt([]));\n const marginal = new Map<string, number>();\n for (const skill of loaded) {\n marginal.set(skill.name, Math.max(0, countTokens(formatSkillsForPrompt([skill])) - framingTokens));\n }\n for (const group of groups) {\n for (const owner of group.owners) {\n for (const skill of owner.skills) {\n const tokens = marginal.get(skill.name);\n if (tokens !== undefined) skill.promptTokens = tokens;\n }\n }\n }\n let bodyTokens = 0;\n for (const skill of loaded) {\n try {\n bodyTokens += countTokens(getOrThrow(await fs.readTextFile(skill.filePath, context)));\n } catch (error) {\n diagnostics.push(`${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n return {\n groups,\n skillCount: loaded.length,\n promptTokens: prompt ? countTokens(prompt) : 0,\n bodyTokens,\n diagnostics,\n };\n}\n\n/** The skills a name resolves to, used to decide what `enter` can invoke. */\nexport function findSkill(catalog: SkillCatalog, name: string): SkillEntry | undefined {\n for (const group of catalog.groups) {\n for (const owner of group.owners) {\n const match = owner.skills.find((skill) => skill.name === name);\n if (match) return match;\n }\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;AAaA,MAAM,mBAAmB;AACzB,MAAM,sBAAsB;;;;;;;;;AAgE5B,IAAI;AAEJ,eAAsB,UAA6C;CACjE,cAAc,OAAO;CACrB,QAAQ,MAAM,UAAA,CAAW;AAC3B;;;;;;;;;AAUA,SAAS,oBAAoB,aAAgE;CAC3F,MAAM,SAAS,YAAY,KAAK,cAAcA,UAAAA,QAAK,QAAQ,SAAS,CAAC;CACrE,QAAQ,cAAc;EACpB,MAAM,WAAWA,UAAAA,QAAK,QAAQ,SAAS;EACvC,OAAO,OAAO,MACX,UACC,UAAU,YAAY,MAAM,WAAW,GAAG,WAAWA,UAAAA,QAAK,KAAK,KAAK,SAAS,WAAW,GAAG,QAAQA,UAAAA,QAAK,KAAK,CACjH;CACF;AACF;AAEA,SAAS,QACP,WACA,OACA,OACA,UACA,QACA,aACA,YAAkC,aACpB;CACd,MAAM,UAAA,GAASC,gCAAAA,kBAAAA,CAAkB;EAAE,KAAK;EAAW,QAAQ;CAAM,CAAC;CAClE,KAAK,MAAM,cAAc,OAAO,aAC9B,YAAY,KAAK,GAAG,WAAW,QAAQ,UAAU,IAAI,WAAW,SAAS;CAI3E,MAAM,OAAOD,UAAAA,QAAK,QAAQ,SAAS;CACnC,MAAM,OAAO,OAAO,OAAO,QAAQ,UAAU;EAC3C,MAAM,iBAAiBA,UAAAA,QAAK,QAAQ,MAAM,QAAQ;EAClD,IAAI,cAAc,qBAAqBA,UAAAA,QAAK,QAAQ,cAAc,MAAM,MAAM,OAAO;EACrF,OAAO,SAAS,cAAc;CAChC,CAAC;CAGD,OAAO,KAAK,GAAG,IAAI;CACnB,OAAO,KACJ,KAAK,WAAW;EACf,MAAM,MAAM;EACZ,aAAa,MAAM;EACnB,UAAU,MAAM;EAChB,SAAS,MAAM;EACf;EACA;EACA,gBAAgB,CAAC,MAAM;CACzB,EAAE,CAAC,CACF,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAC9D;AAEA,SAAS,WAAW,QAA4C;CAC9D,OAAO,OAAO,MAAM,MAAM,UAAU,KAAK,MAAM,cAAc,MAAM,KAAK,CAAC;AAC3E;AAEA,SAAS,kBAAkB,QAA0B,QAAiB,aAAyC;CAC7G,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC;CAC/D,MAAM,yBAAS,IAAI,IAA0B;CAC7C,MAAM,aAAa,CAAC,GAAG,MAAM,CAAC,CAAC,MAC5B,MAAM,UACL,KAAK,WAAW,OAAO,cAAc,MAAM,WAAW,MAAM,KAC5D,KAAK,KAAK,cAAc,MAAM,IAAI,KAClC,KAAK,SAAS,cAAc,MAAM,QAAQ,CAC9C;CACA,KAAK,MAAM,SAAS,YAAY;EAC9B,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG;GACjC,YAAY,KACV,QAAQ,MAAM,WAAW,OAAO,4BAA4B,MAAM,KAAK,2CACzE;GACA;EACF;EACA,cAAc,IAAI,MAAM,IAAI;EAC5B,OAAO,KAAK,KAAK;EACjB,MAAM,QAAQ,MAAM,WAAW;EAC/B,MAAM,UAAU,OAAO,IAAI,KAAK,KAAK,CAAC;EACtC,QAAQ,KAAK;GACX,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,OAAO;GACP;GACA,gBAAgB,CAAC,MAAM;EACzB,CAAC;EACD,OAAO,IAAI,OAAO,OAAO;CAC3B;CACA,OAAO,WACL,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc;EAC/C;EACA,QAAQ,QAAQ,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;CAC3E,EAAE,CACJ;AACF;;;;;;;;;;;AAYA,eAAsB,kBAAkB,SAAqD;CAC3F,MAAM,EAAE,aAAa;CACrB,MAAM,WAAW,oBAAoB,QAAQ,sBAAsB;CACnE,MAAM,cAAwB,CAAC,GAAI,QAAQ,mBAAmB,CAAC,CAAE;CACjE,MAAM,SAAkB,CAAC;CAEzB,MAAM,kBAAoC,CAAC;CAC3C,KAAK,MAAM,SAAS,QAAQ,kBAAkB;EAC5C,MAAM,SAAS,MAAM,YAAY,SAAS,cACxC,QAAQ,WAAW,cAAc,MAAM,cAAc,MAAM,QAAQ,WAAW,CAChF;EACA,IAAI,OAAO,SAAS,GAAG,gBAAgB,KAAK;GAAE,OAAO,MAAM;GAAQ;EAAO,CAAC;CAC7E;CAGA,MAAM,MAAA,GAAKE,gCAAAA,mBAAAA,CAAmB,CAAC,CAAC,IAAI,QAAQ;CAC5C,MAAM,UAAUC,4BAAAA;CAChB,MAAM,eAAiC,CAAC;CACxC,MAAM,gBAAgB,OAAA,GAAMC,6BAAAA,yBAAAA,CAC1B,OAAA,GAAMC,6BAAAA,0BAAAA,CAA0B,IAAI,UAAU,OAAA,GAAMC,6BAAAA,qBAAAA,CAAqB,IAAI,UAAU,OAAO,GAAG,CAAC,GAAG,OAAO,CAC9G;CACA,KAAK,MAAM,SAAS,eAAe;EACjC,MAAM,YAAYN,UAAAA,QAAK,KAAK,MAAM,WAAW,gBAAgB;EAC7D,MAAM,UAAU,MAAM,GAAG,OAAO,WAAW,OAAO;EAClD,IAAI,CAAC,QAAQ,MAAM,CAAC,QAAQ,OAAO;EACnC,MAAM,QAAQA,UAAAA,QAAK,SAAS,MAAM,SAAS;EAC3C,MAAM,SAAS,QAAQ,WAAW,WAAW,OAAO,UAAU,QAAQ,aAAa,MAAM,cAAc;EACvG,IAAI,OAAO,SAAS,GAAG,aAAa,KAAK;GAAE;GAAO;EAAO,CAAC;CAC5D;CAGA,MAAM,eAAe,QADGA,UAAAA,QAAK,KAAK,UAAU,WAAW,gBACZ,GAAG,WAAW,qBAAqB,UAAU,QAAQ,WAAW;CAC3G,MAAM,gBAAgB,aAAa,SAAS,IAAI,CAAC;EAAE,OAAO;EAAqB,QAAQ;CAAa,CAAC,IAAI,CAAC;CAC1G,MAAM,aAAa,kBAAkB,QAAQ,cAAc,CAAC,GAAG,QAAQ,WAAW;CAElF,MAAM,SAAuB;EAC3B;GAAE,KAAK;GAAc,OAAO;GAAc,QAAQ,WAAW,eAAe;EAAE;EAC9E;GAAE,KAAK;GAAQ,OAAO;GAAQ,QAAQ;EAAW;EACjD;GAAE,KAAK;GAAW,OAAO;GAAW,QAAQ,WAAW,YAAY;EAAE;EACrE;GAAE,KAAK;GAAW,OAAO;GAAW,QAAQ;EAAc;CAC5D;CAEA,MAAM,cAAc,MAAM,QAAQ;CAClC,MAAM,UAAA,GAASO,gCAAAA,sBAAAA,CAAsB,MAAM;CAC3C,MAAM,gBAAgB,aAAA,GAAYA,gCAAAA,sBAAAA,CAAsB,CAAC,CAAC,CAAC;CAC3D,MAAM,2BAAW,IAAI,IAAoB;CACzC,KAAK,MAAM,SAAS,QAClB,SAAS,IAAI,MAAM,MAAM,KAAK,IAAI,GAAG,aAAA,GAAYA,gCAAAA,sBAAAA,CAAsB,CAAC,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC;CAEnG,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,SAAS,MAAM,QACxB,KAAK,MAAM,SAAS,MAAM,QAAQ;EAChC,MAAM,SAAS,SAAS,IAAI,MAAM,IAAI;EACtC,IAAI,WAAW,KAAA,GAAW,MAAM,eAAe;CACjD;CAGJ,IAAI,aAAa;CACjB,KAAK,MAAM,SAAS,QAClB,IAAI;EACF,cAAc,aAAA,GAAYC,4BAAAA,WAAAA,CAAW,MAAM,GAAG,aAAa,MAAM,UAAU,OAAO,CAAC,CAAC;CACtF,SAAS,OAAO;EACd,YAAY,KAAK,GAAG,MAAM,SAAS,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;CACjG;CAGF,OAAO;EACL;EACA,YAAY,OAAO;EACnB,cAAc,SAAS,YAAY,MAAM,IAAI;EAC7C;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.mts","names":[],"sources":["../src/services/skillCatalog/index.ts"],"mappings":";;;KAWY;UAEK;EACf;EACA;EACA;EACA;EACA,OAAO;;EAEP;EACA;;;;;;;;EAQA;;UAGe;EACf;EACA,QAAQ;;UAGO;EACf,KAAK;EACL;EACA,QAAQ;;UAGO;EACf,QAAQ;EACR;;EAEA;;EAEA;;EAEA;;UAGe;EACf;;EAEA;EACA,2BAA2B;;EAE3B,sBAAsB;;EAEtB;;wBAaoB,WAAW,SAAS;;;;;;;;;;;wBAkHpB,kBAAkB,SAAS,sBAAsB,QAAQ"}
1
+ {"version":3,"file":"catalog.d.mts","names":[],"sources":["../src/services/skillCatalog/index.ts"],"mappings":";;;KAgBY;UAEK;EACf;EACA;EACA;EACA;EACA,OAAO;;EAEP;EACA;;;;;;;;EAQA;;UAGe;EACf;EACA,QAAQ;;UAGO;EACf,KAAK;EACL;EACA,QAAQ;;UAGO;EACf,QAAQ;EACR;;EAEA;;EAEA;;EAEA;;UAGe;EACf;;EAEA;EACA,2BAA2B;;EAE3B,sBAAsB;;EAEtB;;wBAaoB,WAAW,SAAS;;;;;;;;;;;wBAkHpB,kBAAkB,SAAS,sBAAsB,QAAQ"}
package/dist/catalog.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
- import { listDomainNames, resolvePluginEntries } from "@agimon-ai/po-config/domains";
2
+ import { listDomainNamesAsync, resolvePluginEntriesAsync } from "@agimon-ai/po-config/domains";
3
+ import { createNodePlatform } from "@agimon-ai/po-core/nodePlatform";
4
+ import { BACKGROUND_CONTEXT, getOrThrow } from "@agimon-ai/po-core/platform";
4
5
  import { materializePluginEntries } from "@agimon-ai/po-domain/plugins";
5
6
  import { formatSkillsForPrompt, loadSkillsFromDir } from "@earendil-works/pi-coding-agent";
6
7
  //#region src/services/skillCatalog/index.ts
@@ -112,11 +113,14 @@ async function buildSkillCatalog(options) {
112
113
  skills
113
114
  });
114
115
  }
116
+ const fs = createNodePlatform().env(repoRoot);
117
+ const context = BACKGROUND_CONTEXT;
115
118
  const pluginOwners = [];
116
- const domainPlugins = await materializePluginEntries(resolvePluginEntries(repoRoot, listDomainNames(repoRoot), []));
119
+ const domainPlugins = await materializePluginEntries(await resolvePluginEntriesAsync(fs, repoRoot, await listDomainNamesAsync(fs, repoRoot, context), [], context));
117
120
  for (const entry of domainPlugins) {
118
121
  const directory = path.join(entry.directory, SKILLS_DIRECTORY);
119
- if (!fs.existsSync(directory)) continue;
122
+ const present = await fs.exists(directory, context);
123
+ if (!present.ok || !present.value) continue;
120
124
  const owner = path.basename(entry.directory);
121
125
  const skills = collect(directory, "plugins", owner, isActive, loaded, diagnostics, entry.skillDiscovery);
122
126
  if (skills.length > 0) pluginOwners.push({
@@ -163,7 +167,7 @@ async function buildSkillCatalog(options) {
163
167
  }
164
168
  let bodyTokens = 0;
165
169
  for (const skill of loaded) try {
166
- bodyTokens += countTokens(fs.readFileSync(skill.filePath, "utf8"));
170
+ bodyTokens += countTokens(getOrThrow(await fs.readTextFile(skill.filePath, context)));
167
171
  } catch (error) {
168
172
  diagnostics.push(`${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);
169
173
  }
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.mjs","names":[],"sources":["../src/services/skillCatalog/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { listDomainNames, type PluginSkillDiscovery, resolvePluginEntries } from '@agimon-ai/po-config/domains';\nimport type { SkillSourceContribution } from '@agimon-ai/po-core/skills';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\n\nconst SKILLS_DIRECTORY = 'skills';\nconst SHARED_SKILLS_OWNER = '.claude/skills';\n\nexport type SkillGroupKey = 'extensions' | 'help' | 'plugins' | 'default';\n\nexport interface SkillEntry {\n name: string;\n description: string;\n filePath: string;\n baseDir: string;\n group: SkillGroupKey;\n /** Package name for an extension, plugin directory name, or `.claude/skills`. */\n owner: string;\n modelInvocable: boolean;\n /**\n * What removing this skill would save from the `<available_skills>` block.\n *\n * Marginal rather than proportional: the block has fixed framing that no\n * single skill pays for, so the useful figure is what actually goes away.\n * Bodies are excluded because they load on demand and are not a resting cost.\n */\n promptTokens?: number;\n}\n\nexport interface SkillOwnerNode {\n owner: string;\n skills: SkillEntry[];\n}\n\nexport interface SkillGroup {\n key: SkillGroupKey;\n label: string;\n owners: SkillOwnerNode[];\n}\n\nexport interface SkillCatalog {\n groups: SkillGroup[];\n skillCount: number;\n /** Tokens the `<available_skills>` prompt block costs on every request. */\n promptTokens: number;\n /** Tokens every loaded SKILL.md costs in full, if all of them were read. */\n bodyTokens: number;\n /** Frontmatter and read failures, surfaced instead of thrown. */\n diagnostics: string[];\n}\n\nexport interface SkillCatalogOptions {\n repoRoot: string;\n /** Exact files the current selection resolved to, from `HarnessState.skillDirectories`. */\n activeSkillDirectories: readonly string[];\n extensionSources: readonly SkillSourceContribution[];\n /** Activation-gated wrappers already accepted by the merged active-skill view. */\n helpSkills?: readonly Skill[];\n /** Bounded Help activation and collision diagnostics. */\n helpDiagnostics?: readonly string[];\n}\n\n/**\n * The tokenizer, loaded on first use rather than at import.\n *\n * `gpt-tokenizer` carries its whole vocabulary and costs about a tenth of a\n * second to load. This module is reachable from the skills extension, which Pi\n * loads at session start, so a top-level import would charge every session for\n * a surface most of them never open.\n */\nlet tokenizer: Promise<typeof import('gpt-tokenizer')> | undefined;\n\nexport async function counter(): Promise<(text: string) => number> {\n tokenizer ??= import('gpt-tokenizer');\n return (await tokenizer).countTokens;\n}\n\n/**\n * Matches a directory against the active set.\n *\n * A domain that lists a plugin subset puts individual skill directories in the\n * set rather than the plugin's `skills` root, so containment has to be checked\n * both ways: the set may hold an ancestor of the directory, or a descendant of\n * it.\n */\nfunction createActiveMatcher(directories: readonly string[]): (directory: string) => boolean {\n const active = directories.map((directory) => path.resolve(directory));\n return (directory) => {\n const resolved = path.resolve(directory);\n return active.some(\n (entry) =>\n entry === resolved || entry.startsWith(`${resolved}${path.sep}`) || resolved.startsWith(`${entry}${path.sep}`),\n );\n };\n}\n\nfunction collect(\n directory: string,\n group: SkillGroupKey,\n owner: string,\n isActive: (directory: string) => boolean,\n loaded: Skill[],\n diagnostics: string[],\n discovery: PluginSkillDiscovery = 'recursive',\n): SkillEntry[] {\n const result = loadSkillsFromDir({ dir: directory, source: owner });\n for (const diagnostic of result.diagnostics) {\n diagnostics.push(`${diagnostic.path ?? directory}: ${diagnostic.message}`);\n }\n // Checked per skill, not per root: a subset domain selects some of a plugin's\n // skills and leaves the rest on disk, unloaded and therefore not listed.\n const root = path.resolve(directory);\n const kept = result.skills.filter((skill) => {\n const skillDirectory = path.dirname(skill.filePath);\n if (discovery === 'direct-children' && path.dirname(skillDirectory) !== root) return false;\n return isActive(skillDirectory);\n });\n // Kept in their loaded form as well, because the prompt-block cost is measured\n // by handing them back to Pi's own formatter.\n loaded.push(...kept);\n return kept\n .map((skill) => ({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group,\n owner,\n modelInvocable: !skill.disableModelInvocation,\n }))\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction sortOwners(owners: SkillOwnerNode[]): SkillOwnerNode[] {\n return owners.sort((left, right) => left.owner.localeCompare(right.owner));\n}\n\nfunction collectHelpSkills(skills: readonly Skill[], loaded: Skill[], diagnostics: string[]): SkillOwnerNode[] {\n const occupiedNames = new Set(loaded.map((skill) => skill.name));\n const owners = new Map<string, SkillEntry[]>();\n const candidates = [...skills].sort(\n (left, right) =>\n left.sourceInfo.source.localeCompare(right.sourceInfo.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n for (const skill of candidates) {\n if (occupiedNames.has(skill.name)) {\n diagnostics.push(\n `Help ${skill.sourceInfo.source} [HELP_SKILL_COLLISION]: '${skill.name}' was ignored because a normal skill wins.`,\n );\n continue;\n }\n occupiedNames.add(skill.name);\n loaded.push(skill);\n const owner = skill.sourceInfo.source;\n const entries = owners.get(owner) ?? [];\n entries.push({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group: 'help',\n owner,\n modelInvocable: !skill.disableModelInvocation,\n });\n owners.set(owner, entries);\n }\n return sortOwners(\n [...owners.entries()].map(([owner, entries]) => ({\n owner,\n skills: entries.sort((left, right) => left.name.localeCompare(right.name)),\n })),\n );\n}\n\n/**\n * Every skill this session actually loaded, grouped by where it comes from,\n * priced in tokens.\n *\n * Discovery runs over `loadSkillsFromDir` from Pi rather than the harness's own\n * walker so the listing matches what Pi would load, including its ignore-file\n * handling; the same skills are then handed to Pi's `formatSkillsForPrompt` so\n * the always-on figure is the real prompt block rather than a reconstruction of\n * it.\n */\nexport async function buildSkillCatalog(options: SkillCatalogOptions): Promise<SkillCatalog> {\n const { repoRoot } = options;\n const isActive = createActiveMatcher(options.activeSkillDirectories);\n const diagnostics: string[] = [...(options.helpDiagnostics ?? [])];\n const loaded: Skill[] = [];\n\n const extensionOwners: SkillOwnerNode[] = [];\n for (const entry of options.extensionSources) {\n const skills = entry.directories.flatMap((directory) =>\n collect(directory, 'extensions', entry.source, () => true, loaded, diagnostics),\n );\n if (skills.length > 0) extensionOwners.push({ owner: entry.source, skills });\n }\n\n const pluginOwners: SkillOwnerNode[] = [];\n const domainPlugins = await materializePluginEntries(resolvePluginEntries(repoRoot, listDomainNames(repoRoot), []));\n for (const entry of domainPlugins) {\n const directory = path.join(entry.directory, SKILLS_DIRECTORY);\n if (!fs.existsSync(directory)) continue;\n const owner = path.basename(entry.directory);\n const skills = collect(directory, 'plugins', owner, isActive, loaded, diagnostics, entry.skillDiscovery);\n if (skills.length > 0) pluginOwners.push({ owner, skills });\n }\n\n const sharedDirectory = path.join(repoRoot, '.claude', SKILLS_DIRECTORY);\n const sharedSkills = collect(sharedDirectory, 'default', SHARED_SKILLS_OWNER, isActive, loaded, diagnostics);\n const defaultOwners = sharedSkills.length > 0 ? [{ owner: SHARED_SKILLS_OWNER, skills: sharedSkills }] : [];\n const helpOwners = collectHelpSkills(options.helpSkills ?? [], loaded, diagnostics);\n\n const groups: SkillGroup[] = [\n { key: 'extensions', label: 'extensions', owners: sortOwners(extensionOwners) },\n { key: 'help', label: 'Help', owners: helpOwners },\n { key: 'plugins', label: 'plugins', owners: sortOwners(pluginOwners) },\n { key: 'default', label: 'default', owners: defaultOwners },\n ];\n\n const countTokens = await counter();\n const prompt = formatSkillsForPrompt(loaded);\n const framingTokens = countTokens(formatSkillsForPrompt([]));\n const marginal = new Map<string, number>();\n for (const skill of loaded) {\n marginal.set(skill.name, Math.max(0, countTokens(formatSkillsForPrompt([skill])) - framingTokens));\n }\n for (const group of groups) {\n for (const owner of group.owners) {\n for (const skill of owner.skills) {\n const tokens = marginal.get(skill.name);\n if (tokens !== undefined) skill.promptTokens = tokens;\n }\n }\n }\n let bodyTokens = 0;\n for (const skill of loaded) {\n try {\n bodyTokens += countTokens(fs.readFileSync(skill.filePath, 'utf8'));\n } catch (error) {\n diagnostics.push(`${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n return {\n groups,\n skillCount: loaded.length,\n promptTokens: prompt ? countTokens(prompt) : 0,\n bodyTokens,\n diagnostics,\n };\n}\n\n/** The skills a name resolves to, used to decide what `enter` can invoke. */\nexport function findSkill(catalog: SkillCatalog, name: string): SkillEntry | undefined {\n for (const group of catalog.groups) {\n for (const owner of group.owners) {\n const match = owner.skills.find((skill) => skill.name === name);\n if (match) return match;\n }\n }\n return undefined;\n}\n"],"mappings":";;;;;;AAQA,MAAM,mBAAmB;AACzB,MAAM,sBAAsB;;;;;;;;;AAgE5B,IAAI;AAEJ,eAAsB,UAA6C;CACjE,cAAc,OAAO;CACrB,QAAQ,MAAM,UAAA,CAAW;AAC3B;;;;;;;;;AAUA,SAAS,oBAAoB,aAAgE;CAC3F,MAAM,SAAS,YAAY,KAAK,cAAc,KAAK,QAAQ,SAAS,CAAC;CACrE,QAAQ,cAAc;EACpB,MAAM,WAAW,KAAK,QAAQ,SAAS;EACvC,OAAO,OAAO,MACX,UACC,UAAU,YAAY,MAAM,WAAW,GAAG,WAAW,KAAK,KAAK,KAAK,SAAS,WAAW,GAAG,QAAQ,KAAK,KAAK,CACjH;CACF;AACF;AAEA,SAAS,QACP,WACA,OACA,OACA,UACA,QACA,aACA,YAAkC,aACpB;CACd,MAAM,SAAS,kBAAkB;EAAE,KAAK;EAAW,QAAQ;CAAM,CAAC;CAClE,KAAK,MAAM,cAAc,OAAO,aAC9B,YAAY,KAAK,GAAG,WAAW,QAAQ,UAAU,IAAI,WAAW,SAAS;CAI3E,MAAM,OAAO,KAAK,QAAQ,SAAS;CACnC,MAAM,OAAO,OAAO,OAAO,QAAQ,UAAU;EAC3C,MAAM,iBAAiB,KAAK,QAAQ,MAAM,QAAQ;EAClD,IAAI,cAAc,qBAAqB,KAAK,QAAQ,cAAc,MAAM,MAAM,OAAO;EACrF,OAAO,SAAS,cAAc;CAChC,CAAC;CAGD,OAAO,KAAK,GAAG,IAAI;CACnB,OAAO,KACJ,KAAK,WAAW;EACf,MAAM,MAAM;EACZ,aAAa,MAAM;EACnB,UAAU,MAAM;EAChB,SAAS,MAAM;EACf;EACA;EACA,gBAAgB,CAAC,MAAM;CACzB,EAAE,CAAC,CACF,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAC9D;AAEA,SAAS,WAAW,QAA4C;CAC9D,OAAO,OAAO,MAAM,MAAM,UAAU,KAAK,MAAM,cAAc,MAAM,KAAK,CAAC;AAC3E;AAEA,SAAS,kBAAkB,QAA0B,QAAiB,aAAyC;CAC7G,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC;CAC/D,MAAM,yBAAS,IAAI,IAA0B;CAC7C,MAAM,aAAa,CAAC,GAAG,MAAM,CAAC,CAAC,MAC5B,MAAM,UACL,KAAK,WAAW,OAAO,cAAc,MAAM,WAAW,MAAM,KAC5D,KAAK,KAAK,cAAc,MAAM,IAAI,KAClC,KAAK,SAAS,cAAc,MAAM,QAAQ,CAC9C;CACA,KAAK,MAAM,SAAS,YAAY;EAC9B,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG;GACjC,YAAY,KACV,QAAQ,MAAM,WAAW,OAAO,4BAA4B,MAAM,KAAK,2CACzE;GACA;EACF;EACA,cAAc,IAAI,MAAM,IAAI;EAC5B,OAAO,KAAK,KAAK;EACjB,MAAM,QAAQ,MAAM,WAAW;EAC/B,MAAM,UAAU,OAAO,IAAI,KAAK,KAAK,CAAC;EACtC,QAAQ,KAAK;GACX,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,OAAO;GACP;GACA,gBAAgB,CAAC,MAAM;EACzB,CAAC;EACD,OAAO,IAAI,OAAO,OAAO;CAC3B;CACA,OAAO,WACL,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc;EAC/C;EACA,QAAQ,QAAQ,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;CAC3E,EAAE,CACJ;AACF;;;;;;;;;;;AAYA,eAAsB,kBAAkB,SAAqD;CAC3F,MAAM,EAAE,aAAa;CACrB,MAAM,WAAW,oBAAoB,QAAQ,sBAAsB;CACnE,MAAM,cAAwB,CAAC,GAAI,QAAQ,mBAAmB,CAAC,CAAE;CACjE,MAAM,SAAkB,CAAC;CAEzB,MAAM,kBAAoC,CAAC;CAC3C,KAAK,MAAM,SAAS,QAAQ,kBAAkB;EAC5C,MAAM,SAAS,MAAM,YAAY,SAAS,cACxC,QAAQ,WAAW,cAAc,MAAM,cAAc,MAAM,QAAQ,WAAW,CAChF;EACA,IAAI,OAAO,SAAS,GAAG,gBAAgB,KAAK;GAAE,OAAO,MAAM;GAAQ;EAAO,CAAC;CAC7E;CAEA,MAAM,eAAiC,CAAC;CACxC,MAAM,gBAAgB,MAAM,yBAAyB,qBAAqB,UAAU,gBAAgB,QAAQ,GAAG,CAAC,CAAC,CAAC;CAClH,KAAK,MAAM,SAAS,eAAe;EACjC,MAAM,YAAY,KAAK,KAAK,MAAM,WAAW,gBAAgB;EAC7D,IAAI,CAAC,GAAG,WAAW,SAAS,GAAG;EAC/B,MAAM,QAAQ,KAAK,SAAS,MAAM,SAAS;EAC3C,MAAM,SAAS,QAAQ,WAAW,WAAW,OAAO,UAAU,QAAQ,aAAa,MAAM,cAAc;EACvG,IAAI,OAAO,SAAS,GAAG,aAAa,KAAK;GAAE;GAAO;EAAO,CAAC;CAC5D;CAGA,MAAM,eAAe,QADG,KAAK,KAAK,UAAU,WAAW,gBACZ,GAAG,WAAW,qBAAqB,UAAU,QAAQ,WAAW;CAC3G,MAAM,gBAAgB,aAAa,SAAS,IAAI,CAAC;EAAE,OAAO;EAAqB,QAAQ;CAAa,CAAC,IAAI,CAAC;CAC1G,MAAM,aAAa,kBAAkB,QAAQ,cAAc,CAAC,GAAG,QAAQ,WAAW;CAElF,MAAM,SAAuB;EAC3B;GAAE,KAAK;GAAc,OAAO;GAAc,QAAQ,WAAW,eAAe;EAAE;EAC9E;GAAE,KAAK;GAAQ,OAAO;GAAQ,QAAQ;EAAW;EACjD;GAAE,KAAK;GAAW,OAAO;GAAW,QAAQ,WAAW,YAAY;EAAE;EACrE;GAAE,KAAK;GAAW,OAAO;GAAW,QAAQ;EAAc;CAC5D;CAEA,MAAM,cAAc,MAAM,QAAQ;CAClC,MAAM,SAAS,sBAAsB,MAAM;CAC3C,MAAM,gBAAgB,YAAY,sBAAsB,CAAC,CAAC,CAAC;CAC3D,MAAM,2BAAW,IAAI,IAAoB;CACzC,KAAK,MAAM,SAAS,QAClB,SAAS,IAAI,MAAM,MAAM,KAAK,IAAI,GAAG,YAAY,sBAAsB,CAAC,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC;CAEnG,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,SAAS,MAAM,QACxB,KAAK,MAAM,SAAS,MAAM,QAAQ;EAChC,MAAM,SAAS,SAAS,IAAI,MAAM,IAAI;EACtC,IAAI,WAAW,KAAA,GAAW,MAAM,eAAe;CACjD;CAGJ,IAAI,aAAa;CACjB,KAAK,MAAM,SAAS,QAClB,IAAI;EACF,cAAc,YAAY,GAAG,aAAa,MAAM,UAAU,MAAM,CAAC;CACnE,SAAS,OAAO;EACd,YAAY,KAAK,GAAG,MAAM,SAAS,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;CACjG;CAGF,OAAO;EACL;EACA,YAAY,OAAO;EACnB,cAAc,SAAS,YAAY,MAAM,IAAI;EAC7C;EACA;CACF;AACF"}
1
+ {"version":3,"file":"catalog.mjs","names":[],"sources":["../src/services/skillCatalog/index.ts"],"sourcesContent":["import path from 'node:path';\n\nimport {\n listDomainNamesAsync,\n type PluginSkillDiscovery,\n resolvePluginEntriesAsync,\n} from '@agimon-ai/po-config/domains';\nimport { createNodePlatform } from '@agimon-ai/po-core/nodePlatform';\nimport { BACKGROUND_CONTEXT, getOrThrow } from '@agimon-ai/po-core/platform';\nimport type { SkillSourceContribution } from '@agimon-ai/po-core/skills';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\n\nconst SKILLS_DIRECTORY = 'skills';\nconst SHARED_SKILLS_OWNER = '.claude/skills';\n\nexport type SkillGroupKey = 'extensions' | 'help' | 'plugins' | 'default';\n\nexport interface SkillEntry {\n name: string;\n description: string;\n filePath: string;\n baseDir: string;\n group: SkillGroupKey;\n /** Package name for an extension, plugin directory name, or `.claude/skills`. */\n owner: string;\n modelInvocable: boolean;\n /**\n * What removing this skill would save from the `<available_skills>` block.\n *\n * Marginal rather than proportional: the block has fixed framing that no\n * single skill pays for, so the useful figure is what actually goes away.\n * Bodies are excluded because they load on demand and are not a resting cost.\n */\n promptTokens?: number;\n}\n\nexport interface SkillOwnerNode {\n owner: string;\n skills: SkillEntry[];\n}\n\nexport interface SkillGroup {\n key: SkillGroupKey;\n label: string;\n owners: SkillOwnerNode[];\n}\n\nexport interface SkillCatalog {\n groups: SkillGroup[];\n skillCount: number;\n /** Tokens the `<available_skills>` prompt block costs on every request. */\n promptTokens: number;\n /** Tokens every loaded SKILL.md costs in full, if all of them were read. */\n bodyTokens: number;\n /** Frontmatter and read failures, surfaced instead of thrown. */\n diagnostics: string[];\n}\n\nexport interface SkillCatalogOptions {\n repoRoot: string;\n /** Exact files the current selection resolved to, from `HarnessState.skillDirectories`. */\n activeSkillDirectories: readonly string[];\n extensionSources: readonly SkillSourceContribution[];\n /** Activation-gated wrappers already accepted by the merged active-skill view. */\n helpSkills?: readonly Skill[];\n /** Bounded Help activation and collision diagnostics. */\n helpDiagnostics?: readonly string[];\n}\n\n/**\n * The tokenizer, loaded on first use rather than at import.\n *\n * `gpt-tokenizer` carries its whole vocabulary and costs about a tenth of a\n * second to load. This module is reachable from the skills extension, which Pi\n * loads at session start, so a top-level import would charge every session for\n * a surface most of them never open.\n */\nlet tokenizer: Promise<typeof import('gpt-tokenizer')> | undefined;\n\nexport async function counter(): Promise<(text: string) => number> {\n tokenizer ??= import('gpt-tokenizer');\n return (await tokenizer).countTokens;\n}\n\n/**\n * Matches a directory against the active set.\n *\n * A domain that lists a plugin subset puts individual skill directories in the\n * set rather than the plugin's `skills` root, so containment has to be checked\n * both ways: the set may hold an ancestor of the directory, or a descendant of\n * it.\n */\nfunction createActiveMatcher(directories: readonly string[]): (directory: string) => boolean {\n const active = directories.map((directory) => path.resolve(directory));\n return (directory) => {\n const resolved = path.resolve(directory);\n return active.some(\n (entry) =>\n entry === resolved || entry.startsWith(`${resolved}${path.sep}`) || resolved.startsWith(`${entry}${path.sep}`),\n );\n };\n}\n\nfunction collect(\n directory: string,\n group: SkillGroupKey,\n owner: string,\n isActive: (directory: string) => boolean,\n loaded: Skill[],\n diagnostics: string[],\n discovery: PluginSkillDiscovery = 'recursive',\n): SkillEntry[] {\n const result = loadSkillsFromDir({ dir: directory, source: owner });\n for (const diagnostic of result.diagnostics) {\n diagnostics.push(`${diagnostic.path ?? directory}: ${diagnostic.message}`);\n }\n // Checked per skill, not per root: a subset domain selects some of a plugin's\n // skills and leaves the rest on disk, unloaded and therefore not listed.\n const root = path.resolve(directory);\n const kept = result.skills.filter((skill) => {\n const skillDirectory = path.dirname(skill.filePath);\n if (discovery === 'direct-children' && path.dirname(skillDirectory) !== root) return false;\n return isActive(skillDirectory);\n });\n // Kept in their loaded form as well, because the prompt-block cost is measured\n // by handing them back to Pi's own formatter.\n loaded.push(...kept);\n return kept\n .map((skill) => ({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group,\n owner,\n modelInvocable: !skill.disableModelInvocation,\n }))\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction sortOwners(owners: SkillOwnerNode[]): SkillOwnerNode[] {\n return owners.sort((left, right) => left.owner.localeCompare(right.owner));\n}\n\nfunction collectHelpSkills(skills: readonly Skill[], loaded: Skill[], diagnostics: string[]): SkillOwnerNode[] {\n const occupiedNames = new Set(loaded.map((skill) => skill.name));\n const owners = new Map<string, SkillEntry[]>();\n const candidates = [...skills].sort(\n (left, right) =>\n left.sourceInfo.source.localeCompare(right.sourceInfo.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n for (const skill of candidates) {\n if (occupiedNames.has(skill.name)) {\n diagnostics.push(\n `Help ${skill.sourceInfo.source} [HELP_SKILL_COLLISION]: '${skill.name}' was ignored because a normal skill wins.`,\n );\n continue;\n }\n occupiedNames.add(skill.name);\n loaded.push(skill);\n const owner = skill.sourceInfo.source;\n const entries = owners.get(owner) ?? [];\n entries.push({\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n group: 'help',\n owner,\n modelInvocable: !skill.disableModelInvocation,\n });\n owners.set(owner, entries);\n }\n return sortOwners(\n [...owners.entries()].map(([owner, entries]) => ({\n owner,\n skills: entries.sort((left, right) => left.name.localeCompare(right.name)),\n })),\n );\n}\n\n/**\n * Every skill this session actually loaded, grouped by where it comes from,\n * priced in tokens.\n *\n * Discovery runs over `loadSkillsFromDir` from Pi rather than the harness's own\n * walker so the listing matches what Pi would load, including its ignore-file\n * handling; the same skills are then handed to Pi's `formatSkillsForPrompt` so\n * the always-on figure is the real prompt block rather than a reconstruction of\n * it.\n */\nexport async function buildSkillCatalog(options: SkillCatalogOptions): Promise<SkillCatalog> {\n const { repoRoot } = options;\n const isActive = createActiveMatcher(options.activeSkillDirectories);\n const diagnostics: string[] = [...(options.helpDiagnostics ?? [])];\n const loaded: Skill[] = [];\n\n const extensionOwners: SkillOwnerNode[] = [];\n for (const entry of options.extensionSources) {\n const skills = entry.directories.flatMap((directory) =>\n collect(directory, 'extensions', entry.source, () => true, loaded, diagnostics),\n );\n if (skills.length > 0) extensionOwners.push({ owner: entry.source, skills });\n }\n\n // Pi's skill walker below is Node-only, so this catalog reads through the local Node host.\n const fs = createNodePlatform().env(repoRoot);\n const context = BACKGROUND_CONTEXT;\n const pluginOwners: SkillOwnerNode[] = [];\n const domainPlugins = await materializePluginEntries(\n await resolvePluginEntriesAsync(fs, repoRoot, await listDomainNamesAsync(fs, repoRoot, context), [], context),\n );\n for (const entry of domainPlugins) {\n const directory = path.join(entry.directory, SKILLS_DIRECTORY);\n const present = await fs.exists(directory, context);\n if (!present.ok || !present.value) continue;\n const owner = path.basename(entry.directory);\n const skills = collect(directory, 'plugins', owner, isActive, loaded, diagnostics, entry.skillDiscovery);\n if (skills.length > 0) pluginOwners.push({ owner, skills });\n }\n\n const sharedDirectory = path.join(repoRoot, '.claude', SKILLS_DIRECTORY);\n const sharedSkills = collect(sharedDirectory, 'default', SHARED_SKILLS_OWNER, isActive, loaded, diagnostics);\n const defaultOwners = sharedSkills.length > 0 ? [{ owner: SHARED_SKILLS_OWNER, skills: sharedSkills }] : [];\n const helpOwners = collectHelpSkills(options.helpSkills ?? [], loaded, diagnostics);\n\n const groups: SkillGroup[] = [\n { key: 'extensions', label: 'extensions', owners: sortOwners(extensionOwners) },\n { key: 'help', label: 'Help', owners: helpOwners },\n { key: 'plugins', label: 'plugins', owners: sortOwners(pluginOwners) },\n { key: 'default', label: 'default', owners: defaultOwners },\n ];\n\n const countTokens = await counter();\n const prompt = formatSkillsForPrompt(loaded);\n const framingTokens = countTokens(formatSkillsForPrompt([]));\n const marginal = new Map<string, number>();\n for (const skill of loaded) {\n marginal.set(skill.name, Math.max(0, countTokens(formatSkillsForPrompt([skill])) - framingTokens));\n }\n for (const group of groups) {\n for (const owner of group.owners) {\n for (const skill of owner.skills) {\n const tokens = marginal.get(skill.name);\n if (tokens !== undefined) skill.promptTokens = tokens;\n }\n }\n }\n let bodyTokens = 0;\n for (const skill of loaded) {\n try {\n bodyTokens += countTokens(getOrThrow(await fs.readTextFile(skill.filePath, context)));\n } catch (error) {\n diagnostics.push(`${skill.filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n return {\n groups,\n skillCount: loaded.length,\n promptTokens: prompt ? countTokens(prompt) : 0,\n bodyTokens,\n diagnostics,\n };\n}\n\n/** The skills a name resolves to, used to decide what `enter` can invoke. */\nexport function findSkill(catalog: SkillCatalog, name: string): SkillEntry | undefined {\n for (const group of catalog.groups) {\n for (const owner of group.owners) {\n const match = owner.skills.find((skill) => skill.name === name);\n if (match) return match;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  let _agimon_ai_po_core_mcpFacet = require("@agimon-ai/po-core/mcpFacet");
6
6
  let _agimon_ai_po_core_extensionFile = require("@agimon-ai/po-core/extensionFile");
7
+ require("@agimon-ai/po-core/platform");
7
8
  let _agimon_ai_po_core_serverFacet = require("@agimon-ai/po-core/serverFacet");
8
9
  let typebox = require("typebox");
9
10
  //#region src/services/mcpSkills/index.ts
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.cjs","names":["defineRoutedContribution","defineMcpSkill","readPackageResource","defineMcpSkill","readPackageResource","Type","defineMcpTool","defineMcpTool","defineMcpPlugin","skillDiscovered","skillPoAuthorSkill","skillPoUseSkill","toolLoadSkill","toolSearchSkills"],"sources":["../../src/services/mcpSkills/index.ts","../../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\n\nimport { bindMcpSkills } from '../../../../../services/mcpSkills';\n\nexport default defineRoutedContribution(bindMcpSkills, { cardinality: 'many' });\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a Po package.',\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-author-skill/SKILL.md'),\n});\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.\",\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-skill/SKILL.md'),\n});\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createLoadSkillTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createLoadSkillTool());\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createSearchSkillsTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createSearchSkillsTool());\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/po-core/mcpFacet';\n\nimport skillDiscovered from '../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp';\nimport skillPoAuthorSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp';\nimport skillPoUseSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp';\nimport toolSearchSkills from '../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nconst withWidget = <T>(key: string, value: T): T => value === undefined ? value : ({\n ...value, _meta: { ...(value as { _meta?: object })._meta, [\"po/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/po-skill',\n session: (context) => ({\n get skills(): DoomMcpSessionPlugin['skills'] { return [...many(skillDiscovered, context), at(skillPoAuthorSkill, context), at(skillPoUseSkill, context)]; },\n get tools(): DoomMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/po-skill/load_skill\", via({ name: 'load_skill' }, at(toolLoadSkill, context))), withWidget(\"@agimon-ai/po-skill/search_skills\", via({ name: 'search_skills' }, at(toolSearchSkills, context)))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp.cjs","names":["defineRoutedContribution","defineMcpSkill","readPackageResource","defineMcpSkill","readPackageResource","Type","defineMcpTool","defineMcpTool","defineMcpPlugin","skillDiscovered","skillPoAuthorSkill","skillPoUseSkill","toolLoadSkill","toolSearchSkills"],"sources":["../../src/services/mcpSkills/index.ts","../../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { BACKGROUND_CONTEXT, getOrThrow, withAbortSignal, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], fs: FileSystem, signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = getOrThrow(\n await fs.readTextFile(skill.filePath, withAbortSignal(readSignal, BACKGROUND_CONTEXT)),\n );\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\n\nimport { bindMcpSkills } from '../../../../../services/mcpSkills';\n\nexport default defineRoutedContribution(bindMcpSkills, { cardinality: 'many' });\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a Po package.',\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-author-skill/SKILL.md'),\n});\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.\",\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-skill/SKILL.md'),\n});\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createLoadSkillTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createLoadSkillTool());\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createSearchSkillsTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createSearchSkillsTool());\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/po-core/mcpFacet';\n\nimport skillDiscovered from '../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp';\nimport skillPoAuthorSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp';\nimport skillPoUseSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp';\nimport toolSearchSkills from '../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nconst withWidget = <T>(key: string, value: T): T => value === undefined ? value : ({\n ...value, _meta: { ...(value as { _meta?: object })._meta, [\"po/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/po-skill',\n session: (context) => ({\n get skills(): DoomMcpSessionPlugin['skills'] { return [...many(skillDiscovered, context), at(skillPoAuthorSkill, context), at(skillPoUseSkill, context)]; },\n get tools(): DoomMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/po-skill/load_skill\", via({ name: 'load_skill' }, at(toolLoadSkill, context))), withWidget(\"@agimon-ai/po-skill/search_skills\", via({ name: 'search_skills' }, at(toolSearchSkills, context)))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { defineMcpPlugin, defineMcpSkill, defineMcpTool } from "@agimon-ai/po-core/mcpFacet";
2
2
  import { defineRoutedContribution } from "@agimon-ai/po-core/extensionFile";
3
+ import "@agimon-ai/po-core/platform";
3
4
  import { readPackageResource } from "@agimon-ai/po-core/serverFacet";
4
5
  import { Type } from "typebox";
5
6
  //#region src/services/mcpSkills/index.ts
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.mjs","names":["skillDiscovered","skillPoAuthorSkill","skillPoUseSkill","toolLoadSkill","toolSearchSkills"],"sources":["../../src/services/mcpSkills/index.ts","../../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\n\nimport { bindMcpSkills } from '../../../../../services/mcpSkills';\n\nexport default defineRoutedContribution(bindMcpSkills, { cardinality: 'many' });\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a Po package.',\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-author-skill/SKILL.md'),\n});\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.\",\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-skill/SKILL.md'),\n});\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createLoadSkillTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createLoadSkillTool());\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createSearchSkillsTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createSearchSkillsTool());\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/po-core/mcpFacet';\n\nimport skillDiscovered from '../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp';\nimport skillPoAuthorSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp';\nimport skillPoUseSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp';\nimport toolSearchSkills from '../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nconst withWidget = <T>(key: string, value: T): T => value === undefined ? value : ({\n ...value, _meta: { ...(value as { _meta?: object })._meta, [\"po/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/po-skill',\n session: (context) => ({\n get skills(): DoomMcpSessionPlugin['skills'] { return [...many(skillDiscovered, context), at(skillPoAuthorSkill, context), at(skillPoUseSkill, context)]; },\n get tools(): DoomMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/po-skill/load_skill\", via({ name: 'load_skill' }, at(toolLoadSkill, context))), withWidget(\"@agimon-ai/po-skill/search_skills\", via({ name: 'search_skills' }, at(toolSearchSkills, context)))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":";;;;;AAOA,MAAa,qBAAqB;AAuClC,SAAgB,cAAc,SAAwD;CACpF,MAAM,WAAW,QAAQ,SAAS,IAAsB,kBAAkB;CAC1E,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,wCAAwC;CACvE,OAAO,SAAS,KAAK,OAAO;AAC9B;;;AC9CA,IAAA,yBAAe,yBAAyB,eAAe,EAAE,aAAa,OAAO,CAAC;;;ACD9E,IAAA,8BAAe,eAAe;CAC5B,MAAM;CACN,aACE;CACF,YAAY,oBAAoB,YAAY,KAAK,sCAAsC;AACzF,CAAC;;;ACLD,IAAA,2BAAe,eAAe;CAC5B,MAAM;CACN,aACE;CACF,YAAY,oBAAoB,YAAY,KAAK,mCAAmC;AACtF,CAAC;;;ACND,MAAa,yBAAyB,KAAK,OAAO,EAAE,OAAO,KAAK,SAAS,KAAK,OAAO,CAAC,EAAE,CAAC;AACzF,MAAa,sBAAsB,KAAK,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,CAAC;;;ACCtE,SAAS,KAAK,MAAc;CAC1B,OAAO,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB;CAAK,CAAC,EAAE;AACtD;AAEA,SAAS,OAAO,SAAqD;CACnE,IAAI,QAAQ,cAAc,KAAA,GAAW,MAAM,IAAI,MAAM,qCAAqC;CAC1F,OAAO,QAAQ;AACjB;AAEA,SAAgB,yBAA0E;CACxF,OAAO;EAEL,MAAM;EACN,OAAO;EACP,aAAa;EACb,YAAY;EACZ,aAAa;GAAE,cAAc;GAAM,iBAAiB;GAAO,eAAe;EAAM;EAChF,MAAM,QAAQ,aAAa,EAAE,SAAS,SAAS,WAAW,SAAS;GACjE,MAAM,SAAS,OAAO,KAAK,CAAC,CAAC,kBAAkB;GAC/C,MAAM,YAAY,MAAM,OAAO,OAAO,CAAC,CAAC,KAAK;GAC7C,MAAM,UACJ,WAAW,KAAA,KAAa,WAAW,KAC/B,YACA,UAAU,QAAQ,EAAE,MAAM,kBAAkB,GAAG,KAAK,IAAI,cAAc,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC;GAChH,OAAO;IAAE,GAAG,KAAK,KAAK,UAAU,OAAO,CAAC;IAAG,mBAAmB,EAAE,QAAQ,QAAQ;GAAE;EACpF;CACF;AACF;AAyBA,SAAgB,sBAAoE;CAClF,OAAO;EAEL,MAAM;EACN,OAAO;EACP,aAAa;EACb,YAAY;EACZ,aAAa;GAAE,cAAc;GAAM,iBAAiB;GAAO,eAAe;EAAM;EAChF,MAAM,QAAQ,aAAa,EAAE,QAAQ,SAAS,WAAW,SAAS;GAChE,OAAO,KAAK,MAAM,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;EAC9C;CACF;AACF;;;AChEA,IAAA,yBAAe,cAAc,oBAAoB,CAAC;;;ACAlD,IAAA,4BAAe,cAAc,uBAAuB,CAAC;;;ACMrD,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AAEpE,MAAM,QAAc,OAA6C,YAA6B;CAC5F,MAAM,WAAW,OAAO,UAAU,aAAc,MAA4B,OAAO,IAAI;CACvF,OAAO,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAa;AAC5D;AACA,MAAM,OAAU,UAAmC,UAChD,UAAU,KAAA,IAAY,KAAA,IAAY;CAAE,GAAG;CAAU,GAAI;AAAiB;AAEzE,MAAM,cAAiB,KAAa,UAAgB,UAAU,KAAA,IAAY,QAAS;CACjF,GAAG;CAAO,OAAO;EAAE,GAAI,MAA6B;GAAQ,cAAc;CAAI;AAChF;AAEA,MAAa,MAAM,gBAAgB;CACjC,MAAM;CACN,UAAU,aAAa;EACrB,IAAI,SAAyC;GAAE,OAAO;IAAC,GAAG,KAAKA,wBAAiB,OAAO;IAAG,GAAGC,6BAAoB,OAAO;IAAG,GAAGC,0BAAiB,OAAO;GAAC;EAAG;EAC1J,IAAI,QAAuC;GAAE,OAAO,CAAC,WAAW,kCAAkC,IAAI,EAAE,MAAM,aAAa,GAAG,GAAGC,wBAAe,OAAO,CAAC,CAAC,GAAG,WAAW,qCAAqC,IAAI,EAAE,MAAM,gBAAgB,GAAG,GAAGC,2BAAkB,OAAO,CAAC,CAAC,CAAC;EAAG;CAC/Q;AACF,CAAC"}
1
+ {"version":3,"file":"mcp.mjs","names":["skillDiscovered","skillPoAuthorSkill","skillPoUseSkill","toolLoadSkill","toolSearchSkills"],"sources":["../../src/services/mcpSkills/index.ts","../../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp.ts","../../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { BACKGROUND_CONTEXT, getOrThrow, withAbortSignal, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], fs: FileSystem, signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = getOrThrow(\n await fs.readTextFile(skill.filePath, withAbortSignal(readSignal, BACKGROUND_CONTEXT)),\n );\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\n\nimport { bindMcpSkills } from '../../../../../services/mcpSkills';\n\nexport default defineRoutedContribution(bindMcpSkills, { cardinality: 'many' });\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, adding supporting references or scripts, contributing a runtime skill directory through Cordis, or publishing activation-gated Help prompts from a Po package.',\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-author-skill/SKILL.md'),\n});\n","import { defineMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineMcpSkill({\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Use when browsing available skills, invoking /skill:name, understanding Help and extension-owned skill groups, or diagnosing why a skill is absent or shadowed.\",\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-skill/SKILL.md'),\n});\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createLoadSkillTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createLoadSkillTool());\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createSearchSkillsTool } from '../../../../../services/mcpSkillTools';\n\nexport default defineMcpTool(createSearchSkillsTool());\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/po-core/mcpFacet';\n\nimport skillDiscovered from '../src/extensions/workspaces/sessions/(backend)/skill/discovered.mcp';\nimport skillPoAuthorSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-author-skill.mcp';\nimport skillPoUseSkill from '../src/extensions/workspaces/sessions/(backend)/skill/po-use-skill.mcp';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.mcp';\nimport toolSearchSkills from '../src/extensions/workspaces/sessions/(backend)/tool/search_skills.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nconst withWidget = <T>(key: string, value: T): T => value === undefined ? value : ({\n ...value, _meta: { ...(value as { _meta?: object })._meta, [\"po/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/po-skill',\n session: (context) => ({\n get skills(): DoomMcpSessionPlugin['skills'] { return [...many(skillDiscovered, context), at(skillPoAuthorSkill, context), at(skillPoUseSkill, context)]; },\n get tools(): DoomMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/po-skill/load_skill\", via({ name: 'load_skill' }, at(toolLoadSkill, context))), withWidget(\"@agimon-ai/po-skill/search_skills\", via({ name: 'search_skills' }, at(toolSearchSkills, context)))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
@@ -4,27 +4,25 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  const require_rolldown_runtime = require("../rolldown-runtime-VH7oDXx4.cjs");
6
6
  const require_skills = require("../skills-ComtgYb1.cjs");
7
- let node_fs = require("node:fs");
8
- node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
9
7
  let node_path = require("node:path");
10
8
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
11
9
  let _agimon_ai_po_config_domains = require("@agimon-ai/po-config/domains");
10
+ let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
12
11
  let _agimon_ai_po_domain_plugins = require("@agimon-ai/po-domain/plugins");
13
12
  let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
14
13
  let _agimon_ai_po_core_serverFacet = require("@agimon-ai/po-core/serverFacet");
15
14
  let _agimon_ai_po_core_extensionFile = require("@agimon-ai/po-core/extensionFile");
16
- let node_fs_promises = require("node:fs/promises");
17
15
  let typebox = require("typebox");
18
- let node_os = require("node:os");
19
- node_os = require_rolldown_runtime.__toESM(node_os, 1);
20
16
  let _agimon_ai_po_config_harnessState = require("@agimon-ai/po-config/harnessState");
21
17
  let _agimon_ai_po_domain_resources = require("@agimon-ai/po-domain/resources");
18
+ let node_fs = require("node:fs");
19
+ node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
22
20
  let node_url = require("node:url");
23
21
  let node_worker_threads = require("node:worker_threads");
24
22
  //#region src/services/mcpSkills/index.ts
25
23
  const MCP_SKILLS_SERVICE = "doom/discovered-mcp-skills";
26
24
  /** Explicit remote provider, never a projection of the local resource registry. */
27
- function mountMcpSkills(groups, signal) {
25
+ function mountMcpSkills(groups, fs, signal) {
28
26
  return (context) => {
29
27
  context.provide(MCP_SKILLS_SERVICE, { list(remote) {
30
28
  signal.throwIfAborted();
@@ -41,10 +39,7 @@ function mountMcpSkills(groups, signal) {
41
39
  async read() {
42
40
  const readSignal = AbortSignal.any([signal, remote.signal]);
43
41
  readSignal.throwIfAborted();
44
- const text = await (0, node_fs_promises.readFile)(skill.filePath, {
45
- encoding: "utf8",
46
- signal: readSignal
47
- });
42
+ const text = (0, _agimon_ai_po_core_platform.getOrThrow)(await fs.readTextFile(skill.filePath, (0, _agimon_ai_po_core_platform.withAbortSignal)(readSignal, _agimon_ai_po_core_platform.BACKGROUND_CONTEXT)));
48
43
  readSignal.throwIfAborted();
49
44
  return text;
50
45
  }
@@ -255,15 +250,17 @@ const LOCAL_ONLY_MCP_PACKAGES = /* @__PURE__ */ new Set([
255
250
  "@agimon-ai/po-voice",
256
251
  "@agimon-ai/po-workflow"
257
252
  ]);
258
- async function isMcpSkillPlugin(entry) {
253
+ async function isMcpSkillPlugin(fs, entry, context) {
259
254
  if (entry.source?.type === "npm") return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);
255
+ const source = await fs.readTextFile(node_path.default.join(entry.directory, "package.json"), context);
256
+ if (!source.ok) return source.error.code === "not_found";
260
257
  try {
261
- const manifest = JSON.parse(await (0, node_fs_promises.readFile)(node_path.default.join(entry.directory, "package.json"), "utf8"));
258
+ const manifest = JSON.parse(source.value);
262
259
  if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) return false;
263
260
  const name = manifest.name;
264
261
  return typeof name === "string" && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);
265
- } catch (error) {
266
- return error.code === "ENOENT";
262
+ } catch {
263
+ return false;
267
264
  }
268
265
  }
269
266
  /**
@@ -290,24 +287,25 @@ function within(filePath, roots) {
290
287
  * each group with its `when` lets the existing kernel refresh do the work, the
291
288
  * same way domainServerResources registers per-domain skill resources.
292
289
  */
293
- async function discoverServerSkills(execution, signal) {
290
+ async function discoverServerSkills(execution, fs, signal) {
294
291
  signal.throwIfAborted();
292
+ const context = (0, _agimon_ai_po_core_platform.withAbortSignal)(signal, _agimon_ai_po_core_platform.BACKGROUND_CONTEXT);
295
293
  const state = (0, _agimon_ai_po_config_harnessState.readHarnessState)(execution.environment);
296
- const home = execution.environment.HOME ?? node_os.default.homedir();
294
+ const home = execution.environment.HOME ?? _agimon_ai_po_core_platform.nodeHost.homeDirectory();
297
295
  const repoSkills = node_path.default.join(execution.repoRoot, ".po", "skills");
298
296
  const sources = [];
299
297
  const diagnostics = [];
300
298
  const domains = [];
301
299
  try {
302
- domains.push(...(0, _agimon_ai_po_config_domains.listDomainNames)(execution.repoRoot, home));
300
+ domains.push(...await (0, _agimon_ai_po_config_domains.listDomainNamesAsync)(fs, execution.repoRoot, context, home));
303
301
  } catch (error) {
304
302
  diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);
305
303
  }
306
304
  for (const domain of domains) {
307
305
  signal.throwIfAborted();
308
306
  try {
309
- const plugins = await (0, _agimon_ai_po_domain_plugins.materializePluginEntries)((0, _agimon_ai_po_config_domains.resolvePluginEntries)(execution.repoRoot, [domain], [], home));
310
- const mcpPluginRoots = (await Promise.all(plugins.map(async (plugin) => await isMcpSkillPlugin(plugin) ? node_path.default.join(plugin.directory, "skills") : void 0))).filter((root) => root !== void 0);
307
+ const plugins = await (0, _agimon_ai_po_domain_plugins.materializePluginEntries)(await (0, _agimon_ai_po_config_domains.resolvePluginEntriesAsync)(fs, execution.repoRoot, [domain], [], context, home));
308
+ const mcpPluginRoots = (await Promise.all(plugins.map(async (plugin) => await isMcpSkillPlugin(fs, plugin, context) ? node_path.default.join(plugin.directory, "skills") : void 0))).filter((root) => root !== void 0);
311
309
  const collected = await (0, _agimon_ai_po_domain_resources.collectResources)(execution.repoRoot, plugins, {
312
310
  agents: false,
313
311
  mcp: false,
@@ -333,7 +331,7 @@ async function discoverServerSkills(execution, signal) {
333
331
  const shared = await (0, _agimon_ai_po_domain_resources.discoverSkillsAsync)(execution.repoRoot, node_path.default.join(execution.repoRoot, ".claude", "skills"));
334
332
  if (shared.length > 0) {
335
333
  const sharedPaths = shared.map((skill) => skill.path);
336
- for (const domain of domains) if ((0, _agimon_ai_po_config_domains.resolveSharedSkills)(execution.repoRoot, [domain], home)) sources.push({
334
+ for (const domain of domains) if (await (0, _agimon_ai_po_config_domains.resolveSharedSkillsAsync)(fs, execution.repoRoot, [domain], context, home)) sources.push({
337
335
  domain,
338
336
  paths: sharedPaths,
339
337
  mcpPaths: sharedPaths,
@@ -419,12 +417,12 @@ function createSkillCommands(inventory, catalog, groups) {
419
417
  }
420
418
  //#endregion
421
419
  //#region src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts
422
- async function createSkillServer(agent, signal) {
423
- const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, signal);
420
+ async function createSkillServer(agent, fs, signal) {
421
+ const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, fs, signal);
424
422
  return {
425
423
  commands: createSkillCommands(inventory, catalog, groups),
426
424
  tools: [createSessionLoadSkillTool(agent)],
427
- services: [mountMcpSkills(mcpGroups, signal)],
425
+ services: [mountMcpSkills(mcpGroups, fs, signal)],
428
426
  resources: [...listed.flatMap((group) => group.skills.map((skill) => ({
429
427
  name: skill.name,
430
428
  description: skill.description,
@@ -437,7 +435,7 @@ async function createSkillServer(agent, signal) {
437
435
  mode: group.domain
438
436
  }
439
437
  } },
440
- read: () => (0, node_fs_promises.readFile)(skill.filePath, "utf8")
438
+ read: async () => (0, _agimon_ai_po_core_platform.getOrThrow)(await fs.readTextFile(skill.filePath, _agimon_ai_po_core_platform.BACKGROUND_CONTEXT))
441
439
  }))), ...[{
442
440
  name: "po-author-skill",
443
441
  description: "Author and distribute Po agent skills. Use when creating a SKILL.md, contributing a runtime skill directory, or publishing activation-gated Help prompts and diagnostic tools."
@@ -461,8 +459,8 @@ async function createSkillServer(agent, signal) {
461
459
  }
462
460
  //#endregion
463
461
  //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
464
- var root_server_default = (0, _agimon_ai_po_core_extensionFile.defineRoot)(async ({ agent, signal }) => {
465
- const runtime = agent ? await createSkillServer(agent, signal) : {
462
+ var root_server_default = (0, _agimon_ai_po_core_extensionFile.defineRoot)(async ({ agent, context: root, signal }) => {
463
+ const runtime = agent ? await createSkillServer(agent, (0, _agimon_ai_po_core_platform.requireDoomFs)(root), signal) : {
466
464
  commands: [],
467
465
  resources: []
468
466
  };
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","names":["readFile","Type","fileURLToPath","path","fs","pathToFileURL","getAgentDir","loadSkills","Worker","stripFrontmatter","readFile","path","readHarnessState","os","listDomainNames","materializePluginEntries","resolvePluginEntries","collectResources","discoverSkillsAsync","resolveSharedSkills","formatSkillsForPrompt","SKILL_COMMAND_PREFIX","SKILL_INVOCATION_PREFIX","SKILLS_COMMAND","readFile","packageResourcePath","readPackageResource","defineRoot","defineRoutedContribution","defineRoutedContribution","defineRoutedContribution","defineServerPlugin","composeServerProviderOverrides","rootSession","composeRootHooks","commandSkills","resourceSkills","toolLoadSkill"],"sources":["../../src/services/mcpSkills/index.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/services/deferredSkills/index.ts","../../src/services/serverInventory/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillCommands.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/command/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server.ts","../../generated/server.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","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 Po 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","import { readFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n listDomainNames,\n resolvePluginEntries,\n resolveSharedSkills,\n type PluginEntry,\n} from '@agimon-ai/po-config/domains';\nimport { readHarnessState } from '@agimon-ai/po-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/po-core/headless';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/po-domain/resources';\nimport { formatSkillsForPrompt, type Skill } from '@earendil-works/pi-coding-agent';\n\nimport { DeferredSkillLoader, type DeferredSkillSnapshot } from '../deferredSkills';\n\n/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly mcpGroups: readonly ServerSkillGroup[];\n /**\n * The shared and repository skills this package advertises itself. Domain plugin skills are\n * left out: po-domain already contributes each one, and listing them twice was noise.\n */\n readonly listed: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n readonly mcpPaths: readonly string[];\n /** Advertised by this package rather than by po-domain. */\n readonly listed?: boolean;\n}\n\nconst LOCAL_ONLY_MCP_PACKAGES = new Set([\n '@agimon-ai/po-author',\n '@agimon-ai/po-git',\n '@agimon-ai/po-goal',\n '@agimon-ai/po-help',\n '@agimon-ai/po-loop',\n '@agimon-ai/po-user-feedback',\n '@agimon-ai/po-voice',\n '@agimon-ai/po-workflow',\n]);\n\nasync function isMcpSkillPlugin(entry: PluginEntry): Promise<boolean> {\n if (entry.source?.type === 'npm') return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);\n try {\n const manifest = JSON.parse(await readFile(path.join(entry.directory, 'package.json'), 'utf8')) as unknown;\n if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) return false;\n const name = (manifest as { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);\n } catch (error) {\n // A package manifest is optional for catalog plugins, but a present identity\n // that cannot be read or parsed must never make the plugin remotely eligible.\n return (error as NodeJS.ErrnoException).code === 'ENOENT';\n }\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.po/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? os.homedir();\n const repoSkills = path.join(execution.repoRoot, '.po', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...listDomainNames(execution.repoRoot, home));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));\n const mcpPluginRoots = (\n await Promise.all(\n plugins.map(async (plugin) =>\n (await isMcpSkillPlugin(plugin)) ? path.join(plugin.directory, 'skills') : undefined,\n ),\n )\n ).filter((root): root is string => root !== undefined);\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) {\n const paths = [...collected.skillDirectories];\n sources.push({\n domain,\n paths,\n // Exact collected paths retain this domain's per-plugin skill subset.\n // A plugin root would also admit skills loaded for another domain.\n mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots)),\n });\n }\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (resolveSharedSkills(execution.repoRoot, [domain], home)) {\n sources.push({ domain, paths: sharedPaths, mcpPaths: sharedPaths, listed: true });\n }\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills], mcpPaths: [repoSkills], listed: true });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const listed = sources\n .filter((source) => source.listed === true)\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const mcpGroups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n mcpGroups,\n listed,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n","import type { DoomHeadlessCommand } from '@agimon-ai/po-core/headless';\nimport type { Skill } from '@earendil-works/pi-coding-agent';\n\nimport { expandDeferredSkillCommand, type DeferredSkillSnapshot } from '../../../../../services/deferredSkills';\nimport type { ServerSkillGroup } from '../../../../../services/serverInventory';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, SKILLS_COMMAND } from '../../../../../types/skills';\n\n/**\n * One command per skill, gated by whatever activates that skill.\n *\n * Expansion always reads the full inventory, not the group: a command is only\n * dispatchable while its condition holds, so by the time one runs its skill is\n * active, and looking it up in the union keeps one lookup table instead of one\n * per domain.\n */\nfunction skillCommand(skill: Skill, domain: string | undefined, inventory: DeferredSkillSnapshot): DoomHeadlessCommand {\n return {\n ...(domain === undefined ? {} : { when: { domain } }),\n name: `${SKILL_COMMAND_PREFIX}${skill.name}`,\n description: skill.description,\n execute: async (args, execution) => {\n const text = `${SKILL_INVOCATION_PREFIX}${skill.name}${args ? ` ${args}` : ''}`;\n const expanded = expandDeferredSkillCommand(text, inventory.skills);\n if (expanded === text) throw new Error(`Skill '${skill.name}' is no longer readable.`);\n await execution.session.prompt(expanded);\n },\n };\n}\n\nexport function createSkillCommands(\n inventory: DeferredSkillSnapshot,\n catalog: string,\n groups: readonly ServerSkillGroup[],\n): readonly DoomHeadlessCommand[] {\n return [\n {\n name: SKILLS_COMMAND,\n description: 'List discovered skills or invoke one by name.',\n async execute(args, execution) {\n const requested = args.trim();\n if (requested) {\n await execution.session.prompt(\n expandDeferredSkillCommand(`${SKILL_INVOCATION_PREFIX}${requested}`, inventory.skills),\n );\n return;\n }\n await execution.client.notify({\n title: 'Po skills',\n body: catalog,\n level: 'info',\n });\n },\n },\n ...groups.flatMap((group) => group.skills.map((skill) => skillCommand(skill, group.domain, inventory))),\n ];\n}\n","import { readFile } from 'node:fs/promises';\n\nimport type { DoomHeadlessHostService } from '@agimon-ai/po-core/headless';\nimport { type DoomServerSessionPlugin, packageResourcePath, readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nimport { mountMcpSkills } from '../../../../../services/mcpSkills';\nimport { createSessionLoadSkillTool } from '../../../../../services/mcpSkillTools';\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\n\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n tools: [createSessionLoadSkillTool(agent)],\n services: [mountMcpSkills(mcpGroups, signal)],\n resources: [\n // Each skill joins the prompt's one <available_skills> list, gated like the rest.\n ...listed.flatMap((group) =>\n group.skills.map((skill) => ({\n name: skill.name,\n description: skill.description,\n path: skill.filePath,\n kind: 'skill' as const,\n ...(group.domain === undefined\n ? {}\n : { when: { domain: group.domain, attribution: { kind: 'domain' as const, mode: group.domain } } }),\n read: () => readFile(skill.filePath, 'utf8'),\n })),\n ),\n ...[\n {\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, contributing a runtime skill directory, or publishing activation-gated Help prompts and diagnostic tools.',\n },\n {\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Diagnose missing or shadowed skills and invoke the narrowest available guidance.\",\n },\n ].map((skill) => ({\n ...skill,\n when: { state: { 'minor-mode': 'help' }, attribution: { kind: 'minor' as const, mode: 'help' } },\n kind: 'skill' as const,\n path: packageResourcePath(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n read: () => readPackageResource(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n })),\n ],\n };\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, signal)\n : { commands: [], resources: [] };\n return { value: runtime, services: runtime.services };\n});\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.commands!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.resources!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\n/** The session agent's skill loader; the Pi CLI keeps Pi's own skill handling. */\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.tools?.[0],\n { cardinality: 'optional' },\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport commandSkills from '../src/extensions/workspaces/sessions/(backend)/command/skills.server';\nimport resourceSkills from '../src/extensions/workspaces/sessions/(backend)/resource/skills.server';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\n/* v8 ignore next -- generated normalization branch */\nconst defined = <T>(values: readonly (T | undefined)[]): T[] =>\n values.filter((value): value is T => value !== undefined);\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/po-skill',\n providerOverrides: composeServerProviderOverrides(rootSession),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get commands(): DoomServerSessionPlugin['commands'] { return [...many(commandSkills, contextSession)]; },\n get resources(): DoomServerSessionPlugin['resources'] { return [...many(resourceSkills, contextSession)]; },\n get tools(): DoomServerSessionPlugin['tools'] { return defined([via({ name: 'load_skill' }, at(toolLoadSkill, contextSession))]); },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.cjs","names":["getOrThrow","withAbortSignal","BACKGROUND_CONTEXT","Type","fileURLToPath","path","fs","pathToFileURL","getAgentDir","loadSkills","Worker","stripFrontmatter","path","withAbortSignal","BACKGROUND_CONTEXT","readHarnessState","nodeHost","listDomainNamesAsync","materializePluginEntries","resolvePluginEntriesAsync","collectResources","discoverSkillsAsync","resolveSharedSkillsAsync","formatSkillsForPrompt","SKILL_COMMAND_PREFIX","SKILL_INVOCATION_PREFIX","SKILLS_COMMAND","getOrThrow","BACKGROUND_CONTEXT","packageResourcePath","readPackageResource","defineRoot","requireDoomFs","defineRoutedContribution","defineRoutedContribution","defineRoutedContribution","defineServerPlugin","composeServerProviderOverrides","rootSession","composeRootHooks","commandSkills","resourceSkills","toolLoadSkill"],"sources":["../../src/services/mcpSkills/index.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/services/deferredSkills/index.ts","../../src/services/serverInventory/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillCommands.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/command/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server.ts","../../generated/server.ts"],"sourcesContent":["import type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { BACKGROUND_CONTEXT, getOrThrow, withAbortSignal, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], fs: FileSystem, signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = getOrThrow(\n await fs.readTextFile(skill.filePath, withAbortSignal(readSignal, BACKGROUND_CONTEXT)),\n );\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","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\nimport type { DeferredSkillLoaderOptions, DeferredSkillSnapshot } from '../../types/skills';\n\nexport type { DeferredSkillLoaderOptions, DeferredSkillSnapshot } from '../../types/skills';\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 Po 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\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","import path from 'node:path';\n\nimport {\n listDomainNamesAsync,\n resolvePluginEntriesAsync,\n resolveSharedSkillsAsync,\n type PluginEntry,\n} from '@agimon-ai/po-config/domains';\nimport { readHarnessState } from '@agimon-ai/po-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/po-core/headless';\nimport { BACKGROUND_CONTEXT, nodeHost, withAbortSignal, type FileSystem } from '@agimon-ai/po-core/platform';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/po-domain/resources';\nimport { formatSkillsForPrompt } from '@earendil-works/pi-coding-agent';\n\nimport type { ServerSkillInventory } from '../../types/skills';\nimport { DeferredSkillLoader } from '../deferredSkills';\n\nexport type { ServerSkillGroup, ServerSkillInventory } from '../../types/skills';\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n readonly mcpPaths: readonly string[];\n /** Advertised by this package rather than by po-domain. */\n readonly listed?: boolean;\n}\n\nconst LOCAL_ONLY_MCP_PACKAGES = new Set([\n '@agimon-ai/po-author',\n '@agimon-ai/po-git',\n '@agimon-ai/po-goal',\n '@agimon-ai/po-help',\n '@agimon-ai/po-loop',\n '@agimon-ai/po-user-feedback',\n '@agimon-ai/po-voice',\n '@agimon-ai/po-workflow',\n]);\n\ntype IoContext = Parameters<FileSystem['readTextFile']>[1];\n\nasync function isMcpSkillPlugin(fs: FileSystem, entry: PluginEntry, context: IoContext): Promise<boolean> {\n if (entry.source?.type === 'npm') return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);\n // A package manifest is optional for catalog plugins, but a present identity\n // that cannot be read or parsed must never make the plugin remotely eligible.\n const source = await fs.readTextFile(path.join(entry.directory, 'package.json'), context);\n if (!source.ok) return source.error.code === 'not_found';\n try {\n const manifest = JSON.parse(source.value) as unknown;\n if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) return false;\n const name = (manifest as { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.po/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n fs: FileSystem,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const context = withAbortSignal(signal, BACKGROUND_CONTEXT);\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? nodeHost.homeDirectory();\n const repoSkills = path.join(execution.repoRoot, '.po', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...(await listDomainNamesAsync(fs, execution.repoRoot, context, home)));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(\n await resolvePluginEntriesAsync(fs, execution.repoRoot, [domain], [], context, home),\n );\n const mcpPluginRoots = (\n await Promise.all(\n plugins.map(async (plugin) =>\n (await isMcpSkillPlugin(fs, plugin, context)) ? path.join(plugin.directory, 'skills') : undefined,\n ),\n )\n ).filter((root): root is string => root !== undefined);\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) {\n const paths = [...collected.skillDirectories];\n sources.push({\n domain,\n paths,\n // Exact collected paths retain this domain's per-plugin skill subset.\n // A plugin root would also admit skills loaded for another domain.\n mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots)),\n });\n }\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (await resolveSharedSkillsAsync(fs, execution.repoRoot, [domain], context, home)) {\n sources.push({ domain, paths: sharedPaths, mcpPaths: sharedPaths, listed: true });\n }\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills], mcpPaths: [repoSkills], listed: true });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const listed = sources\n .filter((source) => source.listed === true)\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const mcpGroups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n mcpGroups,\n listed,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n","import type { DoomHeadlessCommand } from '@agimon-ai/po-core/headless';\nimport type { Skill } from '@earendil-works/pi-coding-agent';\n\nimport { expandDeferredSkillCommand, type DeferredSkillSnapshot } from '../../../../../services/deferredSkills';\nimport type { ServerSkillGroup } from '../../../../../services/serverInventory';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, SKILLS_COMMAND } from '../../../../../types/skills';\n\n/**\n * One command per skill, gated by whatever activates that skill.\n *\n * Expansion always reads the full inventory, not the group: a command is only\n * dispatchable while its condition holds, so by the time one runs its skill is\n * active, and looking it up in the union keeps one lookup table instead of one\n * per domain.\n */\nfunction skillCommand(skill: Skill, domain: string | undefined, inventory: DeferredSkillSnapshot): DoomHeadlessCommand {\n return {\n ...(domain === undefined ? {} : { when: { domain } }),\n name: `${SKILL_COMMAND_PREFIX}${skill.name}`,\n description: skill.description,\n execute: async (args, execution) => {\n const text = `${SKILL_INVOCATION_PREFIX}${skill.name}${args ? ` ${args}` : ''}`;\n const expanded = expandDeferredSkillCommand(text, inventory.skills);\n if (expanded === text) throw new Error(`Skill '${skill.name}' is no longer readable.`);\n await execution.session.prompt(expanded);\n },\n };\n}\n\nexport function createSkillCommands(\n inventory: DeferredSkillSnapshot,\n catalog: string,\n groups: readonly ServerSkillGroup[],\n): readonly DoomHeadlessCommand[] {\n return [\n {\n name: SKILLS_COMMAND,\n description: 'List discovered skills or invoke one by name.',\n async execute(args, execution) {\n const requested = args.trim();\n if (requested) {\n await execution.session.prompt(\n expandDeferredSkillCommand(`${SKILL_INVOCATION_PREFIX}${requested}`, inventory.skills),\n );\n return;\n }\n await execution.client.notify({\n title: 'Po skills',\n body: catalog,\n level: 'info',\n });\n },\n },\n ...groups.flatMap((group) => group.skills.map((skill) => skillCommand(skill, group.domain, inventory))),\n ];\n}\n","import type { DoomHeadlessHostService } from '@agimon-ai/po-core/headless';\nimport { BACKGROUND_CONTEXT, getOrThrow, type FileSystem } from '@agimon-ai/po-core/platform';\nimport { type DoomServerSessionPlugin, packageResourcePath, readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nimport { mountMcpSkills } from '../../../../../services/mcpSkills';\nimport { createSessionLoadSkillTool } from '../../../../../services/mcpSkillTools';\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\n\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n fs: FileSystem,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, fs, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n tools: [createSessionLoadSkillTool(agent)],\n services: [mountMcpSkills(mcpGroups, fs, signal)],\n resources: [\n // Each skill joins the prompt's one <available_skills> list, gated like the rest.\n ...listed.flatMap((group) =>\n group.skills.map((skill) => ({\n name: skill.name,\n description: skill.description,\n path: skill.filePath,\n kind: 'skill' as const,\n ...(group.domain === undefined\n ? {}\n : { when: { domain: group.domain, attribution: { kind: 'domain' as const, mode: group.domain } } }),\n read: async () => getOrThrow(await fs.readTextFile(skill.filePath, BACKGROUND_CONTEXT)),\n })),\n ),\n ...[\n {\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, contributing a runtime skill directory, or publishing activation-gated Help prompts and diagnostic tools.',\n },\n {\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Diagnose missing or shadowed skills and invoke the narrowest available guidance.\",\n },\n ].map((skill) => ({\n ...skill,\n when: { state: { 'minor-mode': 'help' }, attribution: { kind: 'minor' as const, mode: 'help' } },\n kind: 'skill' as const,\n path: packageResourcePath(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n read: () => readPackageResource(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n })),\n ],\n };\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport { requireDoomFs } from '@agimon-ai/po-core/platform';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, context: root, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, requireDoomFs(root), signal)\n : { commands: [], resources: [] };\n return { value: runtime, services: runtime.services };\n});\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.commands!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.resources!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\n/** The session agent's skill loader; the Pi CLI keeps Pi's own skill handling. */\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.tools?.[0],\n { cardinality: 'optional' },\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport commandSkills from '../src/extensions/workspaces/sessions/(backend)/command/skills.server';\nimport resourceSkills from '../src/extensions/workspaces/sessions/(backend)/resource/skills.server';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\n/* v8 ignore next -- generated normalization branch */\nconst defined = <T>(values: readonly (T | undefined)[]): T[] =>\n values.filter((value): value is T => value !== undefined);\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/po-skill',\n providerOverrides: composeServerProviderOverrides(rootSession),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get commands(): DoomServerSessionPlugin['commands'] { return [...many(commandSkills, contextSession)]; },\n get resources(): DoomServerSessionPlugin['resources'] { return [...many(resourceSkills, contextSession)]; },\n get tools(): DoomServerSessionPlugin['tools'] { return defined([via({ name: 'load_skill' }, at(toolLoadSkill, contextSession))]); },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}
@@ -1,22 +1,21 @@
1
1
  import { n as SKILL_COMMAND_PREFIX, r as SKILL_INVOCATION_PREFIX, t as SKILLS_COMMAND } from "../skills-Ccvg1Hcz.mjs";
2
- import fs from "node:fs";
3
2
  import path from "node:path";
4
- import { listDomainNames, resolvePluginEntries, resolveSharedSkills } from "@agimon-ai/po-config/domains";
3
+ import { listDomainNamesAsync, resolvePluginEntriesAsync, resolveSharedSkillsAsync } from "@agimon-ai/po-config/domains";
4
+ import { BACKGROUND_CONTEXT, getOrThrow, nodeHost, requireDoomFs, withAbortSignal } from "@agimon-ai/po-core/platform";
5
5
  import { materializePluginEntries } from "@agimon-ai/po-domain/plugins";
6
6
  import { formatSkillsForPrompt, getAgentDir, loadSkills, stripFrontmatter } from "@earendil-works/pi-coding-agent";
7
7
  import { composeServerProviderOverrides, defineServerPlugin, packageResourcePath, readPackageResource } from "@agimon-ai/po-core/serverFacet";
8
8
  import { composeRootHooks, defineRoot, defineRoutedContribution } from "@agimon-ai/po-core/extensionFile";
9
- import { readFile } from "node:fs/promises";
10
9
  import { Type } from "typebox";
11
- import os from "node:os";
12
10
  import { readHarnessState } from "@agimon-ai/po-config/harnessState";
13
11
  import { collectResources, discoverSkillsAsync } from "@agimon-ai/po-domain/resources";
12
+ import fs from "node:fs";
14
13
  import { fileURLToPath, pathToFileURL } from "node:url";
15
14
  import { Worker } from "node:worker_threads";
16
15
  //#region src/services/mcpSkills/index.ts
17
16
  const MCP_SKILLS_SERVICE = "doom/discovered-mcp-skills";
18
17
  /** Explicit remote provider, never a projection of the local resource registry. */
19
- function mountMcpSkills(groups, signal) {
18
+ function mountMcpSkills(groups, fs, signal) {
20
19
  return (context) => {
21
20
  context.provide(MCP_SKILLS_SERVICE, { list(remote) {
22
21
  signal.throwIfAborted();
@@ -33,10 +32,7 @@ function mountMcpSkills(groups, signal) {
33
32
  async read() {
34
33
  const readSignal = AbortSignal.any([signal, remote.signal]);
35
34
  readSignal.throwIfAborted();
36
- const text = await readFile(skill.filePath, {
37
- encoding: "utf8",
38
- signal: readSignal
39
- });
35
+ const text = getOrThrow(await fs.readTextFile(skill.filePath, withAbortSignal(readSignal, BACKGROUND_CONTEXT)));
40
36
  readSignal.throwIfAborted();
41
37
  return text;
42
38
  }
@@ -247,15 +243,17 @@ const LOCAL_ONLY_MCP_PACKAGES = /* @__PURE__ */ new Set([
247
243
  "@agimon-ai/po-voice",
248
244
  "@agimon-ai/po-workflow"
249
245
  ]);
250
- async function isMcpSkillPlugin(entry) {
246
+ async function isMcpSkillPlugin(fs, entry, context) {
251
247
  if (entry.source?.type === "npm") return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);
248
+ const source = await fs.readTextFile(path.join(entry.directory, "package.json"), context);
249
+ if (!source.ok) return source.error.code === "not_found";
252
250
  try {
253
- const manifest = JSON.parse(await readFile(path.join(entry.directory, "package.json"), "utf8"));
251
+ const manifest = JSON.parse(source.value);
254
252
  if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) return false;
255
253
  const name = manifest.name;
256
254
  return typeof name === "string" && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);
257
- } catch (error) {
258
- return error.code === "ENOENT";
255
+ } catch {
256
+ return false;
259
257
  }
260
258
  }
261
259
  /**
@@ -282,24 +280,25 @@ function within(filePath, roots) {
282
280
  * each group with its `when` lets the existing kernel refresh do the work, the
283
281
  * same way domainServerResources registers per-domain skill resources.
284
282
  */
285
- async function discoverServerSkills(execution, signal) {
283
+ async function discoverServerSkills(execution, fs, signal) {
286
284
  signal.throwIfAborted();
285
+ const context = withAbortSignal(signal, BACKGROUND_CONTEXT);
287
286
  const state = readHarnessState(execution.environment);
288
- const home = execution.environment.HOME ?? os.homedir();
287
+ const home = execution.environment.HOME ?? nodeHost.homeDirectory();
289
288
  const repoSkills = path.join(execution.repoRoot, ".po", "skills");
290
289
  const sources = [];
291
290
  const diagnostics = [];
292
291
  const domains = [];
293
292
  try {
294
- domains.push(...listDomainNames(execution.repoRoot, home));
293
+ domains.push(...await listDomainNamesAsync(fs, execution.repoRoot, context, home));
295
294
  } catch (error) {
296
295
  diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);
297
296
  }
298
297
  for (const domain of domains) {
299
298
  signal.throwIfAborted();
300
299
  try {
301
- const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));
302
- const mcpPluginRoots = (await Promise.all(plugins.map(async (plugin) => await isMcpSkillPlugin(plugin) ? path.join(plugin.directory, "skills") : void 0))).filter((root) => root !== void 0);
300
+ const plugins = await materializePluginEntries(await resolvePluginEntriesAsync(fs, execution.repoRoot, [domain], [], context, home));
301
+ const mcpPluginRoots = (await Promise.all(plugins.map(async (plugin) => await isMcpSkillPlugin(fs, plugin, context) ? path.join(plugin.directory, "skills") : void 0))).filter((root) => root !== void 0);
303
302
  const collected = await collectResources(execution.repoRoot, plugins, {
304
303
  agents: false,
305
304
  mcp: false,
@@ -325,7 +324,7 @@ async function discoverServerSkills(execution, signal) {
325
324
  const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, ".claude", "skills"));
326
325
  if (shared.length > 0) {
327
326
  const sharedPaths = shared.map((skill) => skill.path);
328
- for (const domain of domains) if (resolveSharedSkills(execution.repoRoot, [domain], home)) sources.push({
327
+ for (const domain of domains) if (await resolveSharedSkillsAsync(fs, execution.repoRoot, [domain], context, home)) sources.push({
329
328
  domain,
330
329
  paths: sharedPaths,
331
330
  mcpPaths: sharedPaths,
@@ -411,12 +410,12 @@ function createSkillCommands(inventory, catalog, groups) {
411
410
  }
412
411
  //#endregion
413
412
  //#region src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts
414
- async function createSkillServer(agent, signal) {
415
- const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, signal);
413
+ async function createSkillServer(agent, fs, signal) {
414
+ const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, fs, signal);
416
415
  return {
417
416
  commands: createSkillCommands(inventory, catalog, groups),
418
417
  tools: [createSessionLoadSkillTool(agent)],
419
- services: [mountMcpSkills(mcpGroups, signal)],
418
+ services: [mountMcpSkills(mcpGroups, fs, signal)],
420
419
  resources: [...listed.flatMap((group) => group.skills.map((skill) => ({
421
420
  name: skill.name,
422
421
  description: skill.description,
@@ -429,7 +428,7 @@ async function createSkillServer(agent, signal) {
429
428
  mode: group.domain
430
429
  }
431
430
  } },
432
- read: () => readFile(skill.filePath, "utf8")
431
+ read: async () => getOrThrow(await fs.readTextFile(skill.filePath, BACKGROUND_CONTEXT))
433
432
  }))), ...[{
434
433
  name: "po-author-skill",
435
434
  description: "Author and distribute Po agent skills. Use when creating a SKILL.md, contributing a runtime skill directory, or publishing activation-gated Help prompts and diagnostic tools."
@@ -453,8 +452,8 @@ async function createSkillServer(agent, signal) {
453
452
  }
454
453
  //#endregion
455
454
  //#region src/extensions/workspaces/sessions/(backend)/root.server.ts
456
- var root_server_default = defineRoot(async ({ agent, signal }) => {
457
- const runtime = agent ? await createSkillServer(agent, signal) : {
455
+ var root_server_default = defineRoot(async ({ agent, context: root, signal }) => {
456
+ const runtime = agent ? await createSkillServer(agent, requireDoomFs(root), signal) : {
458
457
  commands: [],
459
458
  resources: []
460
459
  };
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":["rootSession","commandSkills","resourceSkills","toolLoadSkill"],"sources":["../../src/services/mcpSkills/index.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/services/deferredSkills/index.ts","../../src/services/serverInventory/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillCommands.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/command/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server.ts","../../generated/server.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\n\nimport type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = await readFile(skill.filePath, { encoding: 'utf8', signal: readSignal });\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","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 Po 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","import { readFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n listDomainNames,\n resolvePluginEntries,\n resolveSharedSkills,\n type PluginEntry,\n} from '@agimon-ai/po-config/domains';\nimport { readHarnessState } from '@agimon-ai/po-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/po-core/headless';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/po-domain/resources';\nimport { formatSkillsForPrompt, type Skill } from '@earendil-works/pi-coding-agent';\n\nimport { DeferredSkillLoader, type DeferredSkillSnapshot } from '../deferredSkills';\n\n/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly mcpGroups: readonly ServerSkillGroup[];\n /**\n * The shared and repository skills this package advertises itself. Domain plugin skills are\n * left out: po-domain already contributes each one, and listing them twice was noise.\n */\n readonly listed: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n readonly mcpPaths: readonly string[];\n /** Advertised by this package rather than by po-domain. */\n readonly listed?: boolean;\n}\n\nconst LOCAL_ONLY_MCP_PACKAGES = new Set([\n '@agimon-ai/po-author',\n '@agimon-ai/po-git',\n '@agimon-ai/po-goal',\n '@agimon-ai/po-help',\n '@agimon-ai/po-loop',\n '@agimon-ai/po-user-feedback',\n '@agimon-ai/po-voice',\n '@agimon-ai/po-workflow',\n]);\n\nasync function isMcpSkillPlugin(entry: PluginEntry): Promise<boolean> {\n if (entry.source?.type === 'npm') return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);\n try {\n const manifest = JSON.parse(await readFile(path.join(entry.directory, 'package.json'), 'utf8')) as unknown;\n if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) return false;\n const name = (manifest as { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);\n } catch (error) {\n // A package manifest is optional for catalog plugins, but a present identity\n // that cannot be read or parsed must never make the plugin remotely eligible.\n return (error as NodeJS.ErrnoException).code === 'ENOENT';\n }\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.po/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? os.homedir();\n const repoSkills = path.join(execution.repoRoot, '.po', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...listDomainNames(execution.repoRoot, home));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(resolvePluginEntries(execution.repoRoot, [domain], [], home));\n const mcpPluginRoots = (\n await Promise.all(\n plugins.map(async (plugin) =>\n (await isMcpSkillPlugin(plugin)) ? path.join(plugin.directory, 'skills') : undefined,\n ),\n )\n ).filter((root): root is string => root !== undefined);\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) {\n const paths = [...collected.skillDirectories];\n sources.push({\n domain,\n paths,\n // Exact collected paths retain this domain's per-plugin skill subset.\n // A plugin root would also admit skills loaded for another domain.\n mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots)),\n });\n }\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (resolveSharedSkills(execution.repoRoot, [domain], home)) {\n sources.push({ domain, paths: sharedPaths, mcpPaths: sharedPaths, listed: true });\n }\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills], mcpPaths: [repoSkills], listed: true });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const listed = sources\n .filter((source) => source.listed === true)\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const mcpGroups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n mcpGroups,\n listed,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n","import type { DoomHeadlessCommand } from '@agimon-ai/po-core/headless';\nimport type { Skill } from '@earendil-works/pi-coding-agent';\n\nimport { expandDeferredSkillCommand, type DeferredSkillSnapshot } from '../../../../../services/deferredSkills';\nimport type { ServerSkillGroup } from '../../../../../services/serverInventory';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, SKILLS_COMMAND } from '../../../../../types/skills';\n\n/**\n * One command per skill, gated by whatever activates that skill.\n *\n * Expansion always reads the full inventory, not the group: a command is only\n * dispatchable while its condition holds, so by the time one runs its skill is\n * active, and looking it up in the union keeps one lookup table instead of one\n * per domain.\n */\nfunction skillCommand(skill: Skill, domain: string | undefined, inventory: DeferredSkillSnapshot): DoomHeadlessCommand {\n return {\n ...(domain === undefined ? {} : { when: { domain } }),\n name: `${SKILL_COMMAND_PREFIX}${skill.name}`,\n description: skill.description,\n execute: async (args, execution) => {\n const text = `${SKILL_INVOCATION_PREFIX}${skill.name}${args ? ` ${args}` : ''}`;\n const expanded = expandDeferredSkillCommand(text, inventory.skills);\n if (expanded === text) throw new Error(`Skill '${skill.name}' is no longer readable.`);\n await execution.session.prompt(expanded);\n },\n };\n}\n\nexport function createSkillCommands(\n inventory: DeferredSkillSnapshot,\n catalog: string,\n groups: readonly ServerSkillGroup[],\n): readonly DoomHeadlessCommand[] {\n return [\n {\n name: SKILLS_COMMAND,\n description: 'List discovered skills or invoke one by name.',\n async execute(args, execution) {\n const requested = args.trim();\n if (requested) {\n await execution.session.prompt(\n expandDeferredSkillCommand(`${SKILL_INVOCATION_PREFIX}${requested}`, inventory.skills),\n );\n return;\n }\n await execution.client.notify({\n title: 'Po skills',\n body: catalog,\n level: 'info',\n });\n },\n },\n ...groups.flatMap((group) => group.skills.map((skill) => skillCommand(skill, group.domain, inventory))),\n ];\n}\n","import { readFile } from 'node:fs/promises';\n\nimport type { DoomHeadlessHostService } from '@agimon-ai/po-core/headless';\nimport { type DoomServerSessionPlugin, packageResourcePath, readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nimport { mountMcpSkills } from '../../../../../services/mcpSkills';\nimport { createSessionLoadSkillTool } from '../../../../../services/mcpSkillTools';\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\n\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n tools: [createSessionLoadSkillTool(agent)],\n services: [mountMcpSkills(mcpGroups, signal)],\n resources: [\n // Each skill joins the prompt's one <available_skills> list, gated like the rest.\n ...listed.flatMap((group) =>\n group.skills.map((skill) => ({\n name: skill.name,\n description: skill.description,\n path: skill.filePath,\n kind: 'skill' as const,\n ...(group.domain === undefined\n ? {}\n : { when: { domain: group.domain, attribution: { kind: 'domain' as const, mode: group.domain } } }),\n read: () => readFile(skill.filePath, 'utf8'),\n })),\n ),\n ...[\n {\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, contributing a runtime skill directory, or publishing activation-gated Help prompts and diagnostic tools.',\n },\n {\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Diagnose missing or shadowed skills and invoke the narrowest available guidance.\",\n },\n ].map((skill) => ({\n ...skill,\n when: { state: { 'minor-mode': 'help' }, attribution: { kind: 'minor' as const, mode: 'help' } },\n kind: 'skill' as const,\n path: packageResourcePath(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n read: () => readPackageResource(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n })),\n ],\n };\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, signal)\n : { commands: [], resources: [] };\n return { value: runtime, services: runtime.services };\n});\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.commands!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.resources!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\n/** The session agent's skill loader; the Pi CLI keeps Pi's own skill handling. */\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.tools?.[0],\n { cardinality: 'optional' },\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport commandSkills from '../src/extensions/workspaces/sessions/(backend)/command/skills.server';\nimport resourceSkills from '../src/extensions/workspaces/sessions/(backend)/resource/skills.server';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\n/* v8 ignore next -- generated normalization branch */\nconst defined = <T>(values: readonly (T | undefined)[]): T[] =>\n values.filter((value): value is T => value !== undefined);\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/po-skill',\n providerOverrides: composeServerProviderOverrides(rootSession),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get commands(): DoomServerSessionPlugin['commands'] { return [...many(commandSkills, contextSession)]; },\n get resources(): DoomServerSessionPlugin['resources'] { return [...many(resourceSkills, contextSession)]; },\n get tools(): DoomServerSessionPlugin['tools'] { return defined([via({ name: 'load_skill' }, at(toolLoadSkill, contextSession))]); },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.mjs","names":["rootSession","commandSkills","resourceSkills","toolLoadSkill"],"sources":["../../src/services/mcpSkills/index.ts","../../src/schemas/mcpSkillTools.ts","../../src/services/mcpSkillTools/index.ts","../../src/services/deferredSkills/index.ts","../../src/services/serverInventory/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillCommands.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/skillServer.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/command/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/skills.server.ts","../../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server.ts","../../generated/server.ts"],"sourcesContent":["import type { DoomMcpPluginContext, DoomMcpSkill } from '@agimon-ai/po-core/mcpFacet';\nimport { BACKGROUND_CONTEXT, getOrThrow, withAbortSignal, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport type { ServerSkillGroup } from '../serverInventory';\n\nexport const MCP_SKILLS_SERVICE = 'doom/discovered-mcp-skills';\n\nexport interface McpSkillProvider {\n list(context: DoomMcpPluginContext): readonly DoomMcpSkill[];\n}\n\n/** Explicit remote provider, never a projection of the local resource registry. */\nexport function mountMcpSkills(groups: readonly ServerSkillGroup[], fs: FileSystem, signal: AbortSignal) {\n return (context: Context): void => {\n context.provide(MCP_SKILLS_SERVICE, {\n list(remote) {\n signal.throwIfAborted();\n remote.signal.throwIfAborted();\n const domains = remote.selection.read().domains;\n const skills = new Map<string, DoomMcpSkill>();\n for (const group of groups) {\n if (group.domain !== undefined && !domains.includes(group.domain)) continue;\n for (const skill of group.skills) {\n // Match command precedence, including shared copies across domains.\n if (skills.has(skill.name)) continue;\n skills.set(skill.name, {\n name: skill.name,\n description: skill.description,\n async read() {\n const readSignal = AbortSignal.any([signal, remote.signal]);\n readSignal.throwIfAborted();\n const text = getOrThrow(\n await fs.readTextFile(skill.filePath, withAbortSignal(readSignal, BACKGROUND_CONTEXT)),\n );\n readSignal.throwIfAborted();\n return text;\n },\n });\n }\n }\n return [...skills.values()];\n },\n } satisfies McpSkillProvider);\n };\n}\n\nexport function bindMcpSkills(context: DoomMcpPluginContext): readonly DoomMcpSkill[] {\n const provider = context.services.get<McpSkillProvider>(MCP_SKILLS_SERVICE);\n if (!provider) throw new Error('Discovered MCP skills are unavailable.');\n return provider.list(context);\n}\n","import { Type } from 'typebox';\n\nexport const searchSkillsParameters = Type.Object({ query: Type.Optional(Type.String()) });\nexport const loadSkillParameters = Type.Object({ name: Type.String() });\n","import type { DoomHeadlessHostService, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport { loadSkillParameters, searchSkillsParameters } from '../../schemas/mcpSkillTools';\n\nfunction text(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction skills(context: Parameters<DoomHeadlessTool['execute']>[4]) {\n if (context.mcpSkills === undefined) throw new Error('Remote skill access is unavailable.');\n return context.mcpSkills;\n}\n\nexport function createSearchSkillsTool(): DoomHeadlessTool<typeof searchSkillsParameters> {\n return {\n // web-plugin-tool-renderers: ignore search_skills (remote MCP only)\n name: 'search_skills',\n label: 'Search skills',\n description: 'Discover repository and active-domain skills available to this remote connection.',\n parameters: searchSkillsParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { query }, _signal, _onUpdate, context) {\n const needle = query?.trim().toLocaleLowerCase();\n const available = await skills(context).list();\n const matches =\n needle === undefined || needle === ''\n ? available\n : available.filter(({ name, description }) => `${name}\\n${description}`.toLocaleLowerCase().includes(needle));\n return { ...text(JSON.stringify(matches)), structuredContent: { skills: matches } };\n },\n };\n}\n\n/**\n * The session agent's way into a skill. Its skills are listed in the prompt, which Po\n * controls, so it needs no search; a remote agent's prompt is not ours, hence the pair above.\n */\nexport function createSessionLoadSkillTool(\n host: Pick<DoomHeadlessHostService, 'readSkill'>,\n): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (server session only, shown as a generic call)\n // DOOM_LOAD_SKILL_TOOL, spelled out so the renderer rule can read it.\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full instructions of a skill from the available skills list by exact name.',\n parameters: loadSkillParameters,\n executionMode: 'parallel',\n async execute(_toolCallId, { name }) {\n const body = host.readSkill(name);\n if (body === undefined) return { ...text(`No skill named ${name} is available in this session.`), isError: true };\n return text(body);\n },\n };\n}\n\nexport function createLoadSkillTool(): DoomHeadlessTool<typeof loadSkillParameters> {\n return {\n // web-plugin-tool-renderers: ignore load_skill (remote MCP only)\n name: 'load_skill',\n label: 'Load skill',\n description: 'Load the full Markdown instructions for an available remote skill by exact name.',\n parameters: loadSkillParameters,\n annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },\n async execute(_toolCallId, { name }, _signal, _onUpdate, context) {\n return text(await skills(context).read(name));\n },\n };\n}\n","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\nimport type { DeferredSkillLoaderOptions, DeferredSkillSnapshot } from '../../types/skills';\n\nexport type { DeferredSkillLoaderOptions, DeferredSkillSnapshot } from '../../types/skills';\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 Po 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\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","import path from 'node:path';\n\nimport {\n listDomainNamesAsync,\n resolvePluginEntriesAsync,\n resolveSharedSkillsAsync,\n type PluginEntry,\n} from '@agimon-ai/po-config/domains';\nimport { readHarnessState } from '@agimon-ai/po-config/harnessState';\nimport type { DoomHeadlessExecutionContext } from '@agimon-ai/po-core/headless';\nimport { BACKGROUND_CONTEXT, nodeHost, withAbortSignal, type FileSystem } from '@agimon-ai/po-core/platform';\nimport { materializePluginEntries } from '@agimon-ai/po-domain/plugins';\nimport { collectResources, discoverSkillsAsync } from '@agimon-ai/po-domain/resources';\nimport { formatSkillsForPrompt } from '@earendil-works/pi-coding-agent';\n\nimport type { ServerSkillInventory } from '../../types/skills';\nimport { DeferredSkillLoader } from '../deferredSkills';\n\nexport type { ServerSkillGroup, ServerSkillInventory } from '../../types/skills';\n\ninterface SkillSource {\n readonly domain?: string;\n readonly paths: readonly string[];\n readonly mcpPaths: readonly string[];\n /** Advertised by this package rather than by po-domain. */\n readonly listed?: boolean;\n}\n\nconst LOCAL_ONLY_MCP_PACKAGES = new Set([\n '@agimon-ai/po-author',\n '@agimon-ai/po-git',\n '@agimon-ai/po-goal',\n '@agimon-ai/po-help',\n '@agimon-ai/po-loop',\n '@agimon-ai/po-user-feedback',\n '@agimon-ai/po-voice',\n '@agimon-ai/po-workflow',\n]);\n\ntype IoContext = Parameters<FileSystem['readTextFile']>[1];\n\nasync function isMcpSkillPlugin(fs: FileSystem, entry: PluginEntry, context: IoContext): Promise<boolean> {\n if (entry.source?.type === 'npm') return !LOCAL_ONLY_MCP_PACKAGES.has(entry.source.package);\n // A package manifest is optional for catalog plugins, but a present identity\n // that cannot be read or parsed must never make the plugin remotely eligible.\n const source = await fs.readTextFile(path.join(entry.directory, 'package.json'), context);\n if (!source.ok) return source.error.code === 'not_found';\n try {\n const manifest = JSON.parse(source.value) as unknown;\n if (typeof manifest !== 'object' || manifest === null || Array.isArray(manifest)) return false;\n const name = (manifest as { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 && name.trim() === name && !LOCAL_ONLY_MCP_PACKAGES.has(name);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether a discovered skill came from one of these paths.\n *\n * A source holds exact SKILL.md files for plugin and shared skills, but a bare\n * directory for `.po/skills`, so membership is containment rather than\n * equality.\n */\nfunction within(filePath: string, roots: readonly string[]): boolean {\n const resolved = path.resolve(filePath);\n return roots.some((root) => {\n const base = path.resolve(root);\n return resolved === base || resolved.startsWith(`${base}${path.sep}`);\n });\n}\n/**\n * Every skill the session could reach, grouped by what activates it.\n *\n * A cockpit `/domains` switch reconciles contributions rather than rebuilding\n * the facet: the session declaration is mounted once. So enumerating only the\n * launch selection would freeze the command list at whatever was selected when\n * the session host was built. Enumerating every domain up front and tagging\n * each group with its `when` lets the existing kernel refresh do the work, the\n * same way domainServerResources registers per-domain skill resources.\n */\nexport async function discoverServerSkills(\n execution: DoomHeadlessExecutionContext,\n fs: FileSystem,\n signal: AbortSignal,\n): Promise<ServerSkillInventory> {\n signal.throwIfAborted();\n const context = withAbortSignal(signal, BACKGROUND_CONTEXT);\n const state = readHarnessState(execution.environment);\n const home = execution.environment.HOME ?? nodeHost.homeDirectory();\n const repoSkills = path.join(execution.repoRoot, '.po', 'skills');\n const sources: SkillSource[] = [];\n const diagnostics: string[] = [];\n // A malformed domain is a configuration error in one domain, so it must not\n // remove every other domain's skills. Enumerating the whole manifest is new\n // exposure here: reading the launch selection could not fail this way.\n const domains: string[] = [];\n try {\n domains.push(...(await listDomainNamesAsync(fs, execution.repoRoot, context, home)));\n } catch (error) {\n diagnostics.push(`Domain skills are unavailable: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Domain-scoped plugin skills come first. The host keeps the first command\n // registered for a name, so this ordering reproduces the collector's rule\n // that a selected plugin outranks a shared fallback of the same name.\n for (const domain of domains) {\n signal.throwIfAborted();\n try {\n const plugins = await materializePluginEntries(\n await resolvePluginEntriesAsync(fs, execution.repoRoot, [domain], [], context, home),\n );\n const mcpPluginRoots = (\n await Promise.all(\n plugins.map(async (plugin) =>\n (await isMcpSkillPlugin(fs, plugin, context)) ? path.join(plugin.directory, 'skills') : undefined,\n ),\n )\n ).filter((root): root is string => root !== undefined);\n const collected = await collectResources(execution.repoRoot, plugins, {\n agents: false,\n mcp: false,\n sharedSkills: false,\n });\n try {\n if (collected.skillDirectories.length > 0) {\n const paths = [...collected.skillDirectories];\n sources.push({\n domain,\n paths,\n // Exact collected paths retain this domain's per-plugin skill subset.\n // A plugin root would also admit skills loaded for another domain.\n mcpPaths: paths.filter((skillPath) => within(skillPath, mcpPluginRoots)),\n });\n }\n } finally {\n await collected.cleanup();\n }\n } catch (error) {\n diagnostics.push(`Domain '${domain}': ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Shared .claude/skills are not owned by any one domain, and a domain can opt\n // out of them. `when` cannot express \"unless every selected domain opts out\",\n // but registering a copy per domain that keeps them says the same thing: the\n // group is active if any such domain is selected, which is exactly what\n // resolveSharedSkills computes.\n signal.throwIfAborted();\n const shared = await discoverSkillsAsync(execution.repoRoot, path.join(execution.repoRoot, '.claude', 'skills'));\n if (shared.length > 0) {\n const sharedPaths = shared.map((skill) => skill.path);\n for (const domain of domains) {\n if (await resolveSharedSkillsAsync(fs, execution.repoRoot, [domain], context, home)) {\n sources.push({ domain, paths: sharedPaths, mcpPaths: sharedPaths, listed: true });\n }\n }\n }\n\n // Repository-local skills belong to the repository, not to a selection.\n sources.push({ paths: [repoSkills], mcpPaths: [repoSkills], listed: true });\n\n signal.throwIfAborted();\n // One walk for every group. Pi drops a path it has already loaded, so the\n // loader gets the unique set and each group selects its own skills back out.\n const unique = [...new Set([...sources.flatMap((source) => source.paths), ...(state.skillDirectories ?? [])])];\n const inventory = await new DeferredSkillLoader({ cwd: execution.cwd, skillPaths: unique }).ready();\n signal.throwIfAborted();\n\n const groups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const listed = sources\n .filter((source) => source.listed === true)\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.paths)),\n }))\n .filter((group) => group.skills.length > 0);\n const mcpGroups = sources\n .map((source) => ({\n ...(source.domain === undefined ? {} : { domain: source.domain }),\n skills: inventory.skills.filter((skill) => within(skill.filePath, source.mcpPaths)),\n }))\n .filter((group) => group.skills.length > 0);\n\n // The catalog still describes the active selection rather than everything on\n // disk, because it is prompt text rather than a menu.\n const activeSkills = inventory.skills.filter((skill) =>\n within(skill.filePath, [...(state.skillDirectories ?? []), repoSkills]),\n );\n return {\n inventory: { skills: inventory.skills, diagnostics: [...inventory.diagnostics, ...diagnostics] },\n groups,\n mcpGroups,\n listed,\n catalog: formatSkillsForPrompt(activeSkills) || '(no discovered skills)',\n };\n}\n","import type { DoomHeadlessCommand } from '@agimon-ai/po-core/headless';\nimport type { Skill } from '@earendil-works/pi-coding-agent';\n\nimport { expandDeferredSkillCommand, type DeferredSkillSnapshot } from '../../../../../services/deferredSkills';\nimport type { ServerSkillGroup } from '../../../../../services/serverInventory';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, SKILLS_COMMAND } from '../../../../../types/skills';\n\n/**\n * One command per skill, gated by whatever activates that skill.\n *\n * Expansion always reads the full inventory, not the group: a command is only\n * dispatchable while its condition holds, so by the time one runs its skill is\n * active, and looking it up in the union keeps one lookup table instead of one\n * per domain.\n */\nfunction skillCommand(skill: Skill, domain: string | undefined, inventory: DeferredSkillSnapshot): DoomHeadlessCommand {\n return {\n ...(domain === undefined ? {} : { when: { domain } }),\n name: `${SKILL_COMMAND_PREFIX}${skill.name}`,\n description: skill.description,\n execute: async (args, execution) => {\n const text = `${SKILL_INVOCATION_PREFIX}${skill.name}${args ? ` ${args}` : ''}`;\n const expanded = expandDeferredSkillCommand(text, inventory.skills);\n if (expanded === text) throw new Error(`Skill '${skill.name}' is no longer readable.`);\n await execution.session.prompt(expanded);\n },\n };\n}\n\nexport function createSkillCommands(\n inventory: DeferredSkillSnapshot,\n catalog: string,\n groups: readonly ServerSkillGroup[],\n): readonly DoomHeadlessCommand[] {\n return [\n {\n name: SKILLS_COMMAND,\n description: 'List discovered skills or invoke one by name.',\n async execute(args, execution) {\n const requested = args.trim();\n if (requested) {\n await execution.session.prompt(\n expandDeferredSkillCommand(`${SKILL_INVOCATION_PREFIX}${requested}`, inventory.skills),\n );\n return;\n }\n await execution.client.notify({\n title: 'Po skills',\n body: catalog,\n level: 'info',\n });\n },\n },\n ...groups.flatMap((group) => group.skills.map((skill) => skillCommand(skill, group.domain, inventory))),\n ];\n}\n","import type { DoomHeadlessHostService } from '@agimon-ai/po-core/headless';\nimport { BACKGROUND_CONTEXT, getOrThrow, type FileSystem } from '@agimon-ai/po-core/platform';\nimport { type DoomServerSessionPlugin, packageResourcePath, readPackageResource } from '@agimon-ai/po-core/serverFacet';\n\nimport { mountMcpSkills } from '../../../../../services/mcpSkills';\nimport { createSessionLoadSkillTool } from '../../../../../services/mcpSkillTools';\nimport { discoverServerSkills } from '../../../../../services/serverInventory';\nimport { createSkillCommands } from './skillCommands';\n\nexport async function createSkillServer(\n agent: DoomHeadlessHostService,\n fs: FileSystem,\n signal: AbortSignal,\n): Promise<DoomServerSessionPlugin> {\n const { inventory, catalog, groups, mcpGroups, listed } = await discoverServerSkills(agent.context, fs, signal);\n return {\n commands: createSkillCommands(inventory, catalog, groups),\n tools: [createSessionLoadSkillTool(agent)],\n services: [mountMcpSkills(mcpGroups, fs, signal)],\n resources: [\n // Each skill joins the prompt's one <available_skills> list, gated like the rest.\n ...listed.flatMap((group) =>\n group.skills.map((skill) => ({\n name: skill.name,\n description: skill.description,\n path: skill.filePath,\n kind: 'skill' as const,\n ...(group.domain === undefined\n ? {}\n : { when: { domain: group.domain, attribution: { kind: 'domain' as const, mode: group.domain } } }),\n read: async () => getOrThrow(await fs.readTextFile(skill.filePath, BACKGROUND_CONTEXT)),\n })),\n ),\n ...[\n {\n name: 'po-author-skill',\n description:\n 'Author and distribute Po agent skills. Use when creating a SKILL.md, contributing a runtime skill directory, or publishing activation-gated Help prompts and diagnostic tools.',\n },\n {\n name: 'po-use-skill',\n description:\n \"Use Po's skill catalog and deferred discovery. Diagnose missing or shadowed skills and invoke the narrowest available guidance.\",\n },\n ].map((skill) => ({\n ...skill,\n when: { state: { 'minor-mode': 'help' }, attribution: { kind: 'minor' as const, mode: 'help' } },\n kind: 'skill' as const,\n path: packageResourcePath(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n read: () => readPackageResource(import.meta.url, `src/prompts/${skill.name}/SKILL.md`),\n })),\n ],\n };\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport { requireDoomFs } from '@agimon-ai/po-core/platform';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nimport { createSkillServer } from './_lib/skillServer';\n\nexport default defineRoot(async ({ agent, context: root, signal }: DoomServerPluginContext) => {\n const runtime: DoomServerSessionPlugin = agent\n ? await createSkillServer(agent, requireDoomFs(root), signal)\n : { commands: [], resources: [] };\n return { value: runtime, services: runtime.services };\n});\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.commands!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution } from '@agimon-ai/po-core/extensionFile';\nimport type { WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.resources!,\n { cardinality: 'many' },\n);\n","import { defineRoutedContribution, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\n\n/** The session agent's skill loader; the Pi CLI keeps Pi's own skill handling. */\nexport default defineRoutedContribution(\n (context: WithRoot<DoomServerPluginContext, DoomServerSessionPlugin>) => context.root.tools?.[0],\n { cardinality: 'optional' },\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-core/serverFacet';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport commandSkills from '../src/extensions/workspaces/sessions/(backend)/command/skills.server';\nimport resourceSkills from '../src/extensions/workspaces/sessions/(backend)/resource/skills.server';\nimport toolLoadSkill from '../src/extensions/workspaces/sessions/(backend)/tool/load_skill.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\ntype ManyFactory<T, C> = (context: C) => T | readonly T[];\nconst many = <T, C>(value: T | readonly T[] | ManyFactory<T, C>, context: C): readonly T[] => {\n const resolved = typeof value === 'function' ? (value as ManyFactory<T, C>)(context) : value;\n return Array.isArray(resolved) ? resolved : [resolved as T];\n};\n/* v8 ignore next -- generated normalization branch */\nconst defined = <T>(values: readonly (T | undefined)[]): T[] =>\n values.filter((value): value is T => value !== undefined);\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/po-skill',\n providerOverrides: composeServerProviderOverrides(rootSession),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get commands(): DoomServerSessionPlugin['commands'] { return [...many(commandSkills, contextSession)]; },\n get resources(): DoomServerSessionPlugin['resources'] { return [...many(resourceSkills, contextSession)]; },\n get tools(): DoomServerSessionPlugin['tools'] { return defined([via({ name: 'load_skill' }, at(toolLoadSkill, contextSession))]); },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Skill } from "@earendil-works/pi-coding-agent";
1
2
  //#region src/types/skills.d.ts
2
3
  export declare const SKILLS_COMMAND = "skills";
3
4
  export declare const SKILL_INVOCATION_PREFIX = "/skill:";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/skills.ts"],"mappings":";qBAAa;qBACA;qBACA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/skills.ts"],"mappings":";;qBAEa;qBACA;qBACA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- import { n as SKILL_COMMAND_PREFIX, r as SKILL_INVOCATION_PREFIX, t as SKILLS_COMMAND } from "./skills-Ccvg1Hcz.mjs";
2
- export { SKILLS_COMMAND, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX };
1
+ export { t as SKILLS_COMMAND, n as SKILL_COMMAND_PREFIX, r as SKILL_INVOCATION_PREFIX } from "./skills-Ccvg1Hcz.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"skills-Ccvg1Hcz.mjs","names":[],"sources":["../src/types/skills.ts"],"sourcesContent":["export const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\n"],"mappings":";AAAA,MAAa,iBAAiB;AAC9B,MAAa,0BAA0B;AACvC,MAAa,uBAAuB"}
1
+ {"version":3,"file":"skills-Ccvg1Hcz.mjs","names":[],"sources":["../src/types/skills.ts"],"sourcesContent":["import type { Skill } from '@earendil-works/pi-coding-agent';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\n\nexport interface DeferredSkillSnapshot {\n skills: Skill[];\n diagnostics: 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/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly mcpGroups: readonly ServerSkillGroup[];\n /**\n * The shared and repository skills this package advertises itself. Domain plugin skills are\n * left out: po-domain already contributes each one, and listing them twice was noise.\n */\n readonly listed: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n"],"mappings":";AAEA,MAAa,iBAAiB;AAC9B,MAAa,0BAA0B;AACvC,MAAa,uBAAuB"}
@@ -1 +1 @@
1
- {"version":3,"file":"skills-ComtgYb1.cjs","names":[],"sources":["../src/types/skills.ts"],"sourcesContent":["export const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\n"],"mappings":";AAAA,MAAa,iBAAiB;AAC9B,MAAa,0BAA0B;AACvC,MAAa,uBAAuB"}
1
+ {"version":3,"file":"skills-ComtgYb1.cjs","names":[],"sources":["../src/types/skills.ts"],"sourcesContent":["import type { Skill } from '@earendil-works/pi-coding-agent';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\n\nexport interface DeferredSkillSnapshot {\n skills: Skill[];\n diagnostics: 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/** Skills and the domain whose selection activates them, or always-on when absent. */\nexport interface ServerSkillGroup {\n readonly domain?: string;\n readonly skills: readonly Skill[];\n}\n\nexport interface ServerSkillInventory {\n /** Every skill any selection can reach, so an activated command can still expand. */\n readonly inventory: DeferredSkillSnapshot;\n readonly groups: readonly ServerSkillGroup[];\n readonly mcpGroups: readonly ServerSkillGroup[];\n /**\n * The shared and repository skills this package advertises itself. Domain plugin skills are\n * left out: po-domain already contributes each one, and listing them twice was noise.\n */\n readonly listed: readonly ServerSkillGroup[];\n readonly catalog: string;\n}\n"],"mappings":";AAEA,MAAa,iBAAiB;AAC9B,MAAa,0BAA0B;AACvC,MAAa,uBAAuB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-skill",
3
- "version": "0.0.0-alpha.2",
3
+ "version": "0.0.0-alpha.3",
4
4
  "description": "Session skill catalogue, deferred skill discovery, and the skill browser for Po.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -65,25 +65,25 @@
65
65
  "access": "public"
66
66
  },
67
67
  "dependencies": {
68
- "@agimon-ai/po-config": "0.0.0-alpha.2",
69
- "@agimon-ai/po-core": "0.0.0-alpha.2",
70
- "@agimon-ai/po-domain": "0.0.0-alpha.2",
71
- "@agimon-ai/po-telemetry": "0.0.0-alpha.2",
72
- "@agimon-ai/po-web-components": "0.0.0-alpha.2",
68
+ "@agimon-ai/po-config": "0.0.0-alpha.3",
69
+ "@agimon-ai/po-core": "0.0.0-alpha.3",
70
+ "@agimon-ai/po-domain": "0.0.0-alpha.3",
71
+ "@agimon-ai/po-telemetry": "0.0.0-alpha.3",
72
+ "@agimon-ai/po-web-components": "0.0.0-alpha.3",
73
73
  "@deepseek-ai/cordis": "4.0.4",
74
74
  "gpt-tokenizer": "4.0.0",
75
- "typebox": "1.3.34"
75
+ "typebox": "1.3.36"
76
76
  },
77
77
  "devDependencies": {
78
- "@agimon-ai/po-build": "0.0.0-alpha.2",
79
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.0-alpha.2",
78
+ "@agimon-ai/po-build": "0.0.0-alpha.3",
79
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.0-alpha.3",
80
80
  "@deepseek-ai/cordis": "4.0.4",
81
81
  "@earendil-works/pi-coding-agent": "1.2.0",
82
- "@types/node": "26.6.4",
82
+ "@types/node": "25.9.9",
83
83
  "@types/react": "19.3.0",
84
84
  "@vitest/coverage-v8": "5.0.3",
85
85
  "react": "19.3.0",
86
- "tsdown": "0.23.0",
86
+ "tsdown": "0.23.1",
87
87
  "typescript": "7.0.2",
88
88
  "vitest": "5.0.3"
89
89
  },