@agimon-ai/doompi-skill 0.0.1-alpha.2 → 0.0.1-alpha.20

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.
Files changed (51) hide show
  1. package/README.md +20 -13
  2. package/dist/adapters/helpSkills.cjs +1 -1
  3. package/dist/adapters/helpSkills.cjs.map +1 -1
  4. package/dist/adapters/helpSkills.mjs +1 -1
  5. package/dist/adapters/helpSkills.mjs.map +1 -1
  6. package/dist/adapters/pi/extension.cjs +1 -1
  7. package/dist/adapters/pi/extension.cjs.map +1 -1
  8. package/dist/adapters/pi/extension.d.cts +1 -6
  9. package/dist/adapters/pi/extension.d.cts.map +1 -1
  10. package/dist/adapters/pi/extension.d.mts +1 -6
  11. package/dist/adapters/pi/extension.d.mts.map +1 -1
  12. package/dist/adapters/pi/extension.mjs +1 -1
  13. package/dist/adapters/pi/extension.mjs.map +1 -1
  14. package/dist/adapters/pi/skillReadiness.cjs +3 -3
  15. package/dist/adapters/pi/skillReadiness.cjs.map +1 -1
  16. package/dist/adapters/pi/skillReadiness.mjs +3 -3
  17. package/dist/adapters/pi/skillReadiness.mjs.map +1 -1
  18. package/dist/adapters/skillCatalog.cjs.map +1 -1
  19. package/dist/adapters/skillCatalog.d.cts +2 -2
  20. package/dist/adapters/skillCatalog.d.mts +2 -2
  21. package/dist/adapters/skillCatalog.mjs.map +1 -1
  22. package/dist/commands/skillsCommand.cjs +1 -1
  23. package/dist/commands/skillsCommand.cjs.map +1 -1
  24. package/dist/commands/skillsCommand.mjs +1 -1
  25. package/dist/commands/skillsCommand.mjs.map +1 -1
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.cts +1 -3
  28. package/dist/index.d.mts +1 -3
  29. package/dist/index.mjs +1 -1
  30. package/dist/types/skills.cjs +1 -1
  31. package/dist/types/skills.cjs.map +1 -1
  32. package/dist/types/skills.d.cts +1 -0
  33. package/dist/types/skills.d.mts +1 -0
  34. package/dist/types/skills.mjs +1 -1
  35. package/dist/types/skills.mjs.map +1 -1
  36. package/llms.txt +10 -2
  37. package/package.json +25 -16
  38. package/src/prompts/doompi-author-skill/SKILL.md +33 -0
  39. package/src/prompts/doompi-use-skill/SKILL.md +18 -0
  40. package/dist/adapters/helpSkills.d.cts +0 -27
  41. package/dist/adapters/helpSkills.d.cts.map +0 -1
  42. package/dist/adapters/helpSkills.d.mts +0 -27
  43. package/dist/adapters/helpSkills.d.mts.map +0 -1
  44. package/dist/adapters/skillSources.cjs +0 -2
  45. package/dist/adapters/skillSources.cjs.map +0 -1
  46. package/dist/adapters/skillSources.d.cts +0 -23
  47. package/dist/adapters/skillSources.d.cts.map +0 -1
  48. package/dist/adapters/skillSources.d.mts +0 -23
  49. package/dist/adapters/skillSources.d.mts.map +0 -1
  50. package/dist/adapters/skillSources.mjs +0 -2
  51. package/dist/adapters/skillSources.mjs.map +0 -1
package/README.md CHANGED
@@ -1,19 +1,19 @@
1
1
  # @agimon-ai/doompi-skill
2
2
 
3
- The session skill catalogue for [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi).
3
+ The session skill catalog for [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi).
4
4
 
5
5
  Pi loads skills from the directories it is given at startup. DoomPi adds two things on top: skills
6
- that are discovered lazily rather than loaded eagerly, and a registry other extensions publish their
7
- own skill directories to.
6
+ that are discovered lazily rather than loaded eagerly, and a registry where other extensions
7
+ publish their own skill directories.
8
8
 
9
9
  ## What it registers
10
10
 
11
- | Surface | Behaviour |
12
- | -------------------- | ------------------------------------------------------------------------------------------------ |
13
- | `/skills` | a fullscreen browser over everything this session loaded and everything another domain would add |
14
- | `SPC e s` | the leader binding for the same browser |
15
- | `input` hook | expands `/skill:<name>` for a skill that was discovered but not loaded |
16
- | `before_agent_start` | appends discovered skills to the system prompt, and surfaces discovery diagnostics once |
11
+ | Surface | Behavior |
12
+ | -------------------- | -------------------------------------------------------------------------------------------------- |
13
+ | `/skills` | a fullscreen browser over everything this session loaded and everything any other domain would add |
14
+ | `SPC e s` | the leader binding for the same browser |
15
+ | `input` hook | expands `/skill:<name>` for a skill that was discovered but not loaded |
16
+ | `before_agent_start` | appends discovered skills to the system prompt, and surfaces discovery diagnostics once |
17
17
 
18
18
  ## Deferred discovery
19
19
 
@@ -25,10 +25,17 @@ diagnostics.
25
25
 
26
26
  ## Contributing skill directories
27
27
 
28
- Any extension can publish directories into the catalogue over the
29
- `doom:pi:skills:v1:*` protocol in `@agimon-ai/doompi-extension-contracts/skills`. Contributions are
30
- keyed by source rather than by handle, so a reload that re-registers the same package replaces its
31
- entry instead of doubling it.
28
+ The package provides the session-owned `doom/skill-sources` Cordis service from
29
+ `@agimon-ai/doompi-extension-contracts/skills`. Extensions register their directories inside
30
+ `ctx.inject([DOOM_SKILL_SOURCES_SERVICE], ...)`; the returned handle is disposed automatically when
31
+ either the contributor or provider unloads. Contributions are keyed by source, so a replacement
32
+ generation takes the previous package slot without doubling it.
33
+
34
+ ## Help guidance
35
+
36
+ While the Help minor mode is active, this package contributes `doompi-author-skill` for creating
37
+ and distributing skills and `doompi-use-skill` for browsing, invoking, and diagnosing them. Both
38
+ prompts are published under `src/prompts` and routed through this package's [`llms.txt`](./llms.txt).
32
39
 
33
40
  ## Installation
34
41
 
@@ -1,2 +1,2 @@
1
- let e=require("@earendil-works/pi-coding-agent"),t=require("@agimon-ai/doompi-extension-contracts/help");function n(e){return e.slice(0,t.DOOM_HELP_MAX_DIAGNOSTIC_LENGTH)}function r(t){return{name:t.name,description:t.description,filePath:t.filePath,baseDir:t.baseDir,sourceInfo:(0,e.createSyntheticSourceInfo)(t.filePath,{source:t.source,scope:`temporary`,origin:`package`,baseDir:t.baseDir}),disableModelInvocation:!1}}function i(e){return!e||e.activation===`inactive`?[]:[...e.skills].sort((e,t)=>e.source.localeCompare(t.source)||e.name.localeCompare(t.name)||e.filePath.localeCompare(t.filePath))}function a(e){return e?e.diagnostics.map(e=>n(`Help ${e.source??`host`} [${e.code}]: ${e.message}`)):[]}function o(e,o,s=[]){let c=new Set([...e.normalSkills.map(e=>e.name),...e.deferredSkills.map(e=>e.name),...e.normalSkillNames??[]]),l=[],u=[...a(o),...s.map(n)];for(let e of i(o)){if(c.has(e.name)){u.push(n(`Help ${e.source} [HELP_SKILL_COLLISION]: '${e.name}' was ignored because a normal skill wins.`));continue}c.add(e.name),l.push(r(e))}let d=u.slice(0,t.DOOM_HELP_MAX_DIAGNOSTICS),f=[...e.deferredSkills,...l];return{skills:[...e.normalSkills,...f],additionalSkills:f,helpSkills:l,diagnostics:d,diagnosticKey:`${o?.hostGeneration??`absent`}:${o?.revision??0}:${d.join(``)}`}}function s(e){let r=[],i=(0,t.createDoomHelpSnapshotClient)(e,{source:`@agimon-ai/doompi`,onError(e){let t=e instanceof Error?e.message:String(e);r.splice(0,r.length,n(`Help client [HELP_SNAPSHOT_FAILED]: ${t}`))}}),a=!1;return{merge(e){return o(e,i.getSnapshot(),r)},dispose(){a||(a=!0,i.dispose(),r.splice(0))}}}exports.createActiveHelpSkillView=s,exports.mergeActiveHelpSkills=o;
1
+ let e=require("@earendil-works/pi-coding-agent"),t=require("@agimon-ai/doompi-extension-contracts/help");function n(e){return e.slice(0,t.DOOM_HELP_MAX_DIAGNOSTIC_LENGTH)}function r(t){return{name:t.name,description:t.description,filePath:t.filePath,baseDir:t.baseDir,sourceInfo:(0,e.createSyntheticSourceInfo)(t.filePath,{source:t.source,scope:`temporary`,origin:`package`,baseDir:t.baseDir}),disableModelInvocation:!1}}function i(e){return!e||e.activation===`inactive`?[]:[...e.skills].sort((e,t)=>e.source.localeCompare(t.source)||e.name.localeCompare(t.name)||e.filePath.localeCompare(t.filePath))}function a(e){return e?e.diagnostics.map(e=>n(`Help ${e.source??`host`} [${e.code}]: ${e.message}`)):[]}function o(e,o,s=[]){let c=new Set([...e.normalSkills.map(e=>e.name),...e.normalSkillNames??[]]),l=e.deferredSkills.filter(e=>!c.has(e.name)),u=new Set([...c,...l.map(e=>e.name)]),d=[],f=[...a(o),...s.map(n)];for(let e of i(o)){if(u.has(e.name)){f.push(n(`Help ${e.source} [HELP_SKILL_COLLISION]: '${e.name}' was ignored because a normal skill wins.`));continue}u.add(e.name),d.push(r(e))}let p=f.slice(0,t.DOOM_HELP_MAX_DIAGNOSTICS),m=[...l,...d];return{skills:[...e.normalSkills,...m],additionalSkills:m,helpSkills:d,diagnostics:p,diagnosticKey:`${o?.hostGeneration??`absent`}:${o?.revision??0}:${p.join(``)}`}}function s(){let e,t=!1;return{bind(n){if(t)throw Error(`The Help skill view is disposed.`);let r=Symbol(n.generation),i=e;e=void 0,i?.unsubscribe();let a={token:r,snapshot:n.getSnapshot(),unsubscribe:n.subscribeSnapshot(t=>{e?.token===r&&(e.snapshot=t)})};e=a;let o=!1;return()=>{o||(o=!0,a.unsubscribe(),e?.token===r&&(e=void 0))}},merge(t){return o(t,e?.snapshot)},dispose(){if(t)return;t=!0;let n=e;e=void 0,n?.unsubscribe()}}}exports.createActiveHelpSkillView=s,exports.mergeActiveHelpSkills=o;
2
2
  //# sourceMappingURL=helpSkills.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpSkills.cjs","names":["DOOM_HELP_MAX_DIAGNOSTIC_LENGTH","createSyntheticSourceInfo","DOOM_HELP_MAX_DIAGNOSTICS","createDoomHelpSnapshotClient"],"sources":["../../src/adapters/helpSkills.ts"],"sourcesContent":["import type { DoomExtensionHost } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n createDoomHelpSnapshotClient,\n DOOM_HELP_MAX_DIAGNOSTIC_LENGTH,\n DOOM_HELP_MAX_DIAGNOSTICS,\n type DoomHelpSkill,\n type DoomHelpSnapshot,\n type DoomHelpSnapshotClient,\n} from '@agimon-ai/doompi-extension-contracts/help';\nimport { createSyntheticSourceInfo, type ExtensionAPI, type Skill } from '@earendil-works/pi-coding-agent';\n\nconst HELP_CLIENT_SOURCE = '@agimon-ai/doompi';\n\nexport interface SkillInventoryInput {\n normalSkills: readonly Skill[];\n deferredSkills: readonly Skill[];\n /** Names from Pi's command catalog when full normal Skill records are unavailable. */\n normalSkillNames?: readonly string[];\n}\n\nexport interface MergedSkillInventory {\n /** Static, deferred, then accepted Help skills in invocation-precedence order. */\n skills: readonly Skill[];\n /** Deferred and Help skills that Doom Pi must append to Pi's existing prompt inventory. */\n additionalSkills: readonly Skill[];\n /** Accepted Help skills, retained separately for the Help catalog group. */\n helpSkills: readonly Skill[];\n diagnostics: readonly string[];\n diagnosticKey: string;\n}\n\nexport interface ActiveHelpSkillView {\n merge(input: SkillInventoryInput): MergedSkillInventory;\n dispose(): void;\n}\n\nfunction boundedDiagnostic(message: string): string {\n return message.slice(0, DOOM_HELP_MAX_DIAGNOSTIC_LENGTH);\n}\n\nfunction toSkill(skill: DoomHelpSkill): Skill {\n return {\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n sourceInfo: createSyntheticSourceInfo(skill.filePath, {\n source: skill.source,\n scope: 'temporary',\n origin: 'package',\n baseDir: skill.baseDir,\n }),\n disableModelInvocation: false,\n };\n}\n\nfunction activeHelpSkills(snapshot: DoomHelpSnapshot | undefined): DoomHelpSkill[] {\n if (!snapshot || snapshot.activation === 'inactive') return [];\n return [...snapshot.skills].sort(\n (left, right) =>\n left.source.localeCompare(right.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n}\n\nfunction snapshotDiagnostics(snapshot: DoomHelpSnapshot | undefined): string[] {\n if (!snapshot) return [];\n return snapshot.diagnostics.map((entry) =>\n boundedDiagnostic(`Help ${entry.source ?? 'host'} [${entry.code}]: ${entry.message}`),\n );\n}\n\nexport function mergeActiveHelpSkills(\n input: SkillInventoryInput,\n snapshot: DoomHelpSnapshot | undefined,\n clientDiagnostics: readonly string[] = [],\n): MergedSkillInventory {\n const occupiedNames = new Set([\n ...input.normalSkills.map((skill) => skill.name),\n ...input.deferredSkills.map((skill) => skill.name),\n ...(input.normalSkillNames ?? []),\n ]);\n const helpSkills: Skill[] = [];\n const diagnostics = [...snapshotDiagnostics(snapshot), ...clientDiagnostics.map(boundedDiagnostic)];\n\n for (const candidate of activeHelpSkills(snapshot)) {\n if (occupiedNames.has(candidate.name)) {\n diagnostics.push(\n boundedDiagnostic(\n `Help ${candidate.source} [HELP_SKILL_COLLISION]: '${candidate.name}' was ignored because a normal skill wins.`,\n ),\n );\n continue;\n }\n occupiedNames.add(candidate.name);\n helpSkills.push(toSkill(candidate));\n }\n\n const boundedDiagnostics = diagnostics.slice(0, DOOM_HELP_MAX_DIAGNOSTICS);\n const additionalSkills = [...input.deferredSkills, ...helpSkills];\n return {\n skills: [...input.normalSkills, ...additionalSkills],\n additionalSkills,\n helpSkills,\n diagnostics: boundedDiagnostics,\n diagnosticKey: `${snapshot?.hostGeneration ?? 'absent'}:${snapshot?.revision ?? 0}:${boundedDiagnostics.join('\\u0001')}`,\n };\n}\n\nexport function createActiveHelpSkillView(pi: Pick<ExtensionAPI, 'events' | 'on'>): ActiveHelpSkillView {\n const clientDiagnostics: string[] = [];\n const client: DoomHelpSnapshotClient = createDoomHelpSnapshotClient(pi as unknown as DoomExtensionHost, {\n source: HELP_CLIENT_SOURCE,\n onError(error) {\n const message = error instanceof Error ? error.message : String(error);\n clientDiagnostics.splice(\n 0,\n clientDiagnostics.length,\n boundedDiagnostic(`Help client [HELP_SNAPSHOT_FAILED]: ${message}`),\n );\n },\n });\n let disposed = false;\n return {\n merge(input) {\n return mergeActiveHelpSkills(input, client.getSnapshot(), clientDiagnostics);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n client.dispose();\n clientDiagnostics.splice(0);\n },\n };\n}\n"],"mappings":"yGAoCA,SAAS,EAAkB,EAAyB,CAClD,OAAO,EAAQ,MAAM,EAAGA,EAAAA,+BAA+B,CACzD,CAEA,SAAS,EAAQ,EAA6B,CAC5C,MAAO,CACL,KAAM,EAAM,KACZ,YAAa,EAAM,YACnB,SAAU,EAAM,SAChB,QAAS,EAAM,QACf,YAAA,EAAYC,EAAAA,0BAAAA,CAA0B,EAAM,SAAU,CACpD,OAAQ,EAAM,OACd,MAAO,YACP,OAAQ,UACR,QAAS,EAAM,OACjB,CAAC,EACD,uBAAwB,EAC1B,CACF,CAEA,SAAS,EAAiB,EAAyD,CAEjF,MADI,CAAC,GAAY,EAAS,aAAe,WAAmB,CAAC,EACtD,CAAC,GAAG,EAAS,MAAM,CAAC,CAAC,MACzB,EAAM,IACL,EAAK,OAAO,cAAc,EAAM,MAAM,GACtC,EAAK,KAAK,cAAc,EAAM,IAAI,GAClC,EAAK,SAAS,cAAc,EAAM,QAAQ,CAC9C,CACF,CAEA,SAAS,EAAoB,EAAkD,CAE7E,OADK,EACE,EAAS,YAAY,IAAK,GAC/B,EAAkB,QAAQ,EAAM,QAAU,OAAO,IAAI,EAAM,KAAK,KAAK,EAAM,SAAS,CACtF,EAHsB,CAAC,CAIzB,CAEA,SAAgB,EACd,EACA,EACA,EAAuC,CAAC,EAClB,CACtB,IAAM,EAAgB,IAAI,IAAI,CAC5B,GAAG,EAAM,aAAa,IAAK,GAAU,EAAM,IAAI,EAC/C,GAAG,EAAM,eAAe,IAAK,GAAU,EAAM,IAAI,EACjD,GAAI,EAAM,kBAAoB,CAAC,CACjC,CAAC,EACK,EAAsB,CAAC,EACvB,EAAc,CAAC,GAAG,EAAoB,CAAQ,EAAG,GAAG,EAAkB,IAAI,CAAiB,CAAC,EAElG,IAAK,IAAM,KAAa,EAAiB,CAAQ,EAAG,CAClD,GAAI,EAAc,IAAI,EAAU,IAAI,EAAG,CACrC,EAAY,KACV,EACE,QAAQ,EAAU,OAAO,4BAA4B,EAAU,KAAK,2CACtE,CACF,EACA,QACF,CACA,EAAc,IAAI,EAAU,IAAI,EAChC,EAAW,KAAK,EAAQ,CAAS,CAAC,CACpC,CAEA,IAAM,EAAqB,EAAY,MAAM,EAAGC,EAAAA,yBAAyB,EACnE,EAAmB,CAAC,GAAG,EAAM,eAAgB,GAAG,CAAU,EAChE,MAAO,CACL,OAAQ,CAAC,GAAG,EAAM,aAAc,GAAG,CAAgB,EACnD,mBACA,aACA,YAAa,EACb,cAAe,GAAG,GAAU,gBAAkB,SAAS,GAAG,GAAU,UAAY,EAAE,GAAG,EAAmB,KAAK,GAAQ,GACvH,CACF,CAEA,SAAgB,EAA0B,EAA8D,CACtG,IAAM,EAA8B,CAAC,EAC/B,GAAA,EAAiCC,EAAAA,6BAAAA,CAA6B,EAAoC,CACtG,OAAQ,oBACR,QAAQ,EAAO,CACb,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACrE,EAAkB,OAChB,EACA,EAAkB,OAClB,EAAkB,uCAAuC,GAAS,CACpE,CACF,CACF,CAAC,EACG,EAAW,GACf,MAAO,CACL,MAAM,EAAO,CACX,OAAO,EAAsB,EAAO,EAAO,YAAY,EAAG,CAAiB,CAC7E,EACA,SAAU,CACJ,IACJ,EAAW,GACX,EAAO,QAAQ,EACf,EAAkB,OAAO,CAAC,EAC5B,CACF,CACF"}
1
+ {"version":3,"file":"helpSkills.cjs","names":["DOOM_HELP_MAX_DIAGNOSTIC_LENGTH","createSyntheticSourceInfo","DOOM_HELP_MAX_DIAGNOSTICS"],"sources":["../../src/adapters/helpSkills.ts"],"sourcesContent":["import {\n DOOM_HELP_MAX_DIAGNOSTIC_LENGTH,\n DOOM_HELP_MAX_DIAGNOSTICS,\n type DoomHelpService,\n type DoomHelpSkill,\n type DoomHelpSnapshot,\n} from '@agimon-ai/doompi-extension-contracts/help';\nimport { createSyntheticSourceInfo, type Skill } from '@earendil-works/pi-coding-agent';\n\nexport interface SkillInventoryInput {\n normalSkills: readonly Skill[];\n deferredSkills: readonly Skill[];\n /** Names from Pi's command catalog when full normal Skill records are unavailable. */\n normalSkillNames?: readonly string[];\n}\n\nexport interface MergedSkillInventory {\n /** Pi-native, unique deferred, then accepted Help skills in invocation-precedence order. */\n skills: readonly Skill[];\n /** Deferred and Help skills that are absent from Pi's existing prompt inventory. */\n additionalSkills: readonly Skill[];\n /** Accepted Help skills, retained separately for the Help catalog group. */\n helpSkills: readonly Skill[];\n diagnostics: readonly string[];\n diagnosticKey: string;\n}\n\nexport interface ActiveHelpSkillView {\n bind(service: DoomHelpService): () => void;\n merge(input: SkillInventoryInput): MergedSkillInventory;\n dispose(): void;\n}\n\nfunction boundedDiagnostic(message: string): string {\n return message.slice(0, DOOM_HELP_MAX_DIAGNOSTIC_LENGTH);\n}\n\nfunction toSkill(skill: DoomHelpSkill): Skill {\n return {\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n sourceInfo: createSyntheticSourceInfo(skill.filePath, {\n source: skill.source,\n scope: 'temporary',\n origin: 'package',\n baseDir: skill.baseDir,\n }),\n disableModelInvocation: false,\n };\n}\n\nfunction activeHelpSkills(snapshot: DoomHelpSnapshot | undefined): DoomHelpSkill[] {\n if (!snapshot || snapshot.activation === 'inactive') return [];\n return [...snapshot.skills].sort(\n (left, right) =>\n left.source.localeCompare(right.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n}\n\nfunction snapshotDiagnostics(snapshot: DoomHelpSnapshot | undefined): string[] {\n if (!snapshot) return [];\n return snapshot.diagnostics.map((entry) =>\n boundedDiagnostic(`Help ${entry.source ?? 'host'} [${entry.code}]: ${entry.message}`),\n );\n}\n\nexport function mergeActiveHelpSkills(\n input: SkillInventoryInput,\n snapshot: DoomHelpSnapshot | undefined,\n clientDiagnostics: readonly string[] = [],\n): MergedSkillInventory {\n const nativeNames = new Set([...input.normalSkills.map((skill) => skill.name), ...(input.normalSkillNames ?? [])]);\n const deferredSkills = input.deferredSkills.filter((skill) => !nativeNames.has(skill.name));\n const occupiedNames = new Set([...nativeNames, ...deferredSkills.map((skill) => skill.name)]);\n const helpSkills: Skill[] = [];\n const diagnostics = [...snapshotDiagnostics(snapshot), ...clientDiagnostics.map(boundedDiagnostic)];\n\n for (const candidate of activeHelpSkills(snapshot)) {\n if (occupiedNames.has(candidate.name)) {\n diagnostics.push(\n boundedDiagnostic(\n `Help ${candidate.source} [HELP_SKILL_COLLISION]: '${candidate.name}' was ignored because a normal skill wins.`,\n ),\n );\n continue;\n }\n occupiedNames.add(candidate.name);\n helpSkills.push(toSkill(candidate));\n }\n\n const boundedDiagnostics = diagnostics.slice(0, DOOM_HELP_MAX_DIAGNOSTICS);\n const additionalSkills = [...deferredSkills, ...helpSkills];\n return {\n skills: [...input.normalSkills, ...additionalSkills],\n additionalSkills,\n helpSkills,\n diagnostics: boundedDiagnostics,\n diagnosticKey: `${snapshot?.hostGeneration ?? 'absent'}:${snapshot?.revision ?? 0}:${boundedDiagnostics.join('\\u0001')}`,\n };\n}\n\nexport function createActiveHelpSkillView(): ActiveHelpSkillView {\n let binding: { readonly token: symbol; snapshot: DoomHelpSnapshot; unsubscribe(): void } | undefined;\n let disposed = false;\n return {\n bind(service) {\n if (disposed) throw new Error('The Help skill view is disposed.');\n const token = Symbol(service.generation);\n const current = binding;\n binding = undefined;\n current?.unsubscribe();\n const next: { readonly token: symbol; snapshot: DoomHelpSnapshot; unsubscribe(): void } = {\n token,\n snapshot: service.getSnapshot(),\n unsubscribe: service.subscribeSnapshot((snapshot) => {\n if (binding?.token === token) binding.snapshot = snapshot;\n }),\n };\n binding = next;\n let bindingDisposed = false;\n return () => {\n if (bindingDisposed) return;\n bindingDisposed = true;\n next.unsubscribe();\n if (binding?.token === token) binding = undefined;\n };\n },\n merge(input) {\n return mergeActiveHelpSkills(input, binding?.snapshot);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n const current = binding;\n binding = undefined;\n current?.unsubscribe();\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{createSyntheticSourceInfo as e}from"@earendil-works/pi-coding-agent";import{DOOM_HELP_MAX_DIAGNOSTICS as t,DOOM_HELP_MAX_DIAGNOSTIC_LENGTH as n,createDoomHelpSnapshotClient as r}from"@agimon-ai/doompi-extension-contracts/help";function i(e){return e.slice(0,n)}function a(t){return{name:t.name,description:t.description,filePath:t.filePath,baseDir:t.baseDir,sourceInfo:e(t.filePath,{source:t.source,scope:`temporary`,origin:`package`,baseDir:t.baseDir}),disableModelInvocation:!1}}function o(e){return!e||e.activation===`inactive`?[]:[...e.skills].sort((e,t)=>e.source.localeCompare(t.source)||e.name.localeCompare(t.name)||e.filePath.localeCompare(t.filePath))}function s(e){return e?e.diagnostics.map(e=>i(`Help ${e.source??`host`} [${e.code}]: ${e.message}`)):[]}function c(e,n,r=[]){let c=new Set([...e.normalSkills.map(e=>e.name),...e.deferredSkills.map(e=>e.name),...e.normalSkillNames??[]]),l=[],u=[...s(n),...r.map(i)];for(let e of o(n)){if(c.has(e.name)){u.push(i(`Help ${e.source} [HELP_SKILL_COLLISION]: '${e.name}' was ignored because a normal skill wins.`));continue}c.add(e.name),l.push(a(e))}let d=u.slice(0,t),f=[...e.deferredSkills,...l];return{skills:[...e.normalSkills,...f],additionalSkills:f,helpSkills:l,diagnostics:d,diagnosticKey:`${n?.hostGeneration??`absent`}:${n?.revision??0}:${d.join(``)}`}}function l(e){let t=[],n=r(e,{source:`@agimon-ai/doompi`,onError(e){let n=e instanceof Error?e.message:String(e);t.splice(0,t.length,i(`Help client [HELP_SNAPSHOT_FAILED]: ${n}`))}}),a=!1;return{merge(e){return c(e,n.getSnapshot(),t)},dispose(){a||(a=!0,n.dispose(),t.splice(0))}}}export{l as createActiveHelpSkillView,c as mergeActiveHelpSkills};
1
+ import{createSyntheticSourceInfo as e}from"@earendil-works/pi-coding-agent";import{DOOM_HELP_MAX_DIAGNOSTICS as t,DOOM_HELP_MAX_DIAGNOSTIC_LENGTH as n}from"@agimon-ai/doompi-extension-contracts/help";function r(e){return e.slice(0,n)}function i(t){return{name:t.name,description:t.description,filePath:t.filePath,baseDir:t.baseDir,sourceInfo:e(t.filePath,{source:t.source,scope:`temporary`,origin:`package`,baseDir:t.baseDir}),disableModelInvocation:!1}}function a(e){return!e||e.activation===`inactive`?[]:[...e.skills].sort((e,t)=>e.source.localeCompare(t.source)||e.name.localeCompare(t.name)||e.filePath.localeCompare(t.filePath))}function o(e){return e?e.diagnostics.map(e=>r(`Help ${e.source??`host`} [${e.code}]: ${e.message}`)):[]}function s(e,n,s=[]){let c=new Set([...e.normalSkills.map(e=>e.name),...e.normalSkillNames??[]]),l=e.deferredSkills.filter(e=>!c.has(e.name)),u=new Set([...c,...l.map(e=>e.name)]),d=[],f=[...o(n),...s.map(r)];for(let e of a(n)){if(u.has(e.name)){f.push(r(`Help ${e.source} [HELP_SKILL_COLLISION]: '${e.name}' was ignored because a normal skill wins.`));continue}u.add(e.name),d.push(i(e))}let p=f.slice(0,t),m=[...l,...d];return{skills:[...e.normalSkills,...m],additionalSkills:m,helpSkills:d,diagnostics:p,diagnosticKey:`${n?.hostGeneration??`absent`}:${n?.revision??0}:${p.join(``)}`}}function c(){let e,t=!1;return{bind(n){if(t)throw Error(`The Help skill view is disposed.`);let r=Symbol(n.generation),i=e;e=void 0,i?.unsubscribe();let a={token:r,snapshot:n.getSnapshot(),unsubscribe:n.subscribeSnapshot(t=>{e?.token===r&&(e.snapshot=t)})};e=a;let o=!1;return()=>{o||(o=!0,a.unsubscribe(),e?.token===r&&(e=void 0))}},merge(t){return s(t,e?.snapshot)},dispose(){if(t)return;t=!0;let n=e;e=void 0,n?.unsubscribe()}}}export{c as createActiveHelpSkillView,s as mergeActiveHelpSkills};
2
2
  //# sourceMappingURL=helpSkills.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpSkills.mjs","names":[],"sources":["../../src/adapters/helpSkills.ts"],"sourcesContent":["import type { DoomExtensionHost } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n createDoomHelpSnapshotClient,\n DOOM_HELP_MAX_DIAGNOSTIC_LENGTH,\n DOOM_HELP_MAX_DIAGNOSTICS,\n type DoomHelpSkill,\n type DoomHelpSnapshot,\n type DoomHelpSnapshotClient,\n} from '@agimon-ai/doompi-extension-contracts/help';\nimport { createSyntheticSourceInfo, type ExtensionAPI, type Skill } from '@earendil-works/pi-coding-agent';\n\nconst HELP_CLIENT_SOURCE = '@agimon-ai/doompi';\n\nexport interface SkillInventoryInput {\n normalSkills: readonly Skill[];\n deferredSkills: readonly Skill[];\n /** Names from Pi's command catalog when full normal Skill records are unavailable. */\n normalSkillNames?: readonly string[];\n}\n\nexport interface MergedSkillInventory {\n /** Static, deferred, then accepted Help skills in invocation-precedence order. */\n skills: readonly Skill[];\n /** Deferred and Help skills that Doom Pi must append to Pi's existing prompt inventory. */\n additionalSkills: readonly Skill[];\n /** Accepted Help skills, retained separately for the Help catalog group. */\n helpSkills: readonly Skill[];\n diagnostics: readonly string[];\n diagnosticKey: string;\n}\n\nexport interface ActiveHelpSkillView {\n merge(input: SkillInventoryInput): MergedSkillInventory;\n dispose(): void;\n}\n\nfunction boundedDiagnostic(message: string): string {\n return message.slice(0, DOOM_HELP_MAX_DIAGNOSTIC_LENGTH);\n}\n\nfunction toSkill(skill: DoomHelpSkill): Skill {\n return {\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n sourceInfo: createSyntheticSourceInfo(skill.filePath, {\n source: skill.source,\n scope: 'temporary',\n origin: 'package',\n baseDir: skill.baseDir,\n }),\n disableModelInvocation: false,\n };\n}\n\nfunction activeHelpSkills(snapshot: DoomHelpSnapshot | undefined): DoomHelpSkill[] {\n if (!snapshot || snapshot.activation === 'inactive') return [];\n return [...snapshot.skills].sort(\n (left, right) =>\n left.source.localeCompare(right.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n}\n\nfunction snapshotDiagnostics(snapshot: DoomHelpSnapshot | undefined): string[] {\n if (!snapshot) return [];\n return snapshot.diagnostics.map((entry) =>\n boundedDiagnostic(`Help ${entry.source ?? 'host'} [${entry.code}]: ${entry.message}`),\n );\n}\n\nexport function mergeActiveHelpSkills(\n input: SkillInventoryInput,\n snapshot: DoomHelpSnapshot | undefined,\n clientDiagnostics: readonly string[] = [],\n): MergedSkillInventory {\n const occupiedNames = new Set([\n ...input.normalSkills.map((skill) => skill.name),\n ...input.deferredSkills.map((skill) => skill.name),\n ...(input.normalSkillNames ?? []),\n ]);\n const helpSkills: Skill[] = [];\n const diagnostics = [...snapshotDiagnostics(snapshot), ...clientDiagnostics.map(boundedDiagnostic)];\n\n for (const candidate of activeHelpSkills(snapshot)) {\n if (occupiedNames.has(candidate.name)) {\n diagnostics.push(\n boundedDiagnostic(\n `Help ${candidate.source} [HELP_SKILL_COLLISION]: '${candidate.name}' was ignored because a normal skill wins.`,\n ),\n );\n continue;\n }\n occupiedNames.add(candidate.name);\n helpSkills.push(toSkill(candidate));\n }\n\n const boundedDiagnostics = diagnostics.slice(0, DOOM_HELP_MAX_DIAGNOSTICS);\n const additionalSkills = [...input.deferredSkills, ...helpSkills];\n return {\n skills: [...input.normalSkills, ...additionalSkills],\n additionalSkills,\n helpSkills,\n diagnostics: boundedDiagnostics,\n diagnosticKey: `${snapshot?.hostGeneration ?? 'absent'}:${snapshot?.revision ?? 0}:${boundedDiagnostics.join('\\u0001')}`,\n };\n}\n\nexport function createActiveHelpSkillView(pi: Pick<ExtensionAPI, 'events' | 'on'>): ActiveHelpSkillView {\n const clientDiagnostics: string[] = [];\n const client: DoomHelpSnapshotClient = createDoomHelpSnapshotClient(pi as unknown as DoomExtensionHost, {\n source: HELP_CLIENT_SOURCE,\n onError(error) {\n const message = error instanceof Error ? error.message : String(error);\n clientDiagnostics.splice(\n 0,\n clientDiagnostics.length,\n boundedDiagnostic(`Help client [HELP_SNAPSHOT_FAILED]: ${message}`),\n );\n },\n });\n let disposed = false;\n return {\n merge(input) {\n return mergeActiveHelpSkills(input, client.getSnapshot(), clientDiagnostics);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n client.dispose();\n clientDiagnostics.splice(0);\n },\n };\n}\n"],"mappings":"0OAoCA,SAAS,EAAkB,EAAyB,CAClD,OAAO,EAAQ,MAAM,EAAG,CAA+B,CACzD,CAEA,SAAS,EAAQ,EAA6B,CAC5C,MAAO,CACL,KAAM,EAAM,KACZ,YAAa,EAAM,YACnB,SAAU,EAAM,SAChB,QAAS,EAAM,QACf,WAAY,EAA0B,EAAM,SAAU,CACpD,OAAQ,EAAM,OACd,MAAO,YACP,OAAQ,UACR,QAAS,EAAM,OACjB,CAAC,EACD,uBAAwB,EAC1B,CACF,CAEA,SAAS,EAAiB,EAAyD,CAEjF,MADI,CAAC,GAAY,EAAS,aAAe,WAAmB,CAAC,EACtD,CAAC,GAAG,EAAS,MAAM,CAAC,CAAC,MACzB,EAAM,IACL,EAAK,OAAO,cAAc,EAAM,MAAM,GACtC,EAAK,KAAK,cAAc,EAAM,IAAI,GAClC,EAAK,SAAS,cAAc,EAAM,QAAQ,CAC9C,CACF,CAEA,SAAS,EAAoB,EAAkD,CAE7E,OADK,EACE,EAAS,YAAY,IAAK,GAC/B,EAAkB,QAAQ,EAAM,QAAU,OAAO,IAAI,EAAM,KAAK,KAAK,EAAM,SAAS,CACtF,EAHsB,CAAC,CAIzB,CAEA,SAAgB,EACd,EACA,EACA,EAAuC,CAAC,EAClB,CACtB,IAAM,EAAgB,IAAI,IAAI,CAC5B,GAAG,EAAM,aAAa,IAAK,GAAU,EAAM,IAAI,EAC/C,GAAG,EAAM,eAAe,IAAK,GAAU,EAAM,IAAI,EACjD,GAAI,EAAM,kBAAoB,CAAC,CACjC,CAAC,EACK,EAAsB,CAAC,EACvB,EAAc,CAAC,GAAG,EAAoB,CAAQ,EAAG,GAAG,EAAkB,IAAI,CAAiB,CAAC,EAElG,IAAK,IAAM,KAAa,EAAiB,CAAQ,EAAG,CAClD,GAAI,EAAc,IAAI,EAAU,IAAI,EAAG,CACrC,EAAY,KACV,EACE,QAAQ,EAAU,OAAO,4BAA4B,EAAU,KAAK,2CACtE,CACF,EACA,QACF,CACA,EAAc,IAAI,EAAU,IAAI,EAChC,EAAW,KAAK,EAAQ,CAAS,CAAC,CACpC,CAEA,IAAM,EAAqB,EAAY,MAAM,EAAG,CAAyB,EACnE,EAAmB,CAAC,GAAG,EAAM,eAAgB,GAAG,CAAU,EAChE,MAAO,CACL,OAAQ,CAAC,GAAG,EAAM,aAAc,GAAG,CAAgB,EACnD,mBACA,aACA,YAAa,EACb,cAAe,GAAG,GAAU,gBAAkB,SAAS,GAAG,GAAU,UAAY,EAAE,GAAG,EAAmB,KAAK,GAAQ,GACvH,CACF,CAEA,SAAgB,EAA0B,EAA8D,CACtG,IAAM,EAA8B,CAAC,EAC/B,EAAiC,EAA6B,EAAoC,CACtG,OAAQ,oBACR,QAAQ,EAAO,CACb,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACrE,EAAkB,OAChB,EACA,EAAkB,OAClB,EAAkB,uCAAuC,GAAS,CACpE,CACF,CACF,CAAC,EACG,EAAW,GACf,MAAO,CACL,MAAM,EAAO,CACX,OAAO,EAAsB,EAAO,EAAO,YAAY,EAAG,CAAiB,CAC7E,EACA,SAAU,CACJ,IACJ,EAAW,GACX,EAAO,QAAQ,EACf,EAAkB,OAAO,CAAC,EAC5B,CACF,CACF"}
1
+ {"version":3,"file":"helpSkills.mjs","names":[],"sources":["../../src/adapters/helpSkills.ts"],"sourcesContent":["import {\n DOOM_HELP_MAX_DIAGNOSTIC_LENGTH,\n DOOM_HELP_MAX_DIAGNOSTICS,\n type DoomHelpService,\n type DoomHelpSkill,\n type DoomHelpSnapshot,\n} from '@agimon-ai/doompi-extension-contracts/help';\nimport { createSyntheticSourceInfo, type Skill } from '@earendil-works/pi-coding-agent';\n\nexport interface SkillInventoryInput {\n normalSkills: readonly Skill[];\n deferredSkills: readonly Skill[];\n /** Names from Pi's command catalog when full normal Skill records are unavailable. */\n normalSkillNames?: readonly string[];\n}\n\nexport interface MergedSkillInventory {\n /** Pi-native, unique deferred, then accepted Help skills in invocation-precedence order. */\n skills: readonly Skill[];\n /** Deferred and Help skills that are absent from Pi's existing prompt inventory. */\n additionalSkills: readonly Skill[];\n /** Accepted Help skills, retained separately for the Help catalog group. */\n helpSkills: readonly Skill[];\n diagnostics: readonly string[];\n diagnosticKey: string;\n}\n\nexport interface ActiveHelpSkillView {\n bind(service: DoomHelpService): () => void;\n merge(input: SkillInventoryInput): MergedSkillInventory;\n dispose(): void;\n}\n\nfunction boundedDiagnostic(message: string): string {\n return message.slice(0, DOOM_HELP_MAX_DIAGNOSTIC_LENGTH);\n}\n\nfunction toSkill(skill: DoomHelpSkill): Skill {\n return {\n name: skill.name,\n description: skill.description,\n filePath: skill.filePath,\n baseDir: skill.baseDir,\n sourceInfo: createSyntheticSourceInfo(skill.filePath, {\n source: skill.source,\n scope: 'temporary',\n origin: 'package',\n baseDir: skill.baseDir,\n }),\n disableModelInvocation: false,\n };\n}\n\nfunction activeHelpSkills(snapshot: DoomHelpSnapshot | undefined): DoomHelpSkill[] {\n if (!snapshot || snapshot.activation === 'inactive') return [];\n return [...snapshot.skills].sort(\n (left, right) =>\n left.source.localeCompare(right.source) ||\n left.name.localeCompare(right.name) ||\n left.filePath.localeCompare(right.filePath),\n );\n}\n\nfunction snapshotDiagnostics(snapshot: DoomHelpSnapshot | undefined): string[] {\n if (!snapshot) return [];\n return snapshot.diagnostics.map((entry) =>\n boundedDiagnostic(`Help ${entry.source ?? 'host'} [${entry.code}]: ${entry.message}`),\n );\n}\n\nexport function mergeActiveHelpSkills(\n input: SkillInventoryInput,\n snapshot: DoomHelpSnapshot | undefined,\n clientDiagnostics: readonly string[] = [],\n): MergedSkillInventory {\n const nativeNames = new Set([...input.normalSkills.map((skill) => skill.name), ...(input.normalSkillNames ?? [])]);\n const deferredSkills = input.deferredSkills.filter((skill) => !nativeNames.has(skill.name));\n const occupiedNames = new Set([...nativeNames, ...deferredSkills.map((skill) => skill.name)]);\n const helpSkills: Skill[] = [];\n const diagnostics = [...snapshotDiagnostics(snapshot), ...clientDiagnostics.map(boundedDiagnostic)];\n\n for (const candidate of activeHelpSkills(snapshot)) {\n if (occupiedNames.has(candidate.name)) {\n diagnostics.push(\n boundedDiagnostic(\n `Help ${candidate.source} [HELP_SKILL_COLLISION]: '${candidate.name}' was ignored because a normal skill wins.`,\n ),\n );\n continue;\n }\n occupiedNames.add(candidate.name);\n helpSkills.push(toSkill(candidate));\n }\n\n const boundedDiagnostics = diagnostics.slice(0, DOOM_HELP_MAX_DIAGNOSTICS);\n const additionalSkills = [...deferredSkills, ...helpSkills];\n return {\n skills: [...input.normalSkills, ...additionalSkills],\n additionalSkills,\n helpSkills,\n diagnostics: boundedDiagnostics,\n diagnosticKey: `${snapshot?.hostGeneration ?? 'absent'}:${snapshot?.revision ?? 0}:${boundedDiagnostics.join('\\u0001')}`,\n };\n}\n\nexport function createActiveHelpSkillView(): ActiveHelpSkillView {\n let binding: { readonly token: symbol; snapshot: DoomHelpSnapshot; unsubscribe(): void } | undefined;\n let disposed = false;\n return {\n bind(service) {\n if (disposed) throw new Error('The Help skill view is disposed.');\n const token = Symbol(service.generation);\n const current = binding;\n binding = undefined;\n current?.unsubscribe();\n const next: { readonly token: symbol; snapshot: DoomHelpSnapshot; unsubscribe(): void } = {\n token,\n snapshot: service.getSnapshot(),\n unsubscribe: service.subscribeSnapshot((snapshot) => {\n if (binding?.token === token) binding.snapshot = snapshot;\n }),\n };\n binding = next;\n let bindingDisposed = false;\n return () => {\n if (bindingDisposed) return;\n bindingDisposed = true;\n next.unsubscribe();\n if (binding?.token === token) binding = undefined;\n };\n },\n merge(input) {\n return mergeActiveHelpSkills(input, binding?.snapshot);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n const current = binding;\n binding = undefined;\n current?.unsubscribe();\n },\n };\n}\n"],"mappings":"wMAiCA,SAAS,EAAkB,EAAyB,CAClD,OAAO,EAAQ,MAAM,EAAG,CAA+B,CACzD,CAEA,SAAS,EAAQ,EAA6B,CAC5C,MAAO,CACL,KAAM,EAAM,KACZ,YAAa,EAAM,YACnB,SAAU,EAAM,SAChB,QAAS,EAAM,QACf,WAAY,EAA0B,EAAM,SAAU,CACpD,OAAQ,EAAM,OACd,MAAO,YACP,OAAQ,UACR,QAAS,EAAM,OACjB,CAAC,EACD,uBAAwB,EAC1B,CACF,CAEA,SAAS,EAAiB,EAAyD,CAEjF,MADI,CAAC,GAAY,EAAS,aAAe,WAAmB,CAAC,EACtD,CAAC,GAAG,EAAS,MAAM,CAAC,CAAC,MACzB,EAAM,IACL,EAAK,OAAO,cAAc,EAAM,MAAM,GACtC,EAAK,KAAK,cAAc,EAAM,IAAI,GAClC,EAAK,SAAS,cAAc,EAAM,QAAQ,CAC9C,CACF,CAEA,SAAS,EAAoB,EAAkD,CAE7E,OADK,EACE,EAAS,YAAY,IAAK,GAC/B,EAAkB,QAAQ,EAAM,QAAU,OAAO,IAAI,EAAM,KAAK,KAAK,EAAM,SAAS,CACtF,EAHsB,CAAC,CAIzB,CAEA,SAAgB,EACd,EACA,EACA,EAAuC,CAAC,EAClB,CACtB,IAAM,EAAc,IAAI,IAAI,CAAC,GAAG,EAAM,aAAa,IAAK,GAAU,EAAM,IAAI,EAAG,GAAI,EAAM,kBAAoB,CAAC,CAAE,CAAC,EAC3G,EAAiB,EAAM,eAAe,OAAQ,GAAU,CAAC,EAAY,IAAI,EAAM,IAAI,CAAC,EACpF,EAAgB,IAAI,IAAI,CAAC,GAAG,EAAa,GAAG,EAAe,IAAK,GAAU,EAAM,IAAI,CAAC,CAAC,EACtF,EAAsB,CAAC,EACvB,EAAc,CAAC,GAAG,EAAoB,CAAQ,EAAG,GAAG,EAAkB,IAAI,CAAiB,CAAC,EAElG,IAAK,IAAM,KAAa,EAAiB,CAAQ,EAAG,CAClD,GAAI,EAAc,IAAI,EAAU,IAAI,EAAG,CACrC,EAAY,KACV,EACE,QAAQ,EAAU,OAAO,4BAA4B,EAAU,KAAK,2CACtE,CACF,EACA,QACF,CACA,EAAc,IAAI,EAAU,IAAI,EAChC,EAAW,KAAK,EAAQ,CAAS,CAAC,CACpC,CAEA,IAAM,EAAqB,EAAY,MAAM,EAAG,CAAyB,EACnE,EAAmB,CAAC,GAAG,EAAgB,GAAG,CAAU,EAC1D,MAAO,CACL,OAAQ,CAAC,GAAG,EAAM,aAAc,GAAG,CAAgB,EACnD,mBACA,aACA,YAAa,EACb,cAAe,GAAG,GAAU,gBAAkB,SAAS,GAAG,GAAU,UAAY,EAAE,GAAG,EAAmB,KAAK,GAAQ,GACvH,CACF,CAEA,SAAgB,GAAiD,CAC/D,IAAI,EACA,EAAW,GACf,MAAO,CACL,KAAK,EAAS,CACZ,GAAI,EAAU,MAAU,MAAM,kCAAkC,EAChE,IAAM,EAAQ,OAAO,EAAQ,UAAU,EACjC,EAAU,EAChB,EAAU,IAAA,GACV,GAAS,YAAY,EACrB,IAAM,EAAoF,CACxF,QACA,SAAU,EAAQ,YAAY,EAC9B,YAAa,EAAQ,kBAAmB,GAAa,CAC/C,GAAS,QAAU,IAAO,EAAQ,SAAW,EACnD,CAAC,CACH,EACA,EAAU,EACV,IAAI,EAAkB,GACtB,UAAa,CACP,IACJ,EAAkB,GAClB,EAAK,YAAY,EACb,GAAS,QAAU,IAAO,EAAU,IAAA,IAC1C,CACF,EACA,MAAM,EAAO,CACX,OAAO,EAAsB,EAAO,GAAS,QAAQ,CACvD,EACA,SAAU,CACR,GAAI,EAAU,OACd,EAAW,GACX,IAAM,EAAU,EAChB,EAAU,IAAA,GACV,GAAS,YAAY,CACvB,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("../helpSkills.cjs"),t=require("../../types/skills.cjs"),n=require("../../commands/skillsCommand.cjs"),r=require("../skillSources.cjs"),i=require("./skillReadiness.cjs");let a=require("@agimon-ai/doompi-config/harnessStore"),o=require("@agimon-ai/doompi-config/piContext"),s=require("@agimon-ai/doompi-ui/leader"),c=require("@deepseek-ai/cordis");function l(){let e,t,n;return{catalog:()=>e??=Promise.resolve().then(()=>require("../skillCatalog.cjs")),deferred:()=>t??=Promise.resolve().then(()=>require("../deferredSkills.cjs")),overlay:()=>n??=Promise.resolve().then(()=>require("../../tui/skillsOverlay.cjs"))}}async function u(u){let d=new c.Context,f=l(),p,m=()=>(p??=d.fiber.dispose(),p);try{d.effect(function*(){let c=r.provideSkillSources(u);yield()=>c.dispose();let l=e.createActiveHelpSkillView(u);yield()=>l.dispose(),yield(0,s.registerDoomLeaderContribution)(u,t.SKILLS_LEADER_CONTRIBUTION);let d=i.registerSkillReadiness(u,l,f.deferred);n.registerSkillsCommand(u,{extensionSources:()=>c.list(),inventory:i.skillInventory(l,d),repositoryRoot:async e=>(0,a.requireHarnessRoot)((0,o.requireDoomConfigContext)(e).harness),buildCatalog:async e=>(await f.catalog()).buildSkillCatalog(e),openOverlay:async(e,t)=>(await f.overlay()).openSkillsOverlay(e,t)})},t.LEADER_SOURCE)}catch(e){throw await m(),e}u.on(`session_shutdown`,m)}exports.default=u,exports.skillsExtension=u;
1
+ const e=require("../../types/skills.cjs"),t=require("../../commands/skillsCommand.cjs"),n=require("../helpSkills.cjs"),r=require("./skillReadiness.cjs");let i=require("@agimon-ai/doompi-config/harnessStore"),a=require("@agimon-ai/doompi-config"),o=require("@agimon-ai/doompi-extension-contracts/config"),s=require("@agimon-ai/doompi-extension-contracts/cordis-host"),c=require("@agimon-ai/doompi-extension-contracts/help"),l=require("@agimon-ai/doompi-extension-contracts/skills"),u=require("@agimon-ai/doompi-extension-contracts/ui-hub");function d(){let e,t,n;return{catalog:()=>e??=Promise.resolve().then(()=>require("../skillCatalog.cjs")),deferred:()=>t??=Promise.resolve().then(()=>require("../deferredSkills.cjs")),overlay:()=>n??=Promise.resolve().then(()=>require("../../tui/skillsOverlay.cjs"))}}function f(f,{pi:p}){let m=d(),h,g;f.inject([o.DOOM_CONFIG_SERVICE],e=>(h=e,()=>{h===e&&(h=void 0)}));let _=()=>{if(!h)throw Error(`Doom skill runtime is waiting for the session config service.`);return h},v=()=>{if(!g)throw Error(`Doom skill sources are waiting for the active session service.`);return g};f.inject([s.DOOM_CORDIS_SESSION_SERVICE],e=>{let t=e.get(s.DOOM_CORDIS_SESSION_SERVICE),n=(0,l.createDoomSkillSourcesService)(`${t.generation}:skill-sources`);return e.provide(l.DOOM_SKILL_SOURCES_SERVICE,n),g=n,()=>{g===n&&(g=void 0),n.dispose()}});let y=n.createActiveHelpSkillView();f.effect(()=>()=>y.dispose(),`${e.LEADER_SOURCE}/help`),f.inject([c.DOOM_HELP_SERVICE],t=>{let n=(0,c.requireDoomHelpService)(t),r=n.register({source:e.LEADER_SOURCE,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-author-skill`,description:`Author and distribute DoomPi 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 DoomPi package.`},{name:`doompi-use-skill`,description:`Use DoomPi'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.`}]}),i=y.bind(n);return()=>{i(),r.dispose()}});let b=r.registerSkillReadiness(p,y,m.deferred,_);t.registerSkillsCommand(p,{cordisContext:_,extensionSources:()=>v().list(),inventory:r.skillInventory(y,b),repositoryRoot:async e=>(0,i.requireHarnessRoot)((0,a.requireDoomConfigContext)(_()).harness),buildCatalog:async e=>(await m.catalog()).buildSkillCatalog(e),openOverlay:async(e,t)=>(await m.overlay()).openSkillsOverlay(e,t)}),f.inject([u.DOOM_UI_HUB_SERVICE],t=>{let n=(0,u.requireDoomUiHub)(t).registerLeader(e.SKILLS_LEADER_CONTRIBUTION);return()=>n.dispose()})}async function p(t){let n=await(0,s.connectDoomCordisHost)(t,e.LEADER_SOURCE),r=n.root.plugin(f,{pi:t});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;t.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}exports.default=p,exports.skillsExtension=p;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["Context","provideSkillSources","createActiveHelpSkillView","registerDoomLeaderContribution","SKILLS_LEADER_CONTRIBUTION","registerSkillReadiness","skillInventory","requireHarnessRoot","requireDoomConfigContext","LEADER_SOURCE"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerSkillsCommand } from '../../commands/skillsCommand.ts';\nimport { LEADER_SOURCE, SKILLS_LEADER_CONTRIBUTION } from '../../types/skills.ts';\nimport { createActiveHelpSkillView } from '../helpSkills.ts';\nimport { provideSkillSources } from '../skillSources.ts';\nimport { registerSkillReadiness, skillInventory } from './skillReadiness.ts';\n\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\n/**\n * The catalogue and the overlay load on first use.\n *\n * Discovery has to start with the session, but building the catalogue and\n * rendering it are only reached by `/skills`, so they stay off the startup path.\n */\nfunction lazyModules() {\n let catalog: Promise<typeof import('../skillCatalog.ts')> | undefined;\n let deferred: Promise<typeof import('../deferredSkills.ts')> | undefined;\n let overlay: Promise<typeof import('../../tui/skillsOverlay.ts')> | undefined;\n return {\n catalog: () => (catalog ??= import('../skillCatalog.ts')),\n deferred: () => (deferred ??= import('../deferredSkills.ts')),\n overlay: () => (overlay ??= import('../../tui/skillsOverlay.ts')),\n };\n}\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse.\n */\nexport async function skillsExtension(pi: ExtensionAPI): Promise<void> {\n const cordis = new Context();\n const load = lazyModules();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n const sources = provideSkillSources(pi);\n yield () => sources.dispose();\n\n const helpSkillView = createActiveHelpSkillView(pi);\n yield () => helpSkillView.dispose();\n\n yield registerDoomLeaderContribution(pi, SKILLS_LEADER_CONTRIBUTION);\n\n const readiness = registerSkillReadiness(pi, helpSkillView, load.deferred);\n registerSkillsCommand(pi, {\n extensionSources: () => sources.list(),\n inventory: skillInventory(helpSkillView, readiness),\n repositoryRoot: async (ctx: ExtensionContext) => requireHarnessRoot(requireDoomConfigContext(ctx).harness),\n buildCatalog: async (request) => (await load.catalog()).buildSkillCatalog(request),\n openOverlay: async (ctx, options) => (await load.overlay()).openSkillsOverlay(ctx, options),\n });\n }, LEADER_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default skillsExtension;\n"],"mappings":"2WAmBA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACJ,MAAO,CACL,YAAgB,IAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAY,qBAAA,CAAA,EAC5B,aAAiB,IAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAa,uBAAA,CAAA,EAC9B,YAAgB,IAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAY,6BAAA,CAAA,CAC9B,CACF,CAQA,eAAsB,EAAgB,EAAiC,CACrE,IAAM,EAAS,IAAIA,EAAAA,QACb,EAAO,EAAY,EACrB,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,IAAM,EAAUC,EAAAA,oBAAoB,CAAE,EACtC,SAAY,EAAQ,QAAQ,EAE5B,IAAM,EAAgBC,EAAAA,0BAA0B,CAAE,EAClD,SAAY,EAAc,QAAQ,EAElC,MAAA,EAAMC,EAAAA,+BAAAA,CAA+B,EAAIC,EAAAA,0BAA0B,EAEnE,IAAM,EAAYC,EAAAA,uBAAuB,EAAI,EAAe,EAAK,QAAQ,EACzE,EAAA,sBAAsB,EAAI,CACxB,qBAAwB,EAAQ,KAAK,EACrC,UAAWC,EAAAA,eAAe,EAAe,CAAS,EAClD,eAAgB,KAAO,KAAA,EAA0BC,EAAAA,mBAAAA,EAAAA,EAAmBC,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,OAAO,EACzG,aAAc,KAAO,KAAa,MAAM,EAAK,QAAQ,EAAA,CAAG,kBAAkB,CAAO,EACjF,YAAa,MAAO,EAAK,KAAa,MAAM,EAAK,QAAQ,EAAA,CAAG,kBAAkB,EAAK,CAAO,CAC5F,CAAC,CACH,EAAGC,EAAAA,aAAa,CAClB,OAAS,EAAO,CAEd,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
1
+ {"version":3,"file":"extension.cjs","names":["DOOM_CONFIG_SERVICE","DOOM_CORDIS_SESSION_SERVICE","createDoomSkillSourcesService","DOOM_SKILL_SOURCES_SERVICE","createActiveHelpSkillView","LEADER_SOURCE","DOOM_HELP_SERVICE","requireDoomHelpService","registerSkillReadiness","skillInventory","requireHarnessRoot","requireDoomConfigContext","DOOM_UI_HUB_SERVICE","requireDoomUiHub","SKILLS_LEADER_CONTRIBUTION","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n createDoomSkillSourcesService,\n DOOM_SKILL_SOURCES_SERVICE,\n type DoomSkillSourcesService,\n} from '@agimon-ai/doompi-extension-contracts/skills';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerSkillsCommand } from '../../commands/skillsCommand.ts';\nimport { LEADER_SOURCE, SKILLS_LEADER_CONTRIBUTION } from '../../types/skills.ts';\nimport { createActiveHelpSkillView } from '../helpSkills.ts';\nimport { registerSkillReadiness, skillInventory } from './skillReadiness.ts';\n\n/**\n * The catalogue and the overlay load on first use.\n *\n * Discovery has to start with the session, but building the catalogue and\n * rendering it are only reached by `/skills`, so they stay off the startup path.\n */\nfunction lazyModules() {\n let catalog: Promise<typeof import('../skillCatalog.ts')> | undefined;\n let deferred: Promise<typeof import('../deferredSkills.ts')> | undefined;\n let overlay: Promise<typeof import('../../tui/skillsOverlay.ts')> | undefined;\n return {\n catalog: () => (catalog ??= import('../skillCatalog.ts')),\n deferred: () => (deferred ??= import('../deferredSkills.ts')),\n overlay: () => (overlay ??= import('../../tui/skillsOverlay.ts')),\n };\n}\n\ninterface SkillPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction skillPlugin(cordis: Context, { pi }: SkillPluginConfig): void {\n const load = lazyModules();\n let activeContext: Context | undefined;\n let activeSources: DoomSkillSourcesService | undefined;\n cordis.inject([DOOM_CONFIG_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom skill runtime is waiting for the session config service.');\n return activeContext;\n };\n const requireSkillSources = (): DoomSkillSourcesService => {\n if (!activeSources) throw new Error('Doom skill sources are waiting for the active session service.');\n return activeSources;\n };\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const service = createDoomSkillSourcesService(`${session.generation}:skill-sources`);\n sessionContext.provide(DOOM_SKILL_SOURCES_SERVICE, service);\n activeSources = service;\n return () => {\n if (activeSources === service) activeSources = undefined;\n service.dispose();\n };\n });\n\n const helpSkillView = createActiveHelpSkillView();\n cordis.effect(() => () => helpSkillView.dispose(), `${LEADER_SOURCE}/help`);\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const help = requireDoomHelpService(helpContext);\n const contribution = help.register({\n source: LEADER_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-skill',\n description:\n 'Author and distribute DoomPi 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 DoomPi package.',\n },\n {\n name: 'doompi-use-skill',\n description:\n \"Use DoomPi'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 },\n ],\n });\n const unbind = helpSkillView.bind(help);\n return () => {\n unbind();\n contribution.dispose();\n };\n });\n const readiness = registerSkillReadiness(pi, helpSkillView, load.deferred, requireRuntimeContext);\n registerSkillsCommand(pi, {\n cordisContext: requireRuntimeContext,\n extensionSources: () => requireSkillSources().list(),\n inventory: skillInventory(helpSkillView, readiness),\n repositoryRoot: async (_ctx: ExtensionContext) =>\n requireHarnessRoot(requireDoomConfigContext(requireRuntimeContext()).harness),\n buildCatalog: async (request) => (await load.catalog()).buildSkillCatalog(request),\n openOverlay: async (ctx, options) => (await load.overlay()).openSkillsOverlay(ctx, options),\n });\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader(SKILLS_LEADER_CONTRIBUTION);\n return () => contribution.dispose();\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function skillsExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(skillPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default skillsExtension;\n"],"mappings":"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"}
@@ -1,11 +1,6 @@
1
1
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  //#region src/adapters/pi/extension.d.ts
3
- /**
4
- * The package's single standard Pi factory, and the owner of the cordis Context.
5
- *
6
- * Pi can reload an extension in-process, so every registration is yielded to the
7
- * fiber and one dispose on session shutdown unwinds them in reverse.
8
- */
3
+ /** The package's single standard Pi factory. */
9
4
  declare function skillsExtension(pi: ExtensionAPI): Promise<void>;
10
5
  //#endregion
11
6
  export { skillsExtension as default, skillsExtension };
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;iBAoCsB,gBAAgB,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;iBAoHsB,gBAAgB,IAAI,eAAe"}
@@ -1,11 +1,6 @@
1
1
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  //#region src/adapters/pi/extension.d.ts
3
- /**
4
- * The package's single standard Pi factory, and the owner of the cordis Context.
5
- *
6
- * Pi can reload an extension in-process, so every registration is yielded to the
7
- * fiber and one dispose on session shutdown unwinds them in reverse.
8
- */
3
+ /** The package's single standard Pi factory. */
9
4
  declare function skillsExtension(pi: ExtensionAPI): Promise<void>;
10
5
  //#endregion
11
6
  export { skillsExtension as default, skillsExtension };
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;iBAoCsB,gBAAgB,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;iBAoHsB,gBAAgB,IAAI,eAAe"}
@@ -1,2 +1,2 @@
1
- import{createActiveHelpSkillView as e}from"../helpSkills.mjs";import{LEADER_SOURCE as t,SKILLS_LEADER_CONTRIBUTION as n}from"../../types/skills.mjs";import{registerSkillsCommand as r}from"../../commands/skillsCommand.mjs";import{provideSkillSources as i}from"../skillSources.mjs";import{registerSkillReadiness as a,skillInventory as o}from"./skillReadiness.mjs";import{requireHarnessRoot as s}from"@agimon-ai/doompi-config/harnessStore";import{requireDoomConfigContext as c}from"@agimon-ai/doompi-config/piContext";import{registerDoomLeaderContribution as l}from"@agimon-ai/doompi-ui/leader";import{Context as u}from"@deepseek-ai/cordis";function d(){let e,t,n;return{catalog:()=>e??=import(`../skillCatalog.mjs`),deferred:()=>t??=import(`../deferredSkills.mjs`),overlay:()=>n??=import(`../../tui/skillsOverlay.mjs`)}}async function f(f){let p=new u,m=d(),h,g=()=>(h??=p.fiber.dispose(),h);try{p.effect(function*(){let t=i(f);yield()=>t.dispose();let u=e(f);yield()=>u.dispose(),yield l(f,n);let d=a(f,u,m.deferred);r(f,{extensionSources:()=>t.list(),inventory:o(u,d),repositoryRoot:async e=>s(c(e).harness),buildCatalog:async e=>(await m.catalog()).buildSkillCatalog(e),openOverlay:async(e,t)=>(await m.overlay()).openSkillsOverlay(e,t)})},t)}catch(e){throw await g(),e}f.on(`session_shutdown`,g)}export{f as default,f as skillsExtension};
1
+ import{LEADER_SOURCE as e,SKILLS_LEADER_CONTRIBUTION as t}from"../../types/skills.mjs";import{registerSkillsCommand as n}from"../../commands/skillsCommand.mjs";import{createActiveHelpSkillView as r}from"../helpSkills.mjs";import{registerSkillReadiness as i,skillInventory as a}from"./skillReadiness.mjs";import{requireHarnessRoot as o}from"@agimon-ai/doompi-config/harnessStore";import{requireDoomConfigContext as s}from"@agimon-ai/doompi-config";import{DOOM_CONFIG_SERVICE as c}from"@agimon-ai/doompi-extension-contracts/config";import{DOOM_CORDIS_SESSION_SERVICE as l,connectDoomCordisHost as u}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as d,requireDoomHelpService as f}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_SKILL_SOURCES_SERVICE as p,createDoomSkillSourcesService as m}from"@agimon-ai/doompi-extension-contracts/skills";import{DOOM_UI_HUB_SERVICE as h,requireDoomUiHub as g}from"@agimon-ai/doompi-extension-contracts/ui-hub";function _(){let e,t,n;return{catalog:()=>e??=import(`../skillCatalog.mjs`),deferred:()=>t??=import(`../deferredSkills.mjs`),overlay:()=>n??=import(`../../tui/skillsOverlay.mjs`)}}function v(u,{pi:v}){let y=_(),b,x;u.inject([c],e=>(b=e,()=>{b===e&&(b=void 0)}));let S=()=>{if(!b)throw Error(`Doom skill runtime is waiting for the session config service.`);return b},C=()=>{if(!x)throw Error(`Doom skill sources are waiting for the active session service.`);return x};u.inject([l],e=>{let t=e.get(l),n=m(`${t.generation}:skill-sources`);return e.provide(p,n),x=n,()=>{x===n&&(x=void 0),n.dispose()}});let w=r();u.effect(()=>()=>w.dispose(),`${e}/help`),u.inject([d],t=>{let n=f(t),r=n.register({source:e,moduleUrl:import.meta.url,skills:[{name:`doompi-author-skill`,description:`Author and distribute DoomPi 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 DoomPi package.`},{name:`doompi-use-skill`,description:`Use DoomPi'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.`}]}),i=w.bind(n);return()=>{i(),r.dispose()}});let T=i(v,w,y.deferred,S);n(v,{cordisContext:S,extensionSources:()=>C().list(),inventory:a(w,T),repositoryRoot:async e=>o(s(S()).harness),buildCatalog:async e=>(await y.catalog()).buildSkillCatalog(e),openOverlay:async(e,t)=>(await y.overlay()).openSkillsOverlay(e,t)}),u.inject([h],e=>{let n=g(e).registerLeader(t);return()=>n.dispose()})}async function y(t){let n=await u(t,e),r=n.root.plugin(v,{pi:t});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;t.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}export{y as default,y as skillsExtension};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerSkillsCommand } from '../../commands/skillsCommand.ts';\nimport { LEADER_SOURCE, SKILLS_LEADER_CONTRIBUTION } from '../../types/skills.ts';\nimport { createActiveHelpSkillView } from '../helpSkills.ts';\nimport { provideSkillSources } from '../skillSources.ts';\nimport { registerSkillReadiness, skillInventory } from './skillReadiness.ts';\n\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\n/**\n * The catalogue and the overlay load on first use.\n *\n * Discovery has to start with the session, but building the catalogue and\n * rendering it are only reached by `/skills`, so they stay off the startup path.\n */\nfunction lazyModules() {\n let catalog: Promise<typeof import('../skillCatalog.ts')> | undefined;\n let deferred: Promise<typeof import('../deferredSkills.ts')> | undefined;\n let overlay: Promise<typeof import('../../tui/skillsOverlay.ts')> | undefined;\n return {\n catalog: () => (catalog ??= import('../skillCatalog.ts')),\n deferred: () => (deferred ??= import('../deferredSkills.ts')),\n overlay: () => (overlay ??= import('../../tui/skillsOverlay.ts')),\n };\n}\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse.\n */\nexport async function skillsExtension(pi: ExtensionAPI): Promise<void> {\n const cordis = new Context();\n const load = lazyModules();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n const sources = provideSkillSources(pi);\n yield () => sources.dispose();\n\n const helpSkillView = createActiveHelpSkillView(pi);\n yield () => helpSkillView.dispose();\n\n yield registerDoomLeaderContribution(pi, SKILLS_LEADER_CONTRIBUTION);\n\n const readiness = registerSkillReadiness(pi, helpSkillView, load.deferred);\n registerSkillsCommand(pi, {\n extensionSources: () => sources.list(),\n inventory: skillInventory(helpSkillView, readiness),\n repositoryRoot: async (ctx: ExtensionContext) => requireHarnessRoot(requireDoomConfigContext(ctx).harness),\n buildCatalog: async (request) => (await load.catalog()).buildSkillCatalog(request),\n openOverlay: async (ctx, options) => (await load.overlay()).openSkillsOverlay(ctx, options),\n });\n }, LEADER_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default skillsExtension;\n"],"mappings":"8nBAmBA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACJ,MAAO,CACL,YAAgB,IAAY,OAAO,uBACnC,aAAiB,IAAa,OAAO,yBACrC,YAAgB,IAAY,OAAO,8BACrC,CACF,CAQA,eAAsB,EAAgB,EAAiC,CACrE,IAAM,EAAS,IAAI,EACb,EAAO,EAAY,EACrB,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,IAAM,EAAU,EAAoB,CAAE,EACtC,SAAY,EAAQ,QAAQ,EAE5B,IAAM,EAAgB,EAA0B,CAAE,EAClD,SAAY,EAAc,QAAQ,EAElC,MAAM,EAA+B,EAAI,CAA0B,EAEnE,IAAM,EAAY,EAAuB,EAAI,EAAe,EAAK,QAAQ,EACzE,EAAsB,EAAI,CACxB,qBAAwB,EAAQ,KAAK,EACrC,UAAW,EAAe,EAAe,CAAS,EAClD,eAAgB,KAAO,IAA0B,EAAmB,EAAyB,CAAG,CAAC,CAAC,OAAO,EACzG,aAAc,KAAO,KAAa,MAAM,EAAK,QAAQ,EAAA,CAAG,kBAAkB,CAAO,EACjF,YAAa,MAAO,EAAK,KAAa,MAAM,EAAK,QAAQ,EAAA,CAAG,kBAAkB,EAAK,CAAO,CAC5F,CAAC,CACH,EAAG,CAAa,CAClB,OAAS,EAAO,CAEd,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n createDoomSkillSourcesService,\n DOOM_SKILL_SOURCES_SERVICE,\n type DoomSkillSourcesService,\n} from '@agimon-ai/doompi-extension-contracts/skills';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerSkillsCommand } from '../../commands/skillsCommand.ts';\nimport { LEADER_SOURCE, SKILLS_LEADER_CONTRIBUTION } from '../../types/skills.ts';\nimport { createActiveHelpSkillView } from '../helpSkills.ts';\nimport { registerSkillReadiness, skillInventory } from './skillReadiness.ts';\n\n/**\n * The catalogue and the overlay load on first use.\n *\n * Discovery has to start with the session, but building the catalogue and\n * rendering it are only reached by `/skills`, so they stay off the startup path.\n */\nfunction lazyModules() {\n let catalog: Promise<typeof import('../skillCatalog.ts')> | undefined;\n let deferred: Promise<typeof import('../deferredSkills.ts')> | undefined;\n let overlay: Promise<typeof import('../../tui/skillsOverlay.ts')> | undefined;\n return {\n catalog: () => (catalog ??= import('../skillCatalog.ts')),\n deferred: () => (deferred ??= import('../deferredSkills.ts')),\n overlay: () => (overlay ??= import('../../tui/skillsOverlay.ts')),\n };\n}\n\ninterface SkillPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction skillPlugin(cordis: Context, { pi }: SkillPluginConfig): void {\n const load = lazyModules();\n let activeContext: Context | undefined;\n let activeSources: DoomSkillSourcesService | undefined;\n cordis.inject([DOOM_CONFIG_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom skill runtime is waiting for the session config service.');\n return activeContext;\n };\n const requireSkillSources = (): DoomSkillSourcesService => {\n if (!activeSources) throw new Error('Doom skill sources are waiting for the active session service.');\n return activeSources;\n };\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const service = createDoomSkillSourcesService(`${session.generation}:skill-sources`);\n sessionContext.provide(DOOM_SKILL_SOURCES_SERVICE, service);\n activeSources = service;\n return () => {\n if (activeSources === service) activeSources = undefined;\n service.dispose();\n };\n });\n\n const helpSkillView = createActiveHelpSkillView();\n cordis.effect(() => () => helpSkillView.dispose(), `${LEADER_SOURCE}/help`);\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const help = requireDoomHelpService(helpContext);\n const contribution = help.register({\n source: LEADER_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-skill',\n description:\n 'Author and distribute DoomPi 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 DoomPi package.',\n },\n {\n name: 'doompi-use-skill',\n description:\n \"Use DoomPi'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 },\n ],\n });\n const unbind = helpSkillView.bind(help);\n return () => {\n unbind();\n contribution.dispose();\n };\n });\n const readiness = registerSkillReadiness(pi, helpSkillView, load.deferred, requireRuntimeContext);\n registerSkillsCommand(pi, {\n cordisContext: requireRuntimeContext,\n extensionSources: () => requireSkillSources().list(),\n inventory: skillInventory(helpSkillView, readiness),\n repositoryRoot: async (_ctx: ExtensionContext) =>\n requireHarnessRoot(requireDoomConfigContext(requireRuntimeContext()).harness),\n buildCatalog: async (request) => (await load.catalog()).buildSkillCatalog(request),\n openOverlay: async (ctx, options) => (await load.overlay()).openSkillsOverlay(ctx, options),\n });\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader(SKILLS_LEADER_CONTRIBUTION);\n return () => contribution.dispose();\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function skillsExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(skillPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default skillsExtension;\n"],"mappings":"89BA4BA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACJ,MAAO,CACL,YAAgB,IAAY,OAAO,uBACnC,aAAiB,IAAa,OAAO,yBACrC,YAAgB,IAAY,OAAO,8BACrC,CACF,CAMA,SAAS,EAAY,EAAiB,CAAE,MAA+B,CACrE,IAAM,EAAO,EAAY,EACrB,EACA,EACJ,EAAO,OAAO,CAAC,CAAmB,EAAI,IACpC,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACD,IAAM,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,+DAA+D,EACnG,OAAO,CACT,EACM,MAAqD,CACzD,GAAI,CAAC,EAAe,MAAU,MAAM,gEAAgE,EACpG,OAAO,CACT,EACA,EAAO,OAAO,CAAC,CAA2B,EAAI,GAAmB,CAC/D,IAAM,EAAU,EAAe,IAAI,CAA2B,EACxD,EAAU,EAA8B,GAAG,EAAQ,WAAW,eAAe,EAGnF,OAFA,EAAe,QAAQ,EAA4B,CAAO,EAC1D,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,IAC/C,EAAQ,QAAQ,CAClB,CACF,CAAC,EAED,IAAM,EAAgB,EAA0B,EAChD,EAAO,eAAmB,EAAc,QAAQ,EAAG,GAAG,EAAc,MAAM,EAC1E,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAO,EAAuB,CAAW,EACzC,EAAe,EAAK,SAAS,CACjC,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,sBACN,YACE,6OACJ,EACA,CACE,KAAM,mBACN,YACE,oNACJ,CACF,CACF,CAAC,EACK,EAAS,EAAc,KAAK,CAAI,EACtC,UAAa,CACX,EAAO,EACP,EAAa,QAAQ,CACvB,CACF,CAAC,EACD,IAAM,EAAY,EAAuB,EAAI,EAAe,EAAK,SAAU,CAAqB,EAChG,EAAsB,EAAI,CACxB,cAAe,EACf,qBAAwB,EAAoB,CAAC,CAAC,KAAK,EACnD,UAAW,EAAe,EAAe,CAAS,EAClD,eAAgB,KAAO,IACrB,EAAmB,EAAyB,EAAsB,CAAC,CAAC,CAAC,OAAO,EAC9E,aAAc,KAAO,KAAa,MAAM,EAAK,QAAQ,EAAA,CAAG,kBAAkB,CAAO,EACjF,YAAa,MAAO,EAAK,KAAa,MAAM,EAAK,QAAQ,EAAA,CAAG,kBAAkB,EAAK,CAAO,CAC5F,CAAC,EAED,EAAO,OAAO,CAAC,CAAmB,EAAI,GAAc,CAClD,IAAM,EAAe,EAAiB,CAAS,CAAC,CAAC,eAAe,CAA0B,EAC1F,UAAa,EAAa,QAAQ,CACpC,CAAC,CACH,CAGA,eAAsB,EAAgB,EAAiC,CACrE,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAa,EAC1D,EAAQ,EAAW,KAAK,OAAO,EAAa,CAAE,IAAG,CAAC,EACxD,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
@@ -1,4 +1,4 @@
1
- const e=require("../../types/skills.cjs");let t=require("@agimon-ai/doompi-config/piContext");const n=`warning`;function r(t){return typeof t.getCommands==`function`?t.getCommands().filter(e=>e.source===`skill`&&e.name.startsWith(`skill:`)).map(t=>t.name.slice(e.SKILL_COMMAND_PREFIX.length)):[]}function i(e,t){return{skills:[],diagnostics:[`${e}: ${t instanceof Error?t.message:String(t)}`]}}function a(e,a,o){let s=0,c,l=``,u=async e=>{let t=await e.ready;return c===e?t:void 0};return e.on(`session_start`,(e,n)=>{let r=(0,t.requireDoomConfigContext)(n).harness;c={id:++s,ready:o().then(({DeferredSkillLoader:e})=>new e({cwd:n.cwd,skillPaths:r.skillDirectories}).start()).catch(e=>i(n.cwd,e)),diagnosticsShown:!1},l=``}),e.on(`input`,async t=>{if(!t.text.startsWith(`/skill:`))return{action:`continue`};let n=c;if(!n)return{action:`continue`};let i=await u(n);if(!i)return{action:`continue`};let s=a.merge({normalSkills:[],deferredSkills:i.skills,normalSkillNames:r(e)}),{expandDeferredSkillCommand:l}=await o(),d=l(t.text,s.additionalSkills);return d===t.text?{action:`continue`}:{action:`transform`,text:d,images:t.images}}),e.on(`before_agent_start`,async(e,t)=>{let r=c;if(!r)return;let i=await u(r);if(!i)return;!r.diagnosticsShown&&i.diagnostics.length>0&&(r.diagnosticsShown=!0,t.ui.notify(`Skill discovery:\n${i.diagnostics.join(`
2
- `)}`,n));let s=a.merge({normalSkills:e.systemPromptOptions.skills??[],deferredSkills:i.skills});if(s.diagnosticKey!==l&&(l=s.diagnosticKey,s.diagnostics.length>0&&t.ui.notify(`Package Help:\n${s.diagnostics.join(`
3
- `)}`,n)),s.additionalSkills.length===0)return;let{buildPromptWithDeferredSkills:d}=await o();return{systemPrompt:d(e.systemPrompt,e.systemPromptOptions,[...s.additionalSkills])}}),{current:async()=>c?u(c):void 0}}function o(e,t){return async n=>{let r=await t.current(),i=e.merge({normalSkills:n.getSystemPromptOptions().skills??[],deferredSkills:r?.skills??[]});return{helpSkills:i.helpSkills,diagnostics:i.diagnostics}}}exports.registerSkillReadiness=a,exports.skillInventory=o;
1
+ const e=require("../../types/skills.cjs");let t=require("@agimon-ai/doompi-config/piContext");const n=`warning`;function r(t){return typeof t.getCommands==`function`?t.getCommands().filter(e=>e.source===`skill`&&e.name.startsWith(`skill:`)).map(t=>t.name.slice(e.SKILL_COMMAND_PREFIX.length)):[]}function i(e,t){return{skills:[],diagnostics:[`${e}: ${t instanceof Error?t.message:String(t)}`]}}function a(e,a,o,s){let c=0,l,u=``,d=async e=>{let t=await e.ready;return l===e?t:void 0};return e.on(`session_start`,(e,n)=>{let r=(0,t.requireDoomConfigContext)(s()).harness;l={id:++c,ready:o().then(({DeferredSkillLoader:e})=>new e({cwd:n.cwd,skillPaths:r.skillDirectories}).start()).catch(e=>i(n.cwd,e)),diagnosticsShown:!1},u=``}),e.on(`input`,async t=>{if(!t.text.startsWith(`/skill:`))return{action:`continue`};let n=l;if(!n)return{action:`continue`};let i=await d(n);if(!i)return{action:`continue`};let s=a.merge({normalSkills:[],deferredSkills:i.skills,normalSkillNames:r(e)}),{expandDeferredSkillCommand:c}=await o(),u=c(t.text,s.additionalSkills);return u===t.text?{action:`continue`}:{action:`transform`,text:u,images:t.images}}),e.on(`before_agent_start`,async(e,t)=>{let r=l;if(!r)return;let i=await d(r);if(!i)return;!r.diagnosticsShown&&i.diagnostics.length>0&&(r.diagnosticsShown=!0,t.ui.notify(`Skill discovery:\n${i.diagnostics.join(`
2
+ `)}`,n));let s=a.merge({normalSkills:e.systemPromptOptions.skills??[],deferredSkills:i.skills});if(s.diagnosticKey!==u&&(u=s.diagnosticKey,s.diagnostics.length>0&&t.ui.notify(`Package Help:\n${s.diagnostics.join(`
3
+ `)}`,n)),s.additionalSkills.length===0)return;let{buildPromptWithDeferredSkills:c}=await o();return{systemPrompt:c(e.systemPrompt,e.systemPromptOptions,[...s.additionalSkills])}}),{current:async()=>l?d(l):void 0}}function o(e,t){return async n=>{let r=await t.current(),i=e.merge({normalSkills:n.getSystemPromptOptions().skills??[],deferredSkills:r?.skills??[]});return{helpSkills:i.helpSkills,diagnostics:i.diagnostics}}}exports.registerSkillReadiness=a,exports.skillInventory=o;
4
4
  //# sourceMappingURL=skillReadiness.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skillReadiness.cjs","names":["SKILL_COMMAND_PREFIX","requireDoomConfigContext"],"sources":["../../../src/adapters/pi/skillReadiness.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext, Skill } from '@earendil-works/pi-coding-agent';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from '../../types/skills.ts';\nimport type { DeferredSkillSnapshot } from '../deferredSkills.ts';\nimport type { createActiveHelpSkillView } from '../helpSkills.ts';\n\nconst WARNING = 'warning';\n\ntype HelpSkillView = ReturnType<typeof createActiveHelpSkillView>;\n\ninterface SkillReadinessGeneration {\n readonly id: number;\n readonly ready: Promise<DeferredSkillSnapshot>;\n diagnosticsShown: boolean;\n}\n\nexport interface SkillReadiness {\n /** The snapshot for the live generation, or undefined once it is superseded. */\n current(): Promise<DeferredSkillSnapshot | undefined>;\n}\n\nfunction loadedSkillCommandNames(pi: ExtensionAPI): string[] {\n if (typeof pi.getCommands !== 'function') return [];\n return pi\n .getCommands()\n .filter((command) => command.source === 'skill' && command.name.startsWith(SKILL_COMMAND_PREFIX))\n .map((command) => command.name.slice(SKILL_COMMAND_PREFIX.length));\n}\n\nfunction failedSnapshot(cwd: string, error: unknown): DeferredSkillSnapshot {\n return {\n skills: [],\n diagnostics: [`${cwd}: ${error instanceof Error ? error.message : String(error)}`],\n };\n}\n\n/**\n * Discovery that starts with the session and is awaited only when something\n * needs it.\n *\n * Scanning every skill directory is the slowest thing this package does, so it\n * runs as a generation started on session_start. A reload starts a new one, and\n * a snapshot from a superseded generation is discarded rather than shown.\n */\nexport function registerSkillReadiness(\n pi: ExtensionAPI,\n helpSkillView: HelpSkillView,\n loadDeferredSkills: () => Promise<typeof import('../deferredSkills.ts')>,\n): SkillReadiness {\n let generationSequence = 0;\n let currentGeneration: SkillReadinessGeneration | undefined;\n let shownHelpDiagnosticKey = '';\n\n const currentSnapshot = async (generation: SkillReadinessGeneration): Promise<DeferredSkillSnapshot | undefined> => {\n const snapshot = await generation.ready;\n return currentGeneration === generation ? snapshot : undefined;\n };\n\n pi.on('session_start', (_event, ctx) => {\n const state = requireDoomConfigContext(ctx).harness;\n const id = ++generationSequence;\n const ready = loadDeferredSkills()\n .then(({ DeferredSkillLoader }) =>\n new DeferredSkillLoader({ cwd: ctx.cwd, skillPaths: state.skillDirectories }).start(),\n )\n .catch((error: unknown) => failedSnapshot(ctx.cwd, error));\n currentGeneration = { id, ready, diagnosticsShown: false };\n shownHelpDiagnosticKey = '';\n });\n\n pi.on('input', async (event) => {\n if (!event.text.startsWith(SKILL_INVOCATION_PREFIX)) return { action: 'continue' };\n\n const generation = currentGeneration;\n if (!generation) return { action: 'continue' };\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return { action: 'continue' };\n\n const inventory = helpSkillView.merge({\n normalSkills: [],\n deferredSkills: snapshot.skills,\n normalSkillNames: loadedSkillCommandNames(pi),\n });\n const { expandDeferredSkillCommand } = await loadDeferredSkills();\n const text = expandDeferredSkillCommand(event.text, inventory.additionalSkills);\n return text === event.text ? { action: 'continue' } : { action: 'transform', text, images: event.images };\n });\n\n pi.on('before_agent_start', async (event, ctx) => {\n const generation = currentGeneration;\n if (!generation) return undefined;\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return undefined;\n\n if (!generation.diagnosticsShown && snapshot.diagnostics.length > 0) {\n generation.diagnosticsShown = true;\n ctx.ui.notify(`Skill discovery:\\n${snapshot.diagnostics.join('\\n')}`, WARNING);\n }\n const inventory = helpSkillView.merge({\n normalSkills: event.systemPromptOptions.skills ?? [],\n deferredSkills: snapshot.skills,\n });\n if (inventory.diagnosticKey !== shownHelpDiagnosticKey) {\n shownHelpDiagnosticKey = inventory.diagnosticKey;\n if (inventory.diagnostics.length > 0) {\n ctx.ui.notify(`Package Help:\\n${inventory.diagnostics.join('\\n')}`, WARNING);\n }\n }\n if (inventory.additionalSkills.length === 0) return undefined;\n\n const { buildPromptWithDeferredSkills } = await loadDeferredSkills();\n return {\n systemPrompt: buildPromptWithDeferredSkills(event.systemPrompt, event.systemPromptOptions, [\n ...inventory.additionalSkills,\n ]),\n };\n });\n\n return {\n current: async () => (currentGeneration ? currentSnapshot(currentGeneration) : undefined),\n };\n}\n\n/** The merged skill inventory the /skills catalogue is built from. */\nexport function skillInventory(helpSkillView: HelpSkillView, readiness: SkillReadiness) {\n return async (ctx: ExtensionContext) => {\n const snapshot = await readiness.current();\n const inventory = helpSkillView.merge({\n // Present on the runtime context; absent from the published ExtensionContext type.\n normalSkills:\n (ctx as unknown as { getSystemPromptOptions(): { skills?: Skill[] } }).getSystemPromptOptions().skills ?? [],\n deferredSkills: snapshot?.skills ?? [],\n });\n return { helpSkills: inventory.helpSkills, diagnostics: inventory.diagnostics };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skillReadiness.cjs","names":["SKILL_COMMAND_PREFIX","requireDoomConfigContext"],"sources":["../../../src/adapters/pi/skillReadiness.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext, Skill } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from '../../types/skills.ts';\nimport type { DeferredSkillSnapshot } from '../deferredSkills.ts';\nimport type { createActiveHelpSkillView } from '../helpSkills.ts';\n\nconst WARNING = 'warning';\n\ntype HelpSkillView = ReturnType<typeof createActiveHelpSkillView>;\n\ninterface SkillReadinessGeneration {\n readonly id: number;\n readonly ready: Promise<DeferredSkillSnapshot>;\n diagnosticsShown: boolean;\n}\n\nexport interface SkillReadiness {\n /** The snapshot for the live generation, or undefined once it is superseded. */\n current(): Promise<DeferredSkillSnapshot | undefined>;\n}\n\nfunction loadedSkillCommandNames(pi: ExtensionAPI): string[] {\n if (typeof pi.getCommands !== 'function') return [];\n return pi\n .getCommands()\n .filter((command) => command.source === 'skill' && command.name.startsWith(SKILL_COMMAND_PREFIX))\n .map((command) => command.name.slice(SKILL_COMMAND_PREFIX.length));\n}\n\nfunction failedSnapshot(cwd: string, error: unknown): DeferredSkillSnapshot {\n return {\n skills: [],\n diagnostics: [`${cwd}: ${error instanceof Error ? error.message : String(error)}`],\n };\n}\n\n/**\n * Discovery that starts with the session and is awaited only when something\n * needs it.\n *\n * Scanning every skill directory is the slowest thing this package does, so it\n * runs as a generation started on session_start. A reload starts a new one, and\n * a snapshot from a superseded generation is discarded rather than shown.\n */\nexport function registerSkillReadiness(\n pi: ExtensionAPI,\n helpSkillView: HelpSkillView,\n loadDeferredSkills: () => Promise<typeof import('../deferredSkills.ts')>,\n cordisContext: () => Context,\n): SkillReadiness {\n let generationSequence = 0;\n let currentGeneration: SkillReadinessGeneration | undefined;\n let shownHelpDiagnosticKey = '';\n\n const currentSnapshot = async (generation: SkillReadinessGeneration): Promise<DeferredSkillSnapshot | undefined> => {\n const snapshot = await generation.ready;\n return currentGeneration === generation ? snapshot : undefined;\n };\n\n pi.on('session_start', (_event, ctx) => {\n const state = requireDoomConfigContext(cordisContext()).harness;\n const id = ++generationSequence;\n const ready = loadDeferredSkills()\n .then(({ DeferredSkillLoader }) =>\n new DeferredSkillLoader({ cwd: ctx.cwd, skillPaths: state.skillDirectories }).start(),\n )\n .catch((error: unknown) => failedSnapshot(ctx.cwd, error));\n currentGeneration = { id, ready, diagnosticsShown: false };\n shownHelpDiagnosticKey = '';\n });\n\n pi.on('input', async (event) => {\n if (!event.text.startsWith(SKILL_INVOCATION_PREFIX)) return { action: 'continue' };\n\n const generation = currentGeneration;\n if (!generation) return { action: 'continue' };\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return { action: 'continue' };\n\n const inventory = helpSkillView.merge({\n normalSkills: [],\n deferredSkills: snapshot.skills,\n normalSkillNames: loadedSkillCommandNames(pi),\n });\n const { expandDeferredSkillCommand } = await loadDeferredSkills();\n const text = expandDeferredSkillCommand(event.text, inventory.additionalSkills);\n return text === event.text ? { action: 'continue' } : { action: 'transform', text, images: event.images };\n });\n\n pi.on('before_agent_start', async (event, ctx) => {\n const generation = currentGeneration;\n if (!generation) return undefined;\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return undefined;\n\n if (!generation.diagnosticsShown && snapshot.diagnostics.length > 0) {\n generation.diagnosticsShown = true;\n ctx.ui.notify(`Skill discovery:\\n${snapshot.diagnostics.join('\\n')}`, WARNING);\n }\n const inventory = helpSkillView.merge({\n normalSkills: event.systemPromptOptions.skills ?? [],\n deferredSkills: snapshot.skills,\n });\n if (inventory.diagnosticKey !== shownHelpDiagnosticKey) {\n shownHelpDiagnosticKey = inventory.diagnosticKey;\n if (inventory.diagnostics.length > 0) {\n ctx.ui.notify(`Package Help:\\n${inventory.diagnostics.join('\\n')}`, WARNING);\n }\n }\n if (inventory.additionalSkills.length === 0) return undefined;\n\n const { buildPromptWithDeferredSkills } = await loadDeferredSkills();\n return {\n systemPrompt: buildPromptWithDeferredSkills(event.systemPrompt, event.systemPromptOptions, [\n ...inventory.additionalSkills,\n ]),\n };\n });\n\n return {\n current: async () => (currentGeneration ? currentSnapshot(currentGeneration) : undefined),\n };\n}\n\n/** The merged skill inventory the /skills catalogue is built from. */\nexport function skillInventory(helpSkillView: HelpSkillView, readiness: SkillReadiness) {\n return async (ctx: ExtensionContext) => {\n const snapshot = await readiness.current();\n const inventory = helpSkillView.merge({\n // Present on the runtime context; absent from the published ExtensionContext type.\n normalSkills:\n (ctx as unknown as { getSystemPromptOptions(): { skills?: Skill[] } }).getSystemPromptOptions().skills ?? [],\n deferredSkills: snapshot?.skills ?? [],\n });\n return { helpSkills: inventory.helpSkills, diagnostics: inventory.diagnostics };\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{SKILL_COMMAND_PREFIX as e}from"../../types/skills.mjs";import{requireDoomConfigContext as t}from"@agimon-ai/doompi-config/piContext";const n=`warning`;function r(t){return typeof t.getCommands==`function`?t.getCommands().filter(e=>e.source===`skill`&&e.name.startsWith(`skill:`)).map(t=>t.name.slice(e.length)):[]}function i(e,t){return{skills:[],diagnostics:[`${e}: ${t instanceof Error?t.message:String(t)}`]}}function a(e,a,o){let s=0,c,l=``,u=async e=>{let t=await e.ready;return c===e?t:void 0};return e.on(`session_start`,(e,n)=>{let r=t(n).harness;c={id:++s,ready:o().then(({DeferredSkillLoader:e})=>new e({cwd:n.cwd,skillPaths:r.skillDirectories}).start()).catch(e=>i(n.cwd,e)),diagnosticsShown:!1},l=``}),e.on(`input`,async t=>{if(!t.text.startsWith(`/skill:`))return{action:`continue`};let n=c;if(!n)return{action:`continue`};let i=await u(n);if(!i)return{action:`continue`};let s=a.merge({normalSkills:[],deferredSkills:i.skills,normalSkillNames:r(e)}),{expandDeferredSkillCommand:l}=await o(),d=l(t.text,s.additionalSkills);return d===t.text?{action:`continue`}:{action:`transform`,text:d,images:t.images}}),e.on(`before_agent_start`,async(e,t)=>{let r=c;if(!r)return;let i=await u(r);if(!i)return;!r.diagnosticsShown&&i.diagnostics.length>0&&(r.diagnosticsShown=!0,t.ui.notify(`Skill discovery:\n${i.diagnostics.join(`
2
- `)}`,n));let s=a.merge({normalSkills:e.systemPromptOptions.skills??[],deferredSkills:i.skills});if(s.diagnosticKey!==l&&(l=s.diagnosticKey,s.diagnostics.length>0&&t.ui.notify(`Package Help:\n${s.diagnostics.join(`
3
- `)}`,n)),s.additionalSkills.length===0)return;let{buildPromptWithDeferredSkills:d}=await o();return{systemPrompt:d(e.systemPrompt,e.systemPromptOptions,[...s.additionalSkills])}}),{current:async()=>c?u(c):void 0}}function o(e,t){return async n=>{let r=await t.current(),i=e.merge({normalSkills:n.getSystemPromptOptions().skills??[],deferredSkills:r?.skills??[]});return{helpSkills:i.helpSkills,diagnostics:i.diagnostics}}}export{a as registerSkillReadiness,o as skillInventory};
1
+ import{SKILL_COMMAND_PREFIX as e}from"../../types/skills.mjs";import{requireDoomConfigContext as t}from"@agimon-ai/doompi-config/piContext";const n=`warning`;function r(t){return typeof t.getCommands==`function`?t.getCommands().filter(e=>e.source===`skill`&&e.name.startsWith(`skill:`)).map(t=>t.name.slice(e.length)):[]}function i(e,t){return{skills:[],diagnostics:[`${e}: ${t instanceof Error?t.message:String(t)}`]}}function a(e,a,o,s){let c=0,l,u=``,d=async e=>{let t=await e.ready;return l===e?t:void 0};return e.on(`session_start`,(e,n)=>{let r=t(s()).harness;l={id:++c,ready:o().then(({DeferredSkillLoader:e})=>new e({cwd:n.cwd,skillPaths:r.skillDirectories}).start()).catch(e=>i(n.cwd,e)),diagnosticsShown:!1},u=``}),e.on(`input`,async t=>{if(!t.text.startsWith(`/skill:`))return{action:`continue`};let n=l;if(!n)return{action:`continue`};let i=await d(n);if(!i)return{action:`continue`};let s=a.merge({normalSkills:[],deferredSkills:i.skills,normalSkillNames:r(e)}),{expandDeferredSkillCommand:c}=await o(),u=c(t.text,s.additionalSkills);return u===t.text?{action:`continue`}:{action:`transform`,text:u,images:t.images}}),e.on(`before_agent_start`,async(e,t)=>{let r=l;if(!r)return;let i=await d(r);if(!i)return;!r.diagnosticsShown&&i.diagnostics.length>0&&(r.diagnosticsShown=!0,t.ui.notify(`Skill discovery:\n${i.diagnostics.join(`
2
+ `)}`,n));let s=a.merge({normalSkills:e.systemPromptOptions.skills??[],deferredSkills:i.skills});if(s.diagnosticKey!==u&&(u=s.diagnosticKey,s.diagnostics.length>0&&t.ui.notify(`Package Help:\n${s.diagnostics.join(`
3
+ `)}`,n)),s.additionalSkills.length===0)return;let{buildPromptWithDeferredSkills:c}=await o();return{systemPrompt:c(e.systemPrompt,e.systemPromptOptions,[...s.additionalSkills])}}),{current:async()=>l?d(l):void 0}}function o(e,t){return async n=>{let r=await t.current(),i=e.merge({normalSkills:n.getSystemPromptOptions().skills??[],deferredSkills:r?.skills??[]});return{helpSkills:i.helpSkills,diagnostics:i.diagnostics}}}export{a as registerSkillReadiness,o as skillInventory};
4
4
  //# sourceMappingURL=skillReadiness.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skillReadiness.mjs","names":[],"sources":["../../../src/adapters/pi/skillReadiness.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext, Skill } from '@earendil-works/pi-coding-agent';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from '../../types/skills.ts';\nimport type { DeferredSkillSnapshot } from '../deferredSkills.ts';\nimport type { createActiveHelpSkillView } from '../helpSkills.ts';\n\nconst WARNING = 'warning';\n\ntype HelpSkillView = ReturnType<typeof createActiveHelpSkillView>;\n\ninterface SkillReadinessGeneration {\n readonly id: number;\n readonly ready: Promise<DeferredSkillSnapshot>;\n diagnosticsShown: boolean;\n}\n\nexport interface SkillReadiness {\n /** The snapshot for the live generation, or undefined once it is superseded. */\n current(): Promise<DeferredSkillSnapshot | undefined>;\n}\n\nfunction loadedSkillCommandNames(pi: ExtensionAPI): string[] {\n if (typeof pi.getCommands !== 'function') return [];\n return pi\n .getCommands()\n .filter((command) => command.source === 'skill' && command.name.startsWith(SKILL_COMMAND_PREFIX))\n .map((command) => command.name.slice(SKILL_COMMAND_PREFIX.length));\n}\n\nfunction failedSnapshot(cwd: string, error: unknown): DeferredSkillSnapshot {\n return {\n skills: [],\n diagnostics: [`${cwd}: ${error instanceof Error ? error.message : String(error)}`],\n };\n}\n\n/**\n * Discovery that starts with the session and is awaited only when something\n * needs it.\n *\n * Scanning every skill directory is the slowest thing this package does, so it\n * runs as a generation started on session_start. A reload starts a new one, and\n * a snapshot from a superseded generation is discarded rather than shown.\n */\nexport function registerSkillReadiness(\n pi: ExtensionAPI,\n helpSkillView: HelpSkillView,\n loadDeferredSkills: () => Promise<typeof import('../deferredSkills.ts')>,\n): SkillReadiness {\n let generationSequence = 0;\n let currentGeneration: SkillReadinessGeneration | undefined;\n let shownHelpDiagnosticKey = '';\n\n const currentSnapshot = async (generation: SkillReadinessGeneration): Promise<DeferredSkillSnapshot | undefined> => {\n const snapshot = await generation.ready;\n return currentGeneration === generation ? snapshot : undefined;\n };\n\n pi.on('session_start', (_event, ctx) => {\n const state = requireDoomConfigContext(ctx).harness;\n const id = ++generationSequence;\n const ready = loadDeferredSkills()\n .then(({ DeferredSkillLoader }) =>\n new DeferredSkillLoader({ cwd: ctx.cwd, skillPaths: state.skillDirectories }).start(),\n )\n .catch((error: unknown) => failedSnapshot(ctx.cwd, error));\n currentGeneration = { id, ready, diagnosticsShown: false };\n shownHelpDiagnosticKey = '';\n });\n\n pi.on('input', async (event) => {\n if (!event.text.startsWith(SKILL_INVOCATION_PREFIX)) return { action: 'continue' };\n\n const generation = currentGeneration;\n if (!generation) return { action: 'continue' };\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return { action: 'continue' };\n\n const inventory = helpSkillView.merge({\n normalSkills: [],\n deferredSkills: snapshot.skills,\n normalSkillNames: loadedSkillCommandNames(pi),\n });\n const { expandDeferredSkillCommand } = await loadDeferredSkills();\n const text = expandDeferredSkillCommand(event.text, inventory.additionalSkills);\n return text === event.text ? { action: 'continue' } : { action: 'transform', text, images: event.images };\n });\n\n pi.on('before_agent_start', async (event, ctx) => {\n const generation = currentGeneration;\n if (!generation) return undefined;\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return undefined;\n\n if (!generation.diagnosticsShown && snapshot.diagnostics.length > 0) {\n generation.diagnosticsShown = true;\n ctx.ui.notify(`Skill discovery:\\n${snapshot.diagnostics.join('\\n')}`, WARNING);\n }\n const inventory = helpSkillView.merge({\n normalSkills: event.systemPromptOptions.skills ?? [],\n deferredSkills: snapshot.skills,\n });\n if (inventory.diagnosticKey !== shownHelpDiagnosticKey) {\n shownHelpDiagnosticKey = inventory.diagnosticKey;\n if (inventory.diagnostics.length > 0) {\n ctx.ui.notify(`Package Help:\\n${inventory.diagnostics.join('\\n')}`, WARNING);\n }\n }\n if (inventory.additionalSkills.length === 0) return undefined;\n\n const { buildPromptWithDeferredSkills } = await loadDeferredSkills();\n return {\n systemPrompt: buildPromptWithDeferredSkills(event.systemPrompt, event.systemPromptOptions, [\n ...inventory.additionalSkills,\n ]),\n };\n });\n\n return {\n current: async () => (currentGeneration ? currentSnapshot(currentGeneration) : undefined),\n };\n}\n\n/** The merged skill inventory the /skills catalogue is built from. */\nexport function skillInventory(helpSkillView: HelpSkillView, readiness: SkillReadiness) {\n return async (ctx: ExtensionContext) => {\n const snapshot = await readiness.current();\n const inventory = helpSkillView.merge({\n // Present on the runtime context; absent from the published ExtensionContext type.\n normalSkills:\n (ctx as unknown as { getSystemPromptOptions(): { skills?: Skill[] } }).getSystemPromptOptions().skills ?? [],\n deferredSkills: snapshot?.skills ?? [],\n });\n return { helpSkills: inventory.helpSkills, diagnostics: inventory.diagnostics };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skillReadiness.mjs","names":[],"sources":["../../../src/adapters/pi/skillReadiness.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext, Skill } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from '../../types/skills.ts';\nimport type { DeferredSkillSnapshot } from '../deferredSkills.ts';\nimport type { createActiveHelpSkillView } from '../helpSkills.ts';\n\nconst WARNING = 'warning';\n\ntype HelpSkillView = ReturnType<typeof createActiveHelpSkillView>;\n\ninterface SkillReadinessGeneration {\n readonly id: number;\n readonly ready: Promise<DeferredSkillSnapshot>;\n diagnosticsShown: boolean;\n}\n\nexport interface SkillReadiness {\n /** The snapshot for the live generation, or undefined once it is superseded. */\n current(): Promise<DeferredSkillSnapshot | undefined>;\n}\n\nfunction loadedSkillCommandNames(pi: ExtensionAPI): string[] {\n if (typeof pi.getCommands !== 'function') return [];\n return pi\n .getCommands()\n .filter((command) => command.source === 'skill' && command.name.startsWith(SKILL_COMMAND_PREFIX))\n .map((command) => command.name.slice(SKILL_COMMAND_PREFIX.length));\n}\n\nfunction failedSnapshot(cwd: string, error: unknown): DeferredSkillSnapshot {\n return {\n skills: [],\n diagnostics: [`${cwd}: ${error instanceof Error ? error.message : String(error)}`],\n };\n}\n\n/**\n * Discovery that starts with the session and is awaited only when something\n * needs it.\n *\n * Scanning every skill directory is the slowest thing this package does, so it\n * runs as a generation started on session_start. A reload starts a new one, and\n * a snapshot from a superseded generation is discarded rather than shown.\n */\nexport function registerSkillReadiness(\n pi: ExtensionAPI,\n helpSkillView: HelpSkillView,\n loadDeferredSkills: () => Promise<typeof import('../deferredSkills.ts')>,\n cordisContext: () => Context,\n): SkillReadiness {\n let generationSequence = 0;\n let currentGeneration: SkillReadinessGeneration | undefined;\n let shownHelpDiagnosticKey = '';\n\n const currentSnapshot = async (generation: SkillReadinessGeneration): Promise<DeferredSkillSnapshot | undefined> => {\n const snapshot = await generation.ready;\n return currentGeneration === generation ? snapshot : undefined;\n };\n\n pi.on('session_start', (_event, ctx) => {\n const state = requireDoomConfigContext(cordisContext()).harness;\n const id = ++generationSequence;\n const ready = loadDeferredSkills()\n .then(({ DeferredSkillLoader }) =>\n new DeferredSkillLoader({ cwd: ctx.cwd, skillPaths: state.skillDirectories }).start(),\n )\n .catch((error: unknown) => failedSnapshot(ctx.cwd, error));\n currentGeneration = { id, ready, diagnosticsShown: false };\n shownHelpDiagnosticKey = '';\n });\n\n pi.on('input', async (event) => {\n if (!event.text.startsWith(SKILL_INVOCATION_PREFIX)) return { action: 'continue' };\n\n const generation = currentGeneration;\n if (!generation) return { action: 'continue' };\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return { action: 'continue' };\n\n const inventory = helpSkillView.merge({\n normalSkills: [],\n deferredSkills: snapshot.skills,\n normalSkillNames: loadedSkillCommandNames(pi),\n });\n const { expandDeferredSkillCommand } = await loadDeferredSkills();\n const text = expandDeferredSkillCommand(event.text, inventory.additionalSkills);\n return text === event.text ? { action: 'continue' } : { action: 'transform', text, images: event.images };\n });\n\n pi.on('before_agent_start', async (event, ctx) => {\n const generation = currentGeneration;\n if (!generation) return undefined;\n const snapshot = await currentSnapshot(generation);\n if (!snapshot) return undefined;\n\n if (!generation.diagnosticsShown && snapshot.diagnostics.length > 0) {\n generation.diagnosticsShown = true;\n ctx.ui.notify(`Skill discovery:\\n${snapshot.diagnostics.join('\\n')}`, WARNING);\n }\n const inventory = helpSkillView.merge({\n normalSkills: event.systemPromptOptions.skills ?? [],\n deferredSkills: snapshot.skills,\n });\n if (inventory.diagnosticKey !== shownHelpDiagnosticKey) {\n shownHelpDiagnosticKey = inventory.diagnosticKey;\n if (inventory.diagnostics.length > 0) {\n ctx.ui.notify(`Package Help:\\n${inventory.diagnostics.join('\\n')}`, WARNING);\n }\n }\n if (inventory.additionalSkills.length === 0) return undefined;\n\n const { buildPromptWithDeferredSkills } = await loadDeferredSkills();\n return {\n systemPrompt: buildPromptWithDeferredSkills(event.systemPrompt, event.systemPromptOptions, [\n ...inventory.additionalSkills,\n ]),\n };\n });\n\n return {\n current: async () => (currentGeneration ? currentSnapshot(currentGeneration) : undefined),\n };\n}\n\n/** The merged skill inventory the /skills catalogue is built from. */\nexport function skillInventory(helpSkillView: HelpSkillView, readiness: SkillReadiness) {\n return async (ctx: ExtensionContext) => {\n const snapshot = await readiness.current();\n const inventory = helpSkillView.merge({\n // Present on the runtime context; absent from the published ExtensionContext type.\n normalSkills:\n (ctx as unknown as { getSystemPromptOptions(): { skills?: Skill[] } }).getSystemPromptOptions().skills ?? [],\n deferredSkills: snapshot?.skills ?? [],\n });\n return { helpSkills: inventory.helpSkills, diagnostics: inventory.diagnostics };\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skillCatalog.cjs","names":["path","loadSkillsFromDir","materializePluginEntries","resolvePluginEntries","listDomainNames","fs","formatSkillsForPrompt"],"sources":["../../src/adapters/skillCatalog.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { listDomainNames, type PluginSkillDiscovery, resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport type { SkillSourceEntry } from './skillSources.ts';\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\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 SkillSourceEntry[];\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 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":"skillCatalog.cjs","names":["path","loadSkillsFromDir","materializePluginEntries","resolvePluginEntries","listDomainNames","fs","formatSkillsForPrompt"],"sources":["../../src/adapters/skillCatalog.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { listDomainNames, type PluginSkillDiscovery, resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport type { SkillSourceContribution } from '@agimon-ai/doompi-extension-contracts/skills';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\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\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 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,4 +1,4 @@
1
- import { SkillSourceEntry } from "./skillSources.cjs";
1
+ import { SkillSourceContribution } from "@agimon-ai/doompi-extension-contracts/skills";
2
2
  import { Skill } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/skillCatalog.d.ts
4
4
  type SkillGroupKey = 'extensions' | 'help' | 'plugins' | 'default';
@@ -35,7 +35,7 @@ interface SkillCatalogOptions {
35
35
  repoRoot: string;
36
36
  /** Exact files the current selection resolved to, from `HarnessState.skillDirectories`. */
37
37
  activeSkillDirectories: readonly string[];
38
- extensionSources: readonly SkillSourceEntry[];
38
+ extensionSources: readonly SkillSourceContribution[];
39
39
  /** Activation-gated wrappers already accepted by the merged active-skill view. */
40
40
  helpSkills?: readonly Skill[];
41
41
  /** Bounded Help activation and collision diagnostics. */
@@ -1,5 +1,5 @@
1
- import { SkillSourceEntry } from "./skillSources.mjs";
2
1
  import { Skill } from "@earendil-works/pi-coding-agent";
2
+ import { SkillSourceContribution } from "@agimon-ai/doompi-extension-contracts/skills";
3
3
  //#region src/adapters/skillCatalog.d.ts
4
4
  type SkillGroupKey = 'extensions' | 'help' | 'plugins' | 'default';
5
5
  interface SkillEntry {
@@ -35,7 +35,7 @@ interface SkillCatalogOptions {
35
35
  repoRoot: string;
36
36
  /** Exact files the current selection resolved to, from `HarnessState.skillDirectories`. */
37
37
  activeSkillDirectories: readonly string[];
38
- extensionSources: readonly SkillSourceEntry[];
38
+ extensionSources: readonly SkillSourceContribution[];
39
39
  /** Activation-gated wrappers already accepted by the merged active-skill view. */
40
40
  helpSkills?: readonly Skill[];
41
41
  /** Bounded Help activation and collision diagnostics. */
@@ -1 +1 @@
1
- {"version":3,"file":"skillCatalog.mjs","names":[],"sources":["../../src/adapters/skillCatalog.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { listDomainNames, type PluginSkillDiscovery, resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport type { SkillSourceEntry } from './skillSources.ts';\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\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 SkillSourceEntry[];\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 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":"skillCatalog.mjs","names":[],"sources":["../../src/adapters/skillCatalog.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { listDomainNames, type PluginSkillDiscovery, resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport type { SkillSourceContribution } from '@agimon-ai/doompi-extension-contracts/skills';\nimport { formatSkillsForPrompt, loadSkillsFromDir, type Skill } from '@earendil-works/pi-coding-agent';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\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\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 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,2 +1,2 @@
1
- const e=require("../types/skills.cjs"),t=require("../services/skillText.cjs");let n=require("@agimon-ai/doompi-config/piContext");function r(r,i){r.registerCommand(e.SKILLS_COMMAND,{description:`Browse the skills this session loaded, and what other domains would add`,handler:async(e,r)=>{if(!r.hasUI||r.mode!==`tui`){r.ui.notify(`/skills requires interactive mode`,`error`);return}let a=(0,n.requireDoomConfigContext)(r).harness,[o,s]=await Promise.all([i.repositoryRoot(r),i.inventory(r)]),c=await i.buildCatalog({repoRoot:o,activeSkillDirectories:a.skillDirectories,extensionSources:i.extensionSources(),helpSkills:s.helpSkills,helpDiagnostics:[...s.diagnostics]}),l=await i.openOverlay(r,{catalog:c,repoRoot:o});l&&(r.ui.setEditorText(t.skillInvocation(l.skill.name)),r.ui.notify(`Loaded ${l.skill.name} into the prompt. Press enter to run it.`,`info`))}})}exports.registerSkillsCommand=r;
1
+ const e=require("../types/skills.cjs"),t=require("../services/skillText.cjs");let n=require("@agimon-ai/doompi-config/piContext");function r(r,i){r.registerCommand(e.SKILLS_COMMAND,{description:`Browse the skills this session loaded, and what other domains would add`,handler:async(e,r)=>{if(!r.hasUI||r.mode!==`tui`){r.ui.notify(`/skills requires interactive mode`,`error`);return}let a=(0,n.requireDoomConfigContext)(i.cordisContext()).harness,[o,s]=await Promise.all([i.repositoryRoot(r),i.inventory(r)]),c=await i.buildCatalog({repoRoot:o,activeSkillDirectories:a.skillDirectories,extensionSources:i.extensionSources(),helpSkills:s.helpSkills,helpDiagnostics:[...s.diagnostics]}),l=await i.openOverlay(r,{catalog:c,repoRoot:o});l&&(r.ui.setEditorText(t.skillInvocation(l.skill.name)),r.ui.notify(`Loaded ${l.skill.name} into the prompt. Press enter to run it.`,`info`))}})}exports.registerSkillsCommand=r;
2
2
  //# sourceMappingURL=skillsCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skillsCommand.cjs","names":["SKILLS_COMMAND","requireDoomConfigContext","skillInvocation"],"sources":["../../src/commands/skillsCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { skillInvocation } from '../services/skillText.ts';\nimport type { SkillCatalog, SkillCatalogOptions } from '../adapters/skillCatalog.ts';\nimport type { SkillsOverlayResult } from '../tui/skillsOverlay.ts';\nimport { SKILLS_COMMAND } from '../types/skills.ts';\n\nconst INFO = 'info';\nconst ERROR = 'error';\n\nexport interface SkillsCommandDependencies {\n /** Skill directories every loaded extension has contributed this session. */\n readonly extensionSources: () => readonly { source: string; directories: string[] }[];\n /** Merged view of normal, deferred and package Help skills. */\n readonly inventory: (ctx: ExtensionContext) => Promise<{\n readonly helpSkills: SkillCatalogOptions['helpSkills'];\n readonly diagnostics: readonly string[];\n }>;\n readonly repositoryRoot: (ctx: ExtensionContext) => Promise<string>;\n readonly buildCatalog: (request: SkillCatalogOptions) => Promise<SkillCatalog>;\n readonly openOverlay: (\n ctx: ExtensionContext,\n options: { catalog: SkillCatalog; repoRoot: string },\n ) => Promise<SkillsOverlayResult | undefined>;\n}\n\nexport function registerSkillsCommand(pi: ExtensionAPI, dependencies: SkillsCommandDependencies): void {\n pi.registerCommand(SKILLS_COMMAND, {\n description: 'Browse the skills this session loaded, and what other domains would add',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI || ctx.mode !== 'tui') {\n ctx.ui.notify('/skills requires interactive mode', ERROR);\n return;\n }\n\n const state = requireDoomConfigContext(ctx).harness;\n const [repoRoot, inventory] = await Promise.all([dependencies.repositoryRoot(ctx), dependencies.inventory(ctx)]);\n const catalog = await dependencies.buildCatalog({\n repoRoot,\n activeSkillDirectories: state.skillDirectories,\n extensionSources: dependencies.extensionSources(),\n helpSkills: inventory.helpSkills,\n helpDiagnostics: [...inventory.diagnostics],\n });\n\n const result = await dependencies.openOverlay(ctx, { catalog, repoRoot });\n if (!result) return;\n ctx.ui.setEditorText(skillInvocation(result.skill.name));\n ctx.ui.notify(`Loaded ${result.skill.name} into the prompt. Press enter to run it.`, INFO);\n },\n });\n}\n"],"mappings":"kIA0BA,SAAgB,EAAsB,EAAkB,EAA+C,CACrG,EAAG,gBAAgBA,EAAAA,eAAgB,CACjC,YAAa,0EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,OAAS,EAAI,OAAS,MAAO,CACpC,EAAI,GAAG,OAAO,oCAAqC,OAAK,EACxD,MACF,CAEA,IAAM,GAAA,EAAQC,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QACtC,CAAC,EAAU,GAAa,MAAM,QAAQ,IAAI,CAAC,EAAa,eAAe,CAAG,EAAG,EAAa,UAAU,CAAG,CAAC,CAAC,EACzG,EAAU,MAAM,EAAa,aAAa,CAC9C,WACA,uBAAwB,EAAM,iBAC9B,iBAAkB,EAAa,iBAAiB,EAChD,WAAY,EAAU,WACtB,gBAAiB,CAAC,GAAG,EAAU,WAAW,CAC5C,CAAC,EAEK,EAAS,MAAM,EAAa,YAAY,EAAK,CAAE,UAAS,UAAS,CAAC,EACnE,IACL,EAAI,GAAG,cAAcC,EAAAA,gBAAgB,EAAO,MAAM,IAAI,CAAC,EACvD,EAAI,GAAG,OAAO,UAAU,EAAO,MAAM,KAAK,0CAA2C,MAAI,EAC3F,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"skillsCommand.cjs","names":["SKILLS_COMMAND","requireDoomConfigContext","skillInvocation"],"sources":["../../src/commands/skillsCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { skillInvocation } from '../services/skillText.ts';\nimport type { SkillCatalog, SkillCatalogOptions } from '../adapters/skillCatalog.ts';\nimport type { SkillsOverlayResult } from '../tui/skillsOverlay.ts';\nimport { SKILLS_COMMAND } from '../types/skills.ts';\n\nconst INFO = 'info';\nconst ERROR = 'error';\n\nexport interface SkillsCommandDependencies {\n readonly cordisContext: () => Context;\n /** Skill directories every loaded extension has contributed this session. */\n readonly extensionSources: () => readonly { source: string; directories: readonly string[] }[];\n /** Merged view of normal, deferred and package Help skills. */\n readonly inventory: (ctx: ExtensionContext) => Promise<{\n readonly helpSkills: SkillCatalogOptions['helpSkills'];\n readonly diagnostics: readonly string[];\n }>;\n readonly repositoryRoot: (ctx: ExtensionContext) => Promise<string>;\n readonly buildCatalog: (request: SkillCatalogOptions) => Promise<SkillCatalog>;\n readonly openOverlay: (\n ctx: ExtensionContext,\n options: { catalog: SkillCatalog; repoRoot: string },\n ) => Promise<SkillsOverlayResult | undefined>;\n}\n\nexport function registerSkillsCommand(pi: ExtensionAPI, dependencies: SkillsCommandDependencies): void {\n pi.registerCommand(SKILLS_COMMAND, {\n description: 'Browse the skills this session loaded, and what other domains would add',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI || ctx.mode !== 'tui') {\n ctx.ui.notify('/skills requires interactive mode', ERROR);\n return;\n }\n\n const state = requireDoomConfigContext(dependencies.cordisContext()).harness;\n const [repoRoot, inventory] = await Promise.all([dependencies.repositoryRoot(ctx), dependencies.inventory(ctx)]);\n const catalog = await dependencies.buildCatalog({\n repoRoot,\n activeSkillDirectories: state.skillDirectories,\n extensionSources: dependencies.extensionSources(),\n helpSkills: inventory.helpSkills,\n helpDiagnostics: [...inventory.diagnostics],\n });\n\n const result = await dependencies.openOverlay(ctx, { catalog, repoRoot });\n if (!result) return;\n ctx.ui.setEditorText(skillInvocation(result.skill.name));\n ctx.ui.notify(`Loaded ${result.skill.name} into the prompt. Press enter to run it.`, INFO);\n },\n });\n}\n"],"mappings":"kIA4BA,SAAgB,EAAsB,EAAkB,EAA+C,CACrG,EAAG,gBAAgBA,EAAAA,eAAgB,CACjC,YAAa,0EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,OAAS,EAAI,OAAS,MAAO,CACpC,EAAI,GAAG,OAAO,oCAAqC,OAAK,EACxD,MACF,CAEA,IAAM,GAAA,EAAQC,EAAAA,yBAAAA,CAAyB,EAAa,cAAc,CAAC,CAAC,CAAC,QAC/D,CAAC,EAAU,GAAa,MAAM,QAAQ,IAAI,CAAC,EAAa,eAAe,CAAG,EAAG,EAAa,UAAU,CAAG,CAAC,CAAC,EACzG,EAAU,MAAM,EAAa,aAAa,CAC9C,WACA,uBAAwB,EAAM,iBAC9B,iBAAkB,EAAa,iBAAiB,EAChD,WAAY,EAAU,WACtB,gBAAiB,CAAC,GAAG,EAAU,WAAW,CAC5C,CAAC,EAEK,EAAS,MAAM,EAAa,YAAY,EAAK,CAAE,UAAS,UAAS,CAAC,EACnE,IACL,EAAI,GAAG,cAAcC,EAAAA,gBAAgB,EAAO,MAAM,IAAI,CAAC,EACvD,EAAI,GAAG,OAAO,UAAU,EAAO,MAAM,KAAK,0CAA2C,MAAI,EAC3F,CACF,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- import{SKILLS_COMMAND as e}from"../types/skills.mjs";import{skillInvocation as t}from"../services/skillText.mjs";import{requireDoomConfigContext as n}from"@agimon-ai/doompi-config/piContext";function r(r,i){r.registerCommand(e,{description:`Browse the skills this session loaded, and what other domains would add`,handler:async(e,r)=>{if(!r.hasUI||r.mode!==`tui`){r.ui.notify(`/skills requires interactive mode`,`error`);return}let a=n(r).harness,[o,s]=await Promise.all([i.repositoryRoot(r),i.inventory(r)]),c=await i.buildCatalog({repoRoot:o,activeSkillDirectories:a.skillDirectories,extensionSources:i.extensionSources(),helpSkills:s.helpSkills,helpDiagnostics:[...s.diagnostics]}),l=await i.openOverlay(r,{catalog:c,repoRoot:o});l&&(r.ui.setEditorText(t(l.skill.name)),r.ui.notify(`Loaded ${l.skill.name} into the prompt. Press enter to run it.`,`info`))}})}export{r as registerSkillsCommand};
1
+ import{SKILLS_COMMAND as e}from"../types/skills.mjs";import{skillInvocation as t}from"../services/skillText.mjs";import{requireDoomConfigContext as n}from"@agimon-ai/doompi-config/piContext";function r(r,i){r.registerCommand(e,{description:`Browse the skills this session loaded, and what other domains would add`,handler:async(e,r)=>{if(!r.hasUI||r.mode!==`tui`){r.ui.notify(`/skills requires interactive mode`,`error`);return}let a=n(i.cordisContext()).harness,[o,s]=await Promise.all([i.repositoryRoot(r),i.inventory(r)]),c=await i.buildCatalog({repoRoot:o,activeSkillDirectories:a.skillDirectories,extensionSources:i.extensionSources(),helpSkills:s.helpSkills,helpDiagnostics:[...s.diagnostics]}),l=await i.openOverlay(r,{catalog:c,repoRoot:o});l&&(r.ui.setEditorText(t(l.skill.name)),r.ui.notify(`Loaded ${l.skill.name} into the prompt. Press enter to run it.`,`info`))}})}export{r as registerSkillsCommand};
2
2
  //# sourceMappingURL=skillsCommand.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skillsCommand.mjs","names":[],"sources":["../../src/commands/skillsCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { skillInvocation } from '../services/skillText.ts';\nimport type { SkillCatalog, SkillCatalogOptions } from '../adapters/skillCatalog.ts';\nimport type { SkillsOverlayResult } from '../tui/skillsOverlay.ts';\nimport { SKILLS_COMMAND } from '../types/skills.ts';\n\nconst INFO = 'info';\nconst ERROR = 'error';\n\nexport interface SkillsCommandDependencies {\n /** Skill directories every loaded extension has contributed this session. */\n readonly extensionSources: () => readonly { source: string; directories: string[] }[];\n /** Merged view of normal, deferred and package Help skills. */\n readonly inventory: (ctx: ExtensionContext) => Promise<{\n readonly helpSkills: SkillCatalogOptions['helpSkills'];\n readonly diagnostics: readonly string[];\n }>;\n readonly repositoryRoot: (ctx: ExtensionContext) => Promise<string>;\n readonly buildCatalog: (request: SkillCatalogOptions) => Promise<SkillCatalog>;\n readonly openOverlay: (\n ctx: ExtensionContext,\n options: { catalog: SkillCatalog; repoRoot: string },\n ) => Promise<SkillsOverlayResult | undefined>;\n}\n\nexport function registerSkillsCommand(pi: ExtensionAPI, dependencies: SkillsCommandDependencies): void {\n pi.registerCommand(SKILLS_COMMAND, {\n description: 'Browse the skills this session loaded, and what other domains would add',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI || ctx.mode !== 'tui') {\n ctx.ui.notify('/skills requires interactive mode', ERROR);\n return;\n }\n\n const state = requireDoomConfigContext(ctx).harness;\n const [repoRoot, inventory] = await Promise.all([dependencies.repositoryRoot(ctx), dependencies.inventory(ctx)]);\n const catalog = await dependencies.buildCatalog({\n repoRoot,\n activeSkillDirectories: state.skillDirectories,\n extensionSources: dependencies.extensionSources(),\n helpSkills: inventory.helpSkills,\n helpDiagnostics: [...inventory.diagnostics],\n });\n\n const result = await dependencies.openOverlay(ctx, { catalog, repoRoot });\n if (!result) return;\n ctx.ui.setEditorText(skillInvocation(result.skill.name));\n ctx.ui.notify(`Loaded ${result.skill.name} into the prompt. Press enter to run it.`, INFO);\n },\n });\n}\n"],"mappings":"+LA0BA,SAAgB,EAAsB,EAAkB,EAA+C,CACrG,EAAG,gBAAgB,EAAgB,CACjC,YAAa,0EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,OAAS,EAAI,OAAS,MAAO,CACpC,EAAI,GAAG,OAAO,oCAAqC,OAAK,EACxD,MACF,CAEA,IAAM,EAAQ,EAAyB,CAAG,CAAC,CAAC,QACtC,CAAC,EAAU,GAAa,MAAM,QAAQ,IAAI,CAAC,EAAa,eAAe,CAAG,EAAG,EAAa,UAAU,CAAG,CAAC,CAAC,EACzG,EAAU,MAAM,EAAa,aAAa,CAC9C,WACA,uBAAwB,EAAM,iBAC9B,iBAAkB,EAAa,iBAAiB,EAChD,WAAY,EAAU,WACtB,gBAAiB,CAAC,GAAG,EAAU,WAAW,CAC5C,CAAC,EAEK,EAAS,MAAM,EAAa,YAAY,EAAK,CAAE,UAAS,UAAS,CAAC,EACnE,IACL,EAAI,GAAG,cAAc,EAAgB,EAAO,MAAM,IAAI,CAAC,EACvD,EAAI,GAAG,OAAO,UAAU,EAAO,MAAM,KAAK,0CAA2C,MAAI,EAC3F,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"skillsCommand.mjs","names":[],"sources":["../../src/commands/skillsCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { skillInvocation } from '../services/skillText.ts';\nimport type { SkillCatalog, SkillCatalogOptions } from '../adapters/skillCatalog.ts';\nimport type { SkillsOverlayResult } from '../tui/skillsOverlay.ts';\nimport { SKILLS_COMMAND } from '../types/skills.ts';\n\nconst INFO = 'info';\nconst ERROR = 'error';\n\nexport interface SkillsCommandDependencies {\n readonly cordisContext: () => Context;\n /** Skill directories every loaded extension has contributed this session. */\n readonly extensionSources: () => readonly { source: string; directories: readonly string[] }[];\n /** Merged view of normal, deferred and package Help skills. */\n readonly inventory: (ctx: ExtensionContext) => Promise<{\n readonly helpSkills: SkillCatalogOptions['helpSkills'];\n readonly diagnostics: readonly string[];\n }>;\n readonly repositoryRoot: (ctx: ExtensionContext) => Promise<string>;\n readonly buildCatalog: (request: SkillCatalogOptions) => Promise<SkillCatalog>;\n readonly openOverlay: (\n ctx: ExtensionContext,\n options: { catalog: SkillCatalog; repoRoot: string },\n ) => Promise<SkillsOverlayResult | undefined>;\n}\n\nexport function registerSkillsCommand(pi: ExtensionAPI, dependencies: SkillsCommandDependencies): void {\n pi.registerCommand(SKILLS_COMMAND, {\n description: 'Browse the skills this session loaded, and what other domains would add',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI || ctx.mode !== 'tui') {\n ctx.ui.notify('/skills requires interactive mode', ERROR);\n return;\n }\n\n const state = requireDoomConfigContext(dependencies.cordisContext()).harness;\n const [repoRoot, inventory] = await Promise.all([dependencies.repositoryRoot(ctx), dependencies.inventory(ctx)]);\n const catalog = await dependencies.buildCatalog({\n repoRoot,\n activeSkillDirectories: state.skillDirectories,\n extensionSources: dependencies.extensionSources(),\n helpSkills: inventory.helpSkills,\n helpDiagnostics: [...inventory.diagnostics],\n });\n\n const result = await dependencies.openOverlay(ctx, { catalog, repoRoot });\n if (!result) return;\n ctx.ui.setEditorText(skillInvocation(result.skill.name));\n ctx.ui.notify(`Loaded ${result.skill.name} into the prompt. Press enter to run it.`, INFO);\n },\n });\n}\n"],"mappings":"+LA4BA,SAAgB,EAAsB,EAAkB,EAA+C,CACrG,EAAG,gBAAgB,EAAgB,CACjC,YAAa,0EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAI,OAAS,EAAI,OAAS,MAAO,CACpC,EAAI,GAAG,OAAO,oCAAqC,OAAK,EACxD,MACF,CAEA,IAAM,EAAQ,EAAyB,EAAa,cAAc,CAAC,CAAC,CAAC,QAC/D,CAAC,EAAU,GAAa,MAAM,QAAQ,IAAI,CAAC,EAAa,eAAe,CAAG,EAAG,EAAa,UAAU,CAAG,CAAC,CAAC,EACzG,EAAU,MAAM,EAAa,aAAa,CAC9C,WACA,uBAAwB,EAAM,iBAC9B,iBAAkB,EAAa,iBAAiB,EAChD,WAAY,EAAU,WACtB,gBAAiB,CAAC,GAAG,EAAU,WAAW,CAC5C,CAAC,EAEK,EAAS,MAAM,EAAa,YAAY,EAAK,CAAE,UAAS,UAAS,CAAC,EACnE,IACL,EAAI,GAAG,cAAc,EAAgB,EAAO,MAAM,IAAI,CAAC,EACvD,EAAI,GAAG,OAAO,UAAU,EAAO,MAAM,KAAK,0CAA2C,MAAI,EAC3F,CACF,CAAC,CACH"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/helpSkills.cjs"),t=require("./types/skills.cjs"),n=require("./services/skillText.cjs"),r=require("./adapters/skillSources.cjs"),i=require("./adapters/pi/extension.cjs"),a=require("./tui/skillsOverlay.cjs");exports.LEADER_SOURCE=t.LEADER_SOURCE,exports.SKILLS_COMMAND=t.SKILLS_COMMAND,exports.SKILLS_LEADER_CONTRIBUTION=t.SKILLS_LEADER_CONTRIBUTION,exports.SKILL_COMMAND_PREFIX=t.SKILL_COMMAND_PREFIX,exports.SKILL_INVOCATION_PREFIX=t.SKILL_INVOCATION_PREFIX,exports.createActiveHelpSkillView=e.createActiveHelpSkillView,exports.openSkillsOverlay=a.openSkillsOverlay,exports.provideSkillSources=r.provideSkillSources,exports.skillInvocation=n.skillInvocation,exports.skillsExtension=i.default;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./types/skills.cjs"),t=require("./services/skillText.cjs"),n=require("./adapters/pi/extension.cjs"),r=require("./tui/skillsOverlay.cjs");exports.LEADER_SOURCE=e.LEADER_SOURCE,exports.SKILLS_COMMAND=e.SKILLS_COMMAND,exports.SKILLS_LEADER_CONTRIBUTION=e.SKILLS_LEADER_CONTRIBUTION,exports.SKILL_COMMAND_PREFIX=e.SKILL_COMMAND_PREFIX,exports.SKILL_INVOCATION_PREFIX=e.SKILL_INVOCATION_PREFIX,exports.openSkillsOverlay=r.openSkillsOverlay,exports.skillInvocation=t.skillInvocation,exports.skillsExtension=n.default;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,5 @@
1
- import { SkillSourceEntry, SkillSourceRegistry, provideSkillSources } from "./adapters/skillSources.cjs";
2
1
  import skillsExtension from "./adapters/pi/extension.cjs";
3
- import { createActiveHelpSkillView } from "./adapters/helpSkills.cjs";
4
2
  import { skillInvocation } from "./services/skillText.cjs";
5
3
  import { SkillsOverlayResult, openSkillsOverlay } from "./tui/skillsOverlay.cjs";
6
4
  import { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from "./types/skills.cjs";
7
- export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, type SkillSourceEntry, type SkillSourceRegistry, type SkillsOverlayResult, createActiveHelpSkillView, openSkillsOverlay, provideSkillSources, skillInvocation, skillsExtension };
5
+ export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, type SkillsOverlayResult, openSkillsOverlay, skillInvocation, skillsExtension };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,5 @@
1
- import { SkillSourceEntry, SkillSourceRegistry, provideSkillSources } from "./adapters/skillSources.mjs";
2
1
  import skillsExtension from "./adapters/pi/extension.mjs";
3
- import { createActiveHelpSkillView } from "./adapters/helpSkills.mjs";
4
2
  import { skillInvocation } from "./services/skillText.mjs";
5
3
  import { SkillsOverlayResult, openSkillsOverlay } from "./tui/skillsOverlay.mjs";
6
4
  import { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX } from "./types/skills.mjs";
7
- export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, type SkillSourceEntry, type SkillSourceRegistry, type SkillsOverlayResult, createActiveHelpSkillView, openSkillsOverlay, provideSkillSources, skillInvocation, skillsExtension };
5
+ export { LEADER_SOURCE, SKILLS_COMMAND, SKILLS_LEADER_CONTRIBUTION, SKILL_COMMAND_PREFIX, SKILL_INVOCATION_PREFIX, type SkillsOverlayResult, openSkillsOverlay, skillInvocation, skillsExtension };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createActiveHelpSkillView as e}from"./adapters/helpSkills.mjs";import{LEADER_SOURCE as t,SKILLS_COMMAND as n,SKILLS_LEADER_CONTRIBUTION as r,SKILL_COMMAND_PREFIX as i,SKILL_INVOCATION_PREFIX as a}from"./types/skills.mjs";import{skillInvocation as o}from"./services/skillText.mjs";import{provideSkillSources as s}from"./adapters/skillSources.mjs";import c from"./adapters/pi/extension.mjs";import{openSkillsOverlay as l}from"./tui/skillsOverlay.mjs";export{t as LEADER_SOURCE,n as SKILLS_COMMAND,r as SKILLS_LEADER_CONTRIBUTION,i as SKILL_COMMAND_PREFIX,a as SKILL_INVOCATION_PREFIX,e as createActiveHelpSkillView,l as openSkillsOverlay,s as provideSkillSources,o as skillInvocation,c as skillsExtension};
1
+ import{LEADER_SOURCE as e,SKILLS_COMMAND as t,SKILLS_LEADER_CONTRIBUTION as n,SKILL_COMMAND_PREFIX as r,SKILL_INVOCATION_PREFIX as i}from"./types/skills.mjs";import{skillInvocation as a}from"./services/skillText.mjs";import o from"./adapters/pi/extension.mjs";import{openSkillsOverlay as s}from"./tui/skillsOverlay.mjs";export{e as LEADER_SOURCE,t as SKILLS_COMMAND,n as SKILLS_LEADER_CONTRIBUTION,r as SKILL_COMMAND_PREFIX,i as SKILL_INVOCATION_PREFIX,s as openSkillsOverlay,a as skillInvocation,o as skillsExtension};
@@ -1,2 +1,2 @@
1
- const e=`skills`,t=`@agimon-ai/doompi-skill`,n={source:t,bindings:[{id:`skills.browse`,path:[{key:`e`,label:`extension`,order:50},{key:`s`,label:`skills`,detail:`browse catalog`}],command:{name:e}}]};exports.LEADER_SOURCE=t,exports.SKILLS_COMMAND=e,exports.SKILLS_LEADER_CONTRIBUTION=n,exports.SKILL_COMMAND_PREFIX=`skill:`,exports.SKILL_INVOCATION_PREFIX=`/skill:`;
1
+ const e=`skills`,t=`@agimon-ai/doompi-skill`,n={source:t,bindings:[{id:`skills.browse`,path:[{key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},{key:`s`,label:`skills`,detail:`browse catalog`}],command:{name:e}}]};exports.LEADER_SOURCE=t,exports.SKILLS_COMMAND=e,exports.SKILLS_LEADER_CONTRIBUTION=n,exports.SKILL_COMMAND_PREFIX=`skill:`,exports.SKILL_INVOCATION_PREFIX=`/skill:`;
2
2
  //# sourceMappingURL=skills.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skills.cjs","names":[],"sources":["../../src/types/skills.ts"],"sourcesContent":["import type { DoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\nexport const LEADER_SOURCE = '@agimon-ai/doompi-skill';\n\n/**\n * The `SPC e s` binding.\n *\n * The label and order repeat the core extension group exactly; the registry\n * rejects a contribution whose shared prefix disagrees.\n */\nexport const SKILLS_LEADER_CONTRIBUTION = {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'skills.browse',\n path: [\n { key: 'e', label: 'extension', order: 50 },\n { key: 's', label: 'skills', detail: 'browse catalog' },\n ],\n command: { name: SKILLS_COMMAND },\n },\n ],\n} satisfies DoomLeaderContribution;\n"],"mappings":"AAEA,MAAa,EAAiB,SAGjB,EAAgB,0BAQhB,EAA6B,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,gBACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,YAAa,MAAO,EAAG,EAC1C,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,gBAAiB,CACxD,EACA,QAAS,CAAE,KAAM,CAAe,CAClC,CACF,CACF"}
1
+ {"version":3,"file":"skills.cjs","names":[],"sources":["../../src/types/skills.ts"],"sourcesContent":["import type { LeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\nexport const LEADER_SOURCE = '@agimon-ai/doompi-skill';\n\n/**\n * The `SPC e s` binding.\n *\n * The label and order repeat the core extension group exactly; the registry\n * rejects a contribution whose shared prefix disagrees.\n */\nexport const SKILLS_LEADER_CONTRIBUTION = {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'skills.browse',\n path: [\n { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 },\n { key: 's', label: 'skills', detail: 'browse catalog' },\n ],\n command: { name: SKILLS_COMMAND },\n },\n ],\n} satisfies LeaderContribution;\n"],"mappings":"AAEA,MAAa,EAAiB,SAGjB,EAAgB,0BAQhB,EAA6B,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,gBACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,YAAa,OAAQ,2BAA4B,MAAO,EAAG,EAC9E,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,gBAAiB,CACxD,EACA,QAAS,CAAE,KAAM,CAAe,CAClC,CACF,CACF"}
@@ -16,6 +16,7 @@ declare const SKILLS_LEADER_CONTRIBUTION: {
16
16
  path: [{
17
17
  key: string;
18
18
  label: string;
19
+ detail: string;
19
20
  order: number;
20
21
  }, {
21
22
  key: string;
@@ -16,6 +16,7 @@ declare const SKILLS_LEADER_CONTRIBUTION: {
16
16
  path: [{
17
17
  key: string;
18
18
  label: string;
19
+ detail: string;
19
20
  order: number;
20
21
  }, {
21
22
  key: string;
@@ -1,2 +1,2 @@
1
- const e=`skills`,t=`/skill:`,n=`skill:`,r=`@agimon-ai/doompi-skill`,i={source:r,bindings:[{id:`skills.browse`,path:[{key:`e`,label:`extension`,order:50},{key:`s`,label:`skills`,detail:`browse catalog`}],command:{name:e}}]};export{r as LEADER_SOURCE,e as SKILLS_COMMAND,i as SKILLS_LEADER_CONTRIBUTION,n as SKILL_COMMAND_PREFIX,t as SKILL_INVOCATION_PREFIX};
1
+ const e=`skills`,t=`/skill:`,n=`skill:`,r=`@agimon-ai/doompi-skill`,i={source:r,bindings:[{id:`skills.browse`,path:[{key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},{key:`s`,label:`skills`,detail:`browse catalog`}],command:{name:e}}]};export{r as LEADER_SOURCE,e as SKILLS_COMMAND,i as SKILLS_LEADER_CONTRIBUTION,n as SKILL_COMMAND_PREFIX,t as SKILL_INVOCATION_PREFIX};
2
2
  //# sourceMappingURL=skills.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skills.mjs","names":[],"sources":["../../src/types/skills.ts"],"sourcesContent":["import type { DoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\nexport const LEADER_SOURCE = '@agimon-ai/doompi-skill';\n\n/**\n * The `SPC e s` binding.\n *\n * The label and order repeat the core extension group exactly; the registry\n * rejects a contribution whose shared prefix disagrees.\n */\nexport const SKILLS_LEADER_CONTRIBUTION = {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'skills.browse',\n path: [\n { key: 'e', label: 'extension', order: 50 },\n { key: 's', label: 'skills', detail: 'browse catalog' },\n ],\n command: { name: SKILLS_COMMAND },\n },\n ],\n} satisfies DoomLeaderContribution;\n"],"mappings":"AAEA,MAAa,EAAiB,SACjB,EAA0B,UAC1B,EAAuB,SACvB,EAAgB,0BAQhB,EAA6B,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,gBACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,YAAa,MAAO,EAAG,EAC1C,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,gBAAiB,CACxD,EACA,QAAS,CAAE,KAAM,CAAe,CAClC,CACF,CACF"}
1
+ {"version":3,"file":"skills.mjs","names":[],"sources":["../../src/types/skills.ts"],"sourcesContent":["import type { LeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\n\nexport const SKILLS_COMMAND = 'skills';\nexport const SKILL_INVOCATION_PREFIX = '/skill:';\nexport const SKILL_COMMAND_PREFIX = 'skill:';\nexport const LEADER_SOURCE = '@agimon-ai/doompi-skill';\n\n/**\n * The `SPC e s` binding.\n *\n * The label and order repeat the core extension group exactly; the registry\n * rejects a contribution whose shared prefix disagrees.\n */\nexport const SKILLS_LEADER_CONTRIBUTION = {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'skills.browse',\n path: [\n { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 },\n { key: 's', label: 'skills', detail: 'browse catalog' },\n ],\n command: { name: SKILLS_COMMAND },\n },\n ],\n} satisfies LeaderContribution;\n"],"mappings":"AAEA,MAAa,EAAiB,SACjB,EAA0B,UAC1B,EAAuB,SACvB,EAAgB,0BAQhB,EAA6B,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,gBACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,YAAa,OAAQ,2BAA4B,MAAO,EAAG,EAC9E,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,gBAAiB,CACxD,EACA,QAAS,CAAE,KAAM,CAAe,CAClC,CACF,CACF"}
package/llms.txt CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  The session skill catalogue, deferred skill discovery, and the `SPC e s` browser.
4
4
 
5
- ## Resources
5
+ ## How to author
6
6
 
7
- - [Package README](./README.md): the `/skills` command, deferred discovery, and how extensions contribute skill directories.
7
+ - [Author DoomPi skills](./src/prompts/doompi-author-skill/SKILL.md): Create, distribute, and register runtime skills or activation-gated Help prompts.
8
+
9
+ ## How to use
10
+
11
+ - [Use DoomPi skills](./src/prompts/doompi-use-skill/SKILL.md): Browse the catalog, invoke deferred skills, and diagnose missing or shadowed skills.
12
+
13
+ ## Package reference
14
+
15
+ - [Package README](./README.md): The `/skills` command, deferred discovery, and extension-contributed skill directories.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-skill",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.20",
4
4
  "description": "Session skill catalogue, deferred skill discovery, and the skill browser for DoomPi.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -15,12 +15,21 @@
15
15
  "skill-discovery"
16
16
  ],
17
17
  "homepage": "https://agimon.ai",
18
+ "bugs": {
19
+ "url": "https://github.com/AgiFlow/doompi/issues"
20
+ },
18
21
  "license": "MIT",
19
22
  "author": "Vuong Ngo",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/AgiFlow/doompi.git",
26
+ "directory": "packages/core/doompi-skill"
27
+ },
20
28
  "files": [
21
29
  "dist",
22
30
  "llms.txt",
23
31
  "README.md",
32
+ "src/prompts",
24
33
  "LICENSE",
25
34
  "package.json"
26
35
  ],
@@ -51,27 +60,27 @@
51
60
  },
52
61
  "dependencies": {
53
62
  "@deepseek-ai/cordis": "4.0.1",
54
- "gpt-tokenizer": "3.4.0",
55
- "@agimon-ai/doompi-config": "0.0.1-alpha.24",
56
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.24",
57
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.24",
58
- "@agimon-ai/doompi-domain": "0.0.1-alpha.2",
59
- "@agimon-ai/doompi-ui": "0.0.1-alpha.24"
63
+ "gpt-tokenizer": "4.0.0",
64
+ "@agimon-ai/doompi-domain": "0.0.1-alpha.20",
65
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.42",
66
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.42",
67
+ "@agimon-ai/doompi-config": "0.0.1-alpha.42",
68
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.42"
60
69
  },
61
70
  "devDependencies": {
62
71
  "@deepseek-ai/cordis": "4.0.1",
63
- "@earendil-works/pi-coding-agent": "0.84.2",
64
- "@earendil-works/pi-tui": "0.84.2",
65
- "@types/node": "26.1.1",
66
- "@vitest/coverage-v8": "4.1.10",
72
+ "@earendil-works/pi-coding-agent": "0.84.4",
73
+ "@earendil-works/pi-tui": "0.84.4",
74
+ "@types/node": "26.2.0",
75
+ "@vitest/coverage-v8": "4.1.11",
67
76
  "tsdown": "0.22.14",
68
- "typescript": "6.0.3",
69
- "vitest": "4.1.10",
70
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.24"
77
+ "typescript": "7.0.2",
78
+ "vitest": "4.1.11",
79
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.40"
71
80
  },
72
81
  "peerDependencies": {
73
- "@earendil-works/pi-coding-agent": "0.84.2",
74
- "@earendil-works/pi-tui": "0.84.2"
82
+ "@earendil-works/pi-coding-agent": "0.84.4",
83
+ "@earendil-works/pi-tui": "0.84.4"
75
84
  },
76
85
  "peerDependenciesMeta": {
77
86
  "@earendil-works/pi-coding-agent": {
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: doompi-author-skill
3
+ description: Author and distribute DoomPi 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 DoomPi package.
4
+ ---
5
+
6
+ # Author DoomPi skills
7
+
8
+ Create one focused skill per directory. Name the directory with lowercase letters, digits, and hyphens, and give it a required `SKILL.md`:
9
+
10
+ ```markdown
11
+ ---
12
+ name: repository-review
13
+ description: Review repository changes when the user asks for implementation-risk and regression analysis.
14
+ ---
15
+
16
+ # Repository review
17
+
18
+ Put the essential workflow and non-obvious constraints here.
19
+ ```
20
+
21
+ The frontmatter `name` must match the directory. Make the description specific enough for automatic routing. Keep the entrypoint concise, and put substantial conditional details in linked `references/`. Add `scripts/`, `assets/`, or `agents/` only when the skill actually uses them.
22
+
23
+ ## Choose the distribution path
24
+
25
+ - For a normal Pi skill that should be discoverable whenever its owning extension is active, ship the skill under a package-owned runtime directory such as `skills/<name>/SKILL.md`. Register that directory through `DOOM_SKILL_SOURCES_SERVICE` inside an owning `cordis.inject(...)`, and return the contribution disposer.
26
+ - For guidance that should appear only while DoomPi Help is active, put it at `src/prompts/<name>/SKILL.md`, publish `src/prompts`, link it from the package's H1-led `llms.txt`, and register a Help descriptor through `DOOM_HELP_SERVICE`.
27
+ - For repository content selected by domains, place the skill in the plugin or shared skill source configured by `domains.yaml`; do not hard-code that directory into the Skill package.
28
+
29
+ Help descriptors carry only `name` and `description`. The Help service resolves the owning package from the contribution's exact package `source` and `moduleUrl`, then wraps its `llms.txt`. Keep every index link inside the published package.
30
+
31
+ ## Verification
32
+
33
+ Validate the skill frontmatter, ensure every relative reference resolves, and test the packed package rather than relying on checkout-only files. For Cordis contributions, cover late provider arrival, provider replacement, withdrawal, and package shutdown. Use `/skills` or `SPC e s` in an interactive session to confirm the expected owner and invocation name.
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: doompi-use-skill
3
+ description: Use DoomPi'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.
4
+ ---
5
+
6
+ # Use DoomPi skills
7
+
8
+ Open the skill browser with `/skills` or `SPC e s`. The catalog groups normal session skills, package Help skills, plugin skills, and extension-contributed directories by owner.
9
+
10
+ Invoke a skill with `/skill:<name>`. DoomPi expands deferred skills before Pi handles the input, so a skill can be invokable even when its full body was not placed in the initial prompt. Help skills appear only while Help mode is active.
11
+
12
+ When a skill is missing:
13
+
14
+ 1. Confirm its owning package, plugin, domain, or Help mode is active.
15
+ 2. Wait for deferred discovery to finish by opening `/skills` or invoking the exact skill.
16
+ 3. Check the browser diagnostics for unreadable `SKILL.md` files, invalid frontmatter, duplicate names, or unavailable Help resources.
17
+ 4. If two sources use the same name, remember that normal loaded and deferred skills take precedence over Help skills.
18
+ 5. After changing domains or package composition, reload or relaunch as requested so the session receives the new skill directories.
@@ -1,27 +0,0 @@
1
- import { ExtensionAPI, Skill } from "@earendil-works/pi-coding-agent";
2
- import "@agimon-ai/doompi-extension-contracts/help";
3
- //#region src/adapters/helpSkills.d.ts
4
- interface SkillInventoryInput {
5
- normalSkills: readonly Skill[];
6
- deferredSkills: readonly Skill[];
7
- /** Names from Pi's command catalog when full normal Skill records are unavailable. */
8
- normalSkillNames?: readonly string[];
9
- }
10
- interface MergedSkillInventory {
11
- /** Static, deferred, then accepted Help skills in invocation-precedence order. */
12
- skills: readonly Skill[];
13
- /** Deferred and Help skills that Doom Pi must append to Pi's existing prompt inventory. */
14
- additionalSkills: readonly Skill[];
15
- /** Accepted Help skills, retained separately for the Help catalog group. */
16
- helpSkills: readonly Skill[];
17
- diagnostics: readonly string[];
18
- diagnosticKey: string;
19
- }
20
- interface ActiveHelpSkillView {
21
- merge(input: SkillInventoryInput): MergedSkillInventory;
22
- dispose(): void;
23
- }
24
- declare function createActiveHelpSkillView(pi: Pick<ExtensionAPI, 'events' | 'on'>): ActiveHelpSkillView;
25
- //#endregion
26
- export { ActiveHelpSkillView, MergedSkillInventory, SkillInventoryInput, createActiveHelpSkillView };
27
- //# sourceMappingURL=helpSkills.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpSkills.d.cts","names":[],"sources":["../../src/adapters/helpSkills.ts"],"mappings":";;;UAaiB;EACf,uBAAuB;EACvB,yBAAyB;;EAEzB;;UAGe;;EAEf,iBAAiB;;EAEjB,2BAA2B;;EAE3B,qBAAqB;EACrB;EACA;;UAGe;EACf,MAAM,OAAO,sBAAsB;EACnC;;iBA6Ec,0BAA0B,IAAI,KAAK,iCAAiC"}
@@ -1,27 +0,0 @@
1
- import { ExtensionAPI, Skill } from "@earendil-works/pi-coding-agent";
2
- import "@agimon-ai/doompi-extension-contracts/help";
3
- //#region src/adapters/helpSkills.d.ts
4
- interface SkillInventoryInput {
5
- normalSkills: readonly Skill[];
6
- deferredSkills: readonly Skill[];
7
- /** Names from Pi's command catalog when full normal Skill records are unavailable. */
8
- normalSkillNames?: readonly string[];
9
- }
10
- interface MergedSkillInventory {
11
- /** Static, deferred, then accepted Help skills in invocation-precedence order. */
12
- skills: readonly Skill[];
13
- /** Deferred and Help skills that Doom Pi must append to Pi's existing prompt inventory. */
14
- additionalSkills: readonly Skill[];
15
- /** Accepted Help skills, retained separately for the Help catalog group. */
16
- helpSkills: readonly Skill[];
17
- diagnostics: readonly string[];
18
- diagnosticKey: string;
19
- }
20
- interface ActiveHelpSkillView {
21
- merge(input: SkillInventoryInput): MergedSkillInventory;
22
- dispose(): void;
23
- }
24
- declare function createActiveHelpSkillView(pi: Pick<ExtensionAPI, 'events' | 'on'>): ActiveHelpSkillView;
25
- //#endregion
26
- export { ActiveHelpSkillView, MergedSkillInventory, SkillInventoryInput, createActiveHelpSkillView };
27
- //# sourceMappingURL=helpSkills.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpSkills.d.mts","names":[],"sources":["../../src/adapters/helpSkills.ts"],"mappings":";;;UAaiB;EACf,uBAAuB;EACvB,yBAAyB;;EAEzB;;UAGe;;EAEf,iBAAiB;;EAEjB,2BAA2B;;EAE3B,qBAAqB;EACrB;EACA;;UAGe;EACf,MAAM,OAAO,sBAAsB;EACnC;;iBA6Ec,0BAA0B,IAAI,KAAK,iCAAiC"}
@@ -1,2 +0,0 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("@agimon-ai/doompi-extension-contracts/protocol"),i=require("@agimon-ai/doompi-extension-contracts/skills");function a(e){let a=new Map,o=new Map,s=0,c,l=()=>{c?.(),c=void 0};return e.on(`session_start`,(u,d)=>{l(),a.clear(),o.clear();let f=(0,r.createProtocolRuntime)({bus:e.events,source:`@agimon-ai/doompi`,sessionId:d.sessionManager.getSessionId()}),p=f.provide(i.SkillSourceRegistrationProtocol,({contribution:e})=>{let r=e.directories.filter(e=>n.default.isAbsolute(e)&&t.default.existsSync(e)),i=`skill-source-${++s}`,c=a.get(e.source);return c&&o.delete(c.handleId),a.set(e.source,{handleId:i,directories:r}),o.set(i,e.source),{handleId:i}}),m=f.provide(i.SkillSourceUnregistrationProtocol,({handleId:e})=>{let t=o.get(e);return t?(o.delete(e),a.get(t)?.handleId===e&&a.delete(t),{removed:!0}):{removed:!1}});c=()=>{p(),m()}}),e.on(`session_shutdown`,()=>{l(),a.clear(),o.clear()}),{list(){return[...a.entries()].map(([e,t])=>({source:e,directories:[...t.directories]})).sort((e,t)=>e.source.localeCompare(t.source))},dispose(){l(),a.clear(),o.clear()}}}exports.provideSkillSources=a;
2
- //# sourceMappingURL=skillSources.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"skillSources.cjs","names":["createProtocolRuntime","SkillSourceRegistrationProtocol","path","fs","SkillSourceUnregistrationProtocol"],"sources":["../../src/adapters/skillSources.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport {\n SkillSourceRegistrationProtocol,\n SkillSourceUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/skills';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\nconst HOST_SOURCE = '@agimon-ai/doompi';\n\nexport interface SkillSourceEntry {\n source: string;\n directories: string[];\n}\n\nexport interface SkillSourceRegistry {\n /** Registered sources, sorted by name so the catalogue order is stable. */\n list(): readonly SkillSourceEntry[];\n dispose(): void;\n}\n\n/**\n * Hosts the registry extensions publish their skill directories to.\n *\n * Contributions are keyed by source rather than by handle, so a reload that\n * re-registers the same package replaces its entry instead of doubling it. The\n * handle is still tracked, because an unregistration that arrives after a\n * replacement must not take the newer entry down with it.\n */\nexport function provideSkillSources(pi: Pick<ExtensionAPI, 'events' | 'on'>): SkillSourceRegistry {\n const bySource = new Map<string, { handleId: string; directories: string[] }>();\n const sourceByHandle = new Map<string, string>();\n let handleSequence = 0;\n let disposeProviders: (() => void) | undefined;\n\n const stopProviders = (): void => {\n disposeProviders?.();\n disposeProviders = undefined;\n };\n\n pi.on('session_start', (_event, ctx) => {\n stopProviders();\n bySource.clear();\n sourceByHandle.clear();\n const runtime = createProtocolRuntime({\n bus: pi.events,\n source: HOST_SOURCE,\n sessionId: ctx.sessionManager.getSessionId(),\n });\n\n const stopRegister = runtime.provide(SkillSourceRegistrationProtocol, ({ contribution }) => {\n // A path that does not resolve is dropped rather than rejected: the\n // contributor guards on existence too, and failing its registration would\n // surface as a session-start error for a directory nobody can browse.\n const directories = contribution.directories.filter(\n (directory) => path.isAbsolute(directory) && fs.existsSync(directory),\n );\n const handleId = `skill-source-${++handleSequence}`;\n const previous = bySource.get(contribution.source);\n if (previous) sourceByHandle.delete(previous.handleId);\n bySource.set(contribution.source, { handleId, directories });\n sourceByHandle.set(handleId, contribution.source);\n return { handleId };\n });\n\n const stopUnregister = runtime.provide(SkillSourceUnregistrationProtocol, ({ handleId }) => {\n const source = sourceByHandle.get(handleId);\n if (!source) return { removed: false };\n sourceByHandle.delete(handleId);\n // Guarded so a late unregistration cannot remove the replacement that\n // already took this source's slot.\n if (bySource.get(source)?.handleId === handleId) bySource.delete(source);\n return { removed: true };\n });\n\n disposeProviders = () => {\n stopRegister();\n stopUnregister();\n };\n });\n\n pi.on('session_shutdown', () => {\n stopProviders();\n bySource.clear();\n sourceByHandle.clear();\n });\n\n return {\n list() {\n return [...bySource.entries()]\n .map(([source, entry]) => ({ source, directories: [...entry.directories] }))\n .sort((left, right) => left.source.localeCompare(right.source));\n },\n dispose() {\n stopProviders();\n bySource.clear();\n sourceByHandle.clear();\n },\n };\n}\n"],"mappings":"qQA8BA,SAAgB,EAAoB,EAA8D,CAChG,IAAM,EAAW,IAAI,IACf,EAAiB,IAAI,IACvB,EAAiB,EACjB,EAEE,MAA4B,CAChC,IAAmB,EACnB,EAAmB,IAAA,EACrB,EAiDA,OA/CA,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,EAAc,EACd,EAAS,MAAM,EACf,EAAe,MAAM,EACrB,IAAM,GAAA,EAAUA,EAAAA,sBAAAA,CAAsB,CACpC,IAAK,EAAG,OACR,OAAQ,oBACR,UAAW,EAAI,eAAe,aAAa,CAC7C,CAAC,EAEK,EAAe,EAAQ,QAAQC,EAAAA,iCAAkC,CAAE,kBAAmB,CAI1F,IAAM,EAAc,EAAa,YAAY,OAC1C,GAAcC,EAAAA,QAAK,WAAW,CAAS,GAAKC,EAAAA,QAAG,WAAW,CAAS,CACtE,EACM,EAAW,gBAAgB,EAAE,IAC7B,EAAW,EAAS,IAAI,EAAa,MAAM,EAIjD,OAHI,GAAU,EAAe,OAAO,EAAS,QAAQ,EACrD,EAAS,IAAI,EAAa,OAAQ,CAAE,WAAU,aAAY,CAAC,EAC3D,EAAe,IAAI,EAAU,EAAa,MAAM,EACzC,CAAE,UAAS,CACpB,CAAC,EAEK,EAAiB,EAAQ,QAAQC,EAAAA,mCAAoC,CAAE,cAAe,CAC1F,IAAM,EAAS,EAAe,IAAI,CAAQ,EAM1C,OALK,GACL,EAAe,OAAO,CAAQ,EAG1B,EAAS,IAAI,CAAM,CAAC,EAAE,WAAa,GAAU,EAAS,OAAO,CAAM,EAChE,CAAE,QAAS,EAAK,GALH,CAAE,QAAS,EAAM,CAMvC,CAAC,EAED,MAAyB,CACvB,EAAa,EACb,EAAe,CACjB,CACF,CAAC,EAED,EAAG,GAAG,uBAA0B,CAC9B,EAAc,EACd,EAAS,MAAM,EACf,EAAe,MAAM,CACvB,CAAC,EAEM,CACL,MAAO,CACL,MAAO,CAAC,GAAG,EAAS,QAAQ,CAAC,CAAC,CAC3B,KAAK,CAAC,EAAQ,MAAY,CAAE,SAAQ,YAAa,CAAC,GAAG,EAAM,WAAW,CAAE,EAAE,CAAC,CAC3E,MAAM,EAAM,IAAU,EAAK,OAAO,cAAc,EAAM,MAAM,CAAC,CAClE,EACA,SAAU,CACR,EAAc,EACd,EAAS,MAAM,EACf,EAAe,MAAM,CACvB,CACF,CACF"}
@@ -1,23 +0,0 @@
1
- import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- //#region src/adapters/skillSources.d.ts
3
- interface SkillSourceEntry {
4
- source: string;
5
- directories: string[];
6
- }
7
- interface SkillSourceRegistry {
8
- /** Registered sources, sorted by name so the catalogue order is stable. */
9
- list(): readonly SkillSourceEntry[];
10
- dispose(): void;
11
- }
12
- /**
13
- * Hosts the registry extensions publish their skill directories to.
14
- *
15
- * Contributions are keyed by source rather than by handle, so a reload that
16
- * re-registers the same package replaces its entry instead of doubling it. The
17
- * handle is still tracked, because an unregistration that arrives after a
18
- * replacement must not take the newer entry down with it.
19
- */
20
- declare function provideSkillSources(pi: Pick<ExtensionAPI, 'events' | 'on'>): SkillSourceRegistry;
21
- //#endregion
22
- export { SkillSourceEntry, SkillSourceRegistry, provideSkillSources };
23
- //# sourceMappingURL=skillSources.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"skillSources.d.cts","names":[],"sources":["../../src/adapters/skillSources.ts"],"mappings":";;UAWiB;EACf;EACA;;UAGe;;EAEf,iBAAiB;EACjB;;;;;;;;;;iBAWc,oBAAoB,IAAI,KAAK,iCAAiC"}
@@ -1,23 +0,0 @@
1
- import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- //#region src/adapters/skillSources.d.ts
3
- interface SkillSourceEntry {
4
- source: string;
5
- directories: string[];
6
- }
7
- interface SkillSourceRegistry {
8
- /** Registered sources, sorted by name so the catalogue order is stable. */
9
- list(): readonly SkillSourceEntry[];
10
- dispose(): void;
11
- }
12
- /**
13
- * Hosts the registry extensions publish their skill directories to.
14
- *
15
- * Contributions are keyed by source rather than by handle, so a reload that
16
- * re-registers the same package replaces its entry instead of doubling it. The
17
- * handle is still tracked, because an unregistration that arrives after a
18
- * replacement must not take the newer entry down with it.
19
- */
20
- declare function provideSkillSources(pi: Pick<ExtensionAPI, 'events' | 'on'>): SkillSourceRegistry;
21
- //#endregion
22
- export { SkillSourceEntry, SkillSourceRegistry, provideSkillSources };
23
- //# sourceMappingURL=skillSources.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"skillSources.d.mts","names":[],"sources":["../../src/adapters/skillSources.ts"],"mappings":";;UAWiB;EACf;EACA;;UAGe;;EAEf,iBAAiB;EACjB;;;;;;;;;;iBAWc,oBAAoB,IAAI,KAAK,iCAAiC"}
@@ -1,2 +0,0 @@
1
- import e from"node:fs";import t from"node:path";import{createProtocolRuntime as n}from"@agimon-ai/doompi-extension-contracts/protocol";import{SkillSourceRegistrationProtocol as r,SkillSourceUnregistrationProtocol as i}from"@agimon-ai/doompi-extension-contracts/skills";function a(a){let o=new Map,s=new Map,c=0,l,u=()=>{l?.(),l=void 0};return a.on(`session_start`,(d,f)=>{u(),o.clear(),s.clear();let p=n({bus:a.events,source:`@agimon-ai/doompi`,sessionId:f.sessionManager.getSessionId()}),m=p.provide(r,({contribution:n})=>{let r=n.directories.filter(n=>t.isAbsolute(n)&&e.existsSync(n)),i=`skill-source-${++c}`,a=o.get(n.source);return a&&s.delete(a.handleId),o.set(n.source,{handleId:i,directories:r}),s.set(i,n.source),{handleId:i}}),h=p.provide(i,({handleId:e})=>{let t=s.get(e);return t?(s.delete(e),o.get(t)?.handleId===e&&o.delete(t),{removed:!0}):{removed:!1}});l=()=>{m(),h()}}),a.on(`session_shutdown`,()=>{u(),o.clear(),s.clear()}),{list(){return[...o.entries()].map(([e,t])=>({source:e,directories:[...t.directories]})).sort((e,t)=>e.source.localeCompare(t.source))},dispose(){u(),o.clear(),s.clear()}}}export{a as provideSkillSources};
2
- //# sourceMappingURL=skillSources.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"skillSources.mjs","names":[],"sources":["../../src/adapters/skillSources.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport {\n SkillSourceRegistrationProtocol,\n SkillSourceUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/skills';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\nconst HOST_SOURCE = '@agimon-ai/doompi';\n\nexport interface SkillSourceEntry {\n source: string;\n directories: string[];\n}\n\nexport interface SkillSourceRegistry {\n /** Registered sources, sorted by name so the catalogue order is stable. */\n list(): readonly SkillSourceEntry[];\n dispose(): void;\n}\n\n/**\n * Hosts the registry extensions publish their skill directories to.\n *\n * Contributions are keyed by source rather than by handle, so a reload that\n * re-registers the same package replaces its entry instead of doubling it. The\n * handle is still tracked, because an unregistration that arrives after a\n * replacement must not take the newer entry down with it.\n */\nexport function provideSkillSources(pi: Pick<ExtensionAPI, 'events' | 'on'>): SkillSourceRegistry {\n const bySource = new Map<string, { handleId: string; directories: string[] }>();\n const sourceByHandle = new Map<string, string>();\n let handleSequence = 0;\n let disposeProviders: (() => void) | undefined;\n\n const stopProviders = (): void => {\n disposeProviders?.();\n disposeProviders = undefined;\n };\n\n pi.on('session_start', (_event, ctx) => {\n stopProviders();\n bySource.clear();\n sourceByHandle.clear();\n const runtime = createProtocolRuntime({\n bus: pi.events,\n source: HOST_SOURCE,\n sessionId: ctx.sessionManager.getSessionId(),\n });\n\n const stopRegister = runtime.provide(SkillSourceRegistrationProtocol, ({ contribution }) => {\n // A path that does not resolve is dropped rather than rejected: the\n // contributor guards on existence too, and failing its registration would\n // surface as a session-start error for a directory nobody can browse.\n const directories = contribution.directories.filter(\n (directory) => path.isAbsolute(directory) && fs.existsSync(directory),\n );\n const handleId = `skill-source-${++handleSequence}`;\n const previous = bySource.get(contribution.source);\n if (previous) sourceByHandle.delete(previous.handleId);\n bySource.set(contribution.source, { handleId, directories });\n sourceByHandle.set(handleId, contribution.source);\n return { handleId };\n });\n\n const stopUnregister = runtime.provide(SkillSourceUnregistrationProtocol, ({ handleId }) => {\n const source = sourceByHandle.get(handleId);\n if (!source) return { removed: false };\n sourceByHandle.delete(handleId);\n // Guarded so a late unregistration cannot remove the replacement that\n // already took this source's slot.\n if (bySource.get(source)?.handleId === handleId) bySource.delete(source);\n return { removed: true };\n });\n\n disposeProviders = () => {\n stopRegister();\n stopUnregister();\n };\n });\n\n pi.on('session_shutdown', () => {\n stopProviders();\n bySource.clear();\n sourceByHandle.clear();\n });\n\n return {\n list() {\n return [...bySource.entries()]\n .map(([source, entry]) => ({ source, directories: [...entry.directories] }))\n .sort((left, right) => left.source.localeCompare(right.source));\n },\n dispose() {\n stopProviders();\n bySource.clear();\n sourceByHandle.clear();\n },\n };\n}\n"],"mappings":"6QA8BA,SAAgB,EAAoB,EAA8D,CAChG,IAAM,EAAW,IAAI,IACf,EAAiB,IAAI,IACvB,EAAiB,EACjB,EAEE,MAA4B,CAChC,IAAmB,EACnB,EAAmB,IAAA,EACrB,EAiDA,OA/CA,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,EAAc,EACd,EAAS,MAAM,EACf,EAAe,MAAM,EACrB,IAAM,EAAU,EAAsB,CACpC,IAAK,EAAG,OACR,OAAQ,oBACR,UAAW,EAAI,eAAe,aAAa,CAC7C,CAAC,EAEK,EAAe,EAAQ,QAAQ,GAAkC,CAAE,kBAAmB,CAI1F,IAAM,EAAc,EAAa,YAAY,OAC1C,GAAc,EAAK,WAAW,CAAS,GAAK,EAAG,WAAW,CAAS,CACtE,EACM,EAAW,gBAAgB,EAAE,IAC7B,EAAW,EAAS,IAAI,EAAa,MAAM,EAIjD,OAHI,GAAU,EAAe,OAAO,EAAS,QAAQ,EACrD,EAAS,IAAI,EAAa,OAAQ,CAAE,WAAU,aAAY,CAAC,EAC3D,EAAe,IAAI,EAAU,EAAa,MAAM,EACzC,CAAE,UAAS,CACpB,CAAC,EAEK,EAAiB,EAAQ,QAAQ,GAAoC,CAAE,cAAe,CAC1F,IAAM,EAAS,EAAe,IAAI,CAAQ,EAM1C,OALK,GACL,EAAe,OAAO,CAAQ,EAG1B,EAAS,IAAI,CAAM,CAAC,EAAE,WAAa,GAAU,EAAS,OAAO,CAAM,EAChE,CAAE,QAAS,EAAK,GALH,CAAE,QAAS,EAAM,CAMvC,CAAC,EAED,MAAyB,CACvB,EAAa,EACb,EAAe,CACjB,CACF,CAAC,EAED,EAAG,GAAG,uBAA0B,CAC9B,EAAc,EACd,EAAS,MAAM,EACf,EAAe,MAAM,CACvB,CAAC,EAEM,CACL,MAAO,CACL,MAAO,CAAC,GAAG,EAAS,QAAQ,CAAC,CAAC,CAC3B,KAAK,CAAC,EAAQ,MAAY,CAAE,SAAQ,YAAa,CAAC,GAAG,EAAM,WAAW,CAAE,EAAE,CAAC,CAC3E,MAAM,EAAM,IAAU,EAAK,OAAO,cAAc,EAAM,MAAM,CAAC,CAClE,EACA,SAAU,CACR,EAAc,EACd,EAAS,MAAM,EACf,EAAe,MAAM,CACvB,CACF,CACF"}