| 1 | import { |
| 2 | PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS, |
| 3 | ProviderDriverKind, |
| 4 | ProviderInstanceId, |
| 5 | type ServerProvider, |
| 6 | } from "@t3tools/contracts"; |
| 7 | import { formatProviderSkillDisplayName } from "@t3tools/shared/inlineSkills"; |
| 8 | import { describe, expect, it } from "vite-plus/test"; |
| 9 | |
| 10 | import { |
| 11 | dedupeProviderSkillsByName, |
| 12 | getProviderSlashCommandsForSlashMenu, |
| 13 | getProviderSkillsForSlashMenu, |
| 14 | hasCompleteProviderWorkspaceSnapshot, |
| 15 | hasCurrentProviderWorkspaceSnapshot, |
| 16 | resolveProviderSkillsForCwd, |
| 17 | resolveProviderSlashCommandsForCwd, |
| 18 | resolveProviderSkillSourceKind, |
| 19 | } from "./providerSkills.ts"; |
| 20 | |
| 21 | const provider = { |
| 22 | instanceId: ProviderInstanceId.make("codex"), |
| 23 | driver: ProviderDriverKind.make("codex"), |
| 24 | enabled: true, |
| 25 | installed: true, |
| 26 | version: "1.0.0", |
| 27 | status: "ready", |
| 28 | auth: { status: "authenticated" }, |
| 29 | checkedAt: "2026-01-01T00:00:00.000Z", |
| 30 | models: [], |
| 31 | slashCommands: [{ name: "global" }], |
| 32 | skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }], |
| 33 | workspaceSnapshots: [ |
| 34 | { |
| 35 | cwd: "/workspace/project-a", |
| 36 | checkedAt: "2026-01-01T00:01:00.000Z", |
| 37 | slashCommands: [{ name: "project" }], |
| 38 | skills: [{ name: "project", path: "/workspace/project-a/SKILL.md", enabled: true }], |
| 39 | }, |
| 40 | ], |
| 41 | } satisfies ServerProvider; |
| 42 | |
| 43 | describe("formatProviderSkillDisplayName", () => { |
| 44 | it("prefers the provider display name", () => { |
| 45 | expect( |
| 46 | formatProviderSkillDisplayName({ |
| 47 | name: "review-follow-up", |
| 48 | displayName: "Review Follow-up", |
| 49 | }), |
| 50 | ).toBe("Review Follow-up"); |
| 51 | }); |
| 52 | |
| 53 | it("falls back to a title-cased skill name", () => { |
| 54 | expect( |
| 55 | formatProviderSkillDisplayName({ |
| 56 | name: "review-follow-up", |
| 57 | }), |
| 58 | ).toBe("Review Follow Up"); |
| 59 | }); |
| 60 | }); |
| 61 | |
| 62 | describe("dedupeProviderSkillsByName", () => { |
| 63 | it("keeps the first resolved skill and preserves unrelated skill order", () => { |
| 64 | const firstSkill = { |
| 65 | name: "branch-audit", |
| 66 | path: "/Users/matt/.codex/skills/branch-audit/SKILL.md", |
| 67 | enabled: true, |
| 68 | }; |
| 69 | const otherSkill = { |
| 70 | name: "browser", |
| 71 | path: "/Users/matt/.agents/skills/browser/SKILL.md", |
| 72 | enabled: true, |
| 73 | }; |
| 74 | const duplicateSkill = { |
| 75 | name: "Branch-Audit", |
| 76 | path: "/Users/matt/.agents/skills/branch-audit/SKILL.md", |
| 77 | enabled: true, |
| 78 | }; |
| 79 | |
| 80 | expect(dedupeProviderSkillsByName([firstSkill, otherSkill, duplicateSkill])).toEqual([ |
| 81 | firstSkill, |
| 82 | otherSkill, |
| 83 | ]); |
| 84 | }); |
| 85 | }); |
| 86 | |
| 87 | describe("getProviderSkillsForSlashMenu", () => { |
| 88 | it("keeps the skill alias when the provider also exposes it as a slash command", () => { |
| 89 | const askMatt = { |
| 90 | name: "ask-matt", |
| 91 | path: "/Users/matt/.agents/skills/ask-matt/SKILL.md", |
| 92 | enabled: true, |
| 93 | }; |
| 94 | expect(getProviderSkillsForSlashMenu([askMatt], true).map((skill) => skill.name)).toEqual([ |
| 95 | "ask-matt", |
| 96 | ]); |
| 97 | }); |
| 98 | |
| 99 | it("shows one row when enabled skills share a name", () => { |
| 100 | const skills = [ |
| 101 | { |
| 102 | name: "babysit-pr", |
| 103 | path: "/Users/matt/.codex/skills/babysit-pr/SKILL.md", |
| 104 | enabled: true, |
| 105 | }, |
| 106 | { |
| 107 | name: "browser", |
| 108 | path: "/Users/matt/.agents/skills/browser/SKILL.md", |
| 109 | enabled: true, |
| 110 | }, |
| 111 | { |
| 112 | name: "babysit-pr", |
| 113 | path: "/Users/matt/.agents/skills/babysit-pr/SKILL.md", |
| 114 | enabled: true, |
| 115 | }, |
| 116 | ]; |
| 117 | |
| 118 | expect(getProviderSkillsForSlashMenu(skills, true).map((skill) => skill.name)).toEqual([ |
| 119 | "babysit-pr", |
| 120 | "browser", |
| 121 | ]); |
| 122 | }); |
| 123 | |
| 124 | it("keeps an enabled skill when a disabled duplicate appears first", () => { |
| 125 | const enabledSkill = { |
| 126 | name: "babysit-pr", |
| 127 | path: "/Users/matt/.agents/skills/babysit-pr/SKILL.md", |
| 128 | enabled: true, |
| 129 | }; |
| 130 | const skills = [ |
| 131 | { |
| 132 | name: "babysit-pr", |
| 133 | path: "/Users/matt/.codex/skills/babysit-pr/SKILL.md", |
| 134 | enabled: false, |
| 135 | }, |
| 136 | enabledSkill, |
| 137 | ]; |
| 138 | |
| 139 | expect(getProviderSkillsForSlashMenu(skills, true)).toEqual([enabledSkill]); |
| 140 | }); |
| 141 | }); |
| 142 | |
| 143 | describe("getProviderSkillsForSlashMenu", () => { |
| 144 | it("drops a skill the provider reserves for the agent", () => { |
| 145 | const skills = [ |
| 146 | { |
| 147 | name: "legacy-system-context", |
| 148 | path: "/Users/matt/.claude/skills/legacy-system-context/SKILL.md", |
| 149 | enabled: true, |
| 150 | userInvocable: false, |
| 151 | }, |
| 152 | { |
| 153 | name: "deploy", |
| 154 | path: "/Users/matt/.claude/skills/deploy/SKILL.md", |
| 155 | enabled: true, |
| 156 | // Reserved for the user, not the agent: still a valid pick. |
| 157 | userInvocationOnly: true, |
| 158 | }, |
| 159 | ]; |
| 160 | |
| 161 | expect(getProviderSkillsForSlashMenu(skills, true).map((skill) => skill.name)).toEqual([ |
| 162 | "deploy", |
| 163 | ]); |
| 164 | }); |
| 165 | }); |
| 166 | |
| 167 | describe("getProviderSlashCommandsForSlashMenu", () => { |
| 168 | const commands = [ |
| 169 | { name: "ask-matt", description: "Ask which skill fits your situation." }, |
| 170 | { name: "compact", description: "Compact the conversation." }, |
| 171 | ]; |
| 172 | const skills = [ |
| 173 | { |
| 174 | name: "ask-matt", |
| 175 | path: "/Users/matt/.agents/skills/ask-matt/SKILL.md", |
| 176 | enabled: true, |
| 177 | }, |
| 178 | ]; |
| 179 | |
| 180 | it("lets the skill alias win when a provider command has the same name", () => { |
| 181 | expect( |
| 182 | getProviderSlashCommandsForSlashMenu(commands, skills).map((command) => command.name), |
| 183 | ).toEqual(["compact"]); |
| 184 | }); |
| 185 | |
| 186 | it("keeps the provider command when the matching skill alias is hidden", () => { |
| 187 | const visibleSkills = getProviderSkillsForSlashMenu(skills, false); |
| 188 | |
| 189 | expect( |
| 190 | getProviderSlashCommandsForSlashMenu(commands, visibleSkills).map((command) => command.name), |
| 191 | ).toEqual(["ask-matt", "compact"]); |
| 192 | }); |
| 193 | }); |
| 194 | |
| 195 | describe("resolveProviderSkillSourceKind", () => { |
| 196 | it("marks plugin-backed skills as app installs", () => { |
| 197 | expect( |
| 198 | resolveProviderSkillSourceKind({ |
| 199 | path: "/Users/julius/.codex/plugins/cache/openai-curated/github/skills/gh-fix-ci/SKILL.md", |
| 200 | scope: "user", |
| 201 | }), |
| 202 | ).toBe("app"); |
| 203 | }); |
| 204 | |
| 205 | it("maps standard scopes to source kinds", () => { |
| 206 | expect( |
| 207 | resolveProviderSkillSourceKind({ |
| 208 | path: "/workspace/.codex/skills/review-follow-up/SKILL.md", |
| 209 | scope: "repo", |
| 210 | }), |
| 211 | ).toBe("repo"); |
| 212 | expect( |
| 213 | resolveProviderSkillSourceKind({ |
| 214 | path: "/workspace/.codex/skills/review-follow-up/SKILL.md", |
| 215 | scope: "project", |
| 216 | }), |
| 217 | ).toBe("project"); |
| 218 | expect( |
| 219 | resolveProviderSkillSourceKind({ |
| 220 | path: "/Users/julius/.agents/skills/agent-browser/SKILL.md", |
| 221 | scope: "user", |
| 222 | }), |
| 223 | ).toBe("personal"); |
| 224 | expect( |
| 225 | resolveProviderSkillSourceKind({ |
| 226 | path: "/usr/local/share/codex/skills/imagegen/SKILL.md", |
| 227 | scope: "system", |
| 228 | }), |
| 229 | ).toBe("system"); |
| 230 | }); |
| 231 | |
| 232 | it("keeps unknown and missing scopes usable", () => { |
| 233 | expect( |
| 234 | resolveProviderSkillSourceKind({ |
| 235 | path: "/opt/skills/team-review/SKILL.md", |
| 236 | scope: "team_shared", |
| 237 | }), |
| 238 | ).toBe("other"); |
| 239 | expect( |
| 240 | resolveProviderSkillSourceKind({ |
| 241 | path: "/opt/skills/team-review/SKILL.md", |
| 242 | }), |
| 243 | ).toBe("other"); |
| 244 | }); |
| 245 | }); |
| 246 | |
| 247 | describe("workspace provider snapshots", () => { |
| 248 | it("uses the cwd snapshot after a provider session has populated it", () => { |
| 249 | expect(resolveProviderSkillsForCwd(provider, "/workspace/project-a")).toEqual([ |
| 250 | { name: "project", path: "/workspace/project-a/SKILL.md", enabled: true }, |
| 251 | ]); |
| 252 | expect(resolveProviderSlashCommandsForCwd(provider, "/workspace/project-a")).toEqual([ |
| 253 | { name: "project" }, |
| 254 | ]); |
| 255 | }); |
| 256 | |
| 257 | it("keeps the machine snapshot before this cwd has a provider snapshot", () => { |
| 258 | expect(resolveProviderSkillsForCwd(provider, "/workspace/project-b")).toEqual(provider.skills); |
| 259 | expect(resolveProviderSlashCommandsForCwd(provider, null)).toEqual(provider.slashCommands); |
| 260 | }); |
| 261 | |
| 262 | it("uses partial workspace skills and commands while keeping discovery retryable", () => { |
| 263 | const partial = { |
| 264 | ...provider, |
| 265 | workspaceSnapshots: provider.workspaceSnapshots.map((snapshot) => ({ |
| 266 | ...snapshot, |
| 267 | slashCommands: [{ name: "compact" }], |
| 268 | slashCommandsPending: true, |
| 269 | })), |
| 270 | } satisfies ServerProvider; |
| 271 | expect(resolveProviderSkillsForCwd(partial, "/workspace/project-a")).toEqual( |
| 272 | provider.workspaceSnapshots[0]?.skills, |
| 273 | ); |
| 274 | expect(resolveProviderSlashCommandsForCwd(partial, "/workspace/project-a")).toEqual([ |
| 275 | { name: "compact" }, |
| 276 | ]); |
| 277 | expect(hasCompleteProviderWorkspaceSnapshot(partial, "/workspace/project-a")).toBe(false); |
| 278 | expect(hasCompleteProviderWorkspaceSnapshot(provider, "/workspace/project-a")).toBe(true); |
| 279 | expect(hasCompleteProviderWorkspaceSnapshot(provider, "/workspace/project-b")).toBe(false); |
| 280 | expect(hasCompleteProviderWorkspaceSnapshot(undefined, "/workspace/project-a")).toBe(false); |
| 281 | expect(hasCompleteProviderWorkspaceSnapshot(provider, null)).toBe(false); |
| 282 | }); |
| 283 | |
| 284 | it("asks for a rescan once the workspace snapshot outlives its TTL", () => { |
| 285 | const scannedAt = Date.parse("2026-01-01T00:01:00.000Z"); |
| 286 | const cwd = "/workspace/project-a"; |
| 287 | expect(hasCurrentProviderWorkspaceSnapshot(provider, cwd, scannedAt)).toBe(true); |
| 288 | expect( |
| 289 | hasCurrentProviderWorkspaceSnapshot( |
| 290 | provider, |
| 291 | cwd, |
| 292 | scannedAt + PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS - 1, |
| 293 | ), |
| 294 | ).toBe(true); |
| 295 | expect( |
| 296 | hasCurrentProviderWorkspaceSnapshot( |
| 297 | provider, |
| 298 | cwd, |
| 299 | scannedAt + PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS, |
| 300 | ), |
| 301 | ).toBe(false); |
| 302 | expect(hasCurrentProviderWorkspaceSnapshot(provider, "/workspace/project-b", scannedAt)).toBe( |
| 303 | false, |
| 304 | ); |
| 305 | }); |
| 306 | }); |