feat: add Pi provider managed resources
This commit is contained in:
157
tests/unit/pi-resource-loader.test.ts
Normal file
157
tests/unit/pi-resource-loader.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import YAML from 'yaml';
|
||||
import type { PiProviderSelection } from '@electron/coding-runtime/pi/provider-config';
|
||||
import {
|
||||
buildPiManagedInputArgs,
|
||||
getPiManagedPaths,
|
||||
materializePiAgentResources,
|
||||
resolveBundledCodingSkillsDir,
|
||||
resolveExplicitCodingSkillPaths,
|
||||
} from '@electron/coding-runtime/pi/resource-loader';
|
||||
|
||||
const temporaryRoots: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
async function fixtureRoot(): Promise<{
|
||||
root: string;
|
||||
userDataDir: string;
|
||||
projectDir: string;
|
||||
skillsDir: string;
|
||||
}> {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-resources-'));
|
||||
temporaryRoots.push(root);
|
||||
const userDataDir = path.join(root, 'user-data');
|
||||
const projectDir = path.join(root, 'project');
|
||||
const skillsDir = path.join(root, 'bundled-skills');
|
||||
await Promise.all([
|
||||
mkdir(path.join(projectDir, '.pi', 'skills', 'untrusted-project-skill'), { recursive: true }),
|
||||
mkdir(path.join(projectDir, '.agents', 'skills', 'untrusted-agent-skill'), { recursive: true }),
|
||||
mkdir(path.join(skillsDir, 'grilling'), { recursive: true }),
|
||||
mkdir(path.join(skillsDir, 'agent-browser'), { recursive: true }),
|
||||
]);
|
||||
await Promise.all([
|
||||
writeFile(path.join(projectDir, '.pi', 'skills', 'untrusted-project-skill', 'SKILL.md'), 'untrusted', 'utf8'),
|
||||
writeFile(path.join(projectDir, '.agents', 'skills', 'untrusted-agent-skill', 'SKILL.md'), 'untrusted', 'utf8'),
|
||||
writeFile(path.join(skillsDir, 'grilling', 'SKILL.md'), '---\nname: grilling\n---\n', 'utf8'),
|
||||
writeFile(path.join(skillsDir, 'agent-browser', 'SKILL.md'), '---\nname: agent-browser\n---\n', 'utf8'),
|
||||
]);
|
||||
return { root, userDataDir, projectDir, skillsDir };
|
||||
}
|
||||
|
||||
describe('Pi managed resource loader', () => {
|
||||
it('materializes only managed prompt and explicitly selected bundled skills', async () => {
|
||||
const fixture = await fixtureRoot();
|
||||
const prompt = 'PRIVATE PARTNER PROMPT CONTENT';
|
||||
const resources = await materializePiAgentResources({
|
||||
userDataDir: fixture.userDataDir,
|
||||
projectId: 'project-1',
|
||||
agentId: 'agent-1',
|
||||
prompt,
|
||||
skillIds: ['grilling', 'grilling'],
|
||||
bundledSkillsDir: fixture.skillsDir,
|
||||
revision: { provider: 3, resources: 7 },
|
||||
});
|
||||
|
||||
expect(await readFile(resources.promptPath, 'utf8')).toBe(prompt);
|
||||
expect(resources.skillIds).toEqual(['grilling']);
|
||||
expect(resources.skillPaths).toEqual([path.join(fixture.skillsDir, 'grilling', 'SKILL.md')]);
|
||||
expect(JSON.stringify(resources.summary)).not.toContain(prompt);
|
||||
const manifest = JSON.parse(await readFile(resources.manifestPath, 'utf8')) as Record<string, unknown>;
|
||||
expect(manifest).toMatchObject({
|
||||
schemaVersion: 1,
|
||||
projectId: 'project-1',
|
||||
agentId: 'agent-1',
|
||||
promptFile: 'agent-1.md',
|
||||
skillIds: ['grilling'],
|
||||
revision: { provider: 3, resources: 7 },
|
||||
});
|
||||
expect(JSON.stringify(manifest)).not.toContain(prompt);
|
||||
await expect(readFile(path.join(fixture.userDataDir, '.pi', 'agents', 'agent-1.md'), 'utf8'))
|
||||
.rejects.toMatchObject({ code: 'ENOENT' });
|
||||
});
|
||||
|
||||
it('builds argv from managed paths without prompt content, credentials, or auto-discovery roots', async () => {
|
||||
const fixture = await fixtureRoot();
|
||||
const prompt = 'PROMPT-MUST-NOT-BE-IN-ARGV';
|
||||
const resources = await materializePiAgentResources({
|
||||
userDataDir: fixture.userDataDir,
|
||||
projectId: 'project-1',
|
||||
agentId: 'agent-1',
|
||||
prompt,
|
||||
skillIds: ['agent-browser'],
|
||||
bundledSkillsDir: fixture.skillsDir,
|
||||
revision: { provider: 1, resources: 1 },
|
||||
});
|
||||
const selection: PiProviderSelection = {
|
||||
accountId: 'private-account-id',
|
||||
runtimeProviderId: 'makelore-account-opaque',
|
||||
modelId: 'model-a',
|
||||
thinkingLevel: 'medium',
|
||||
input: ['text'],
|
||||
};
|
||||
const args = buildPiManagedInputArgs(selection, resources);
|
||||
const serialized = JSON.stringify(args);
|
||||
|
||||
expect(args).toEqual([
|
||||
'--provider', 'makelore-account-opaque',
|
||||
'--model', 'model-a',
|
||||
'--thinking', 'medium',
|
||||
'--system-prompt', resources.promptPath,
|
||||
'--skill', path.join(fixture.skillsDir, 'agent-browser', 'SKILL.md'),
|
||||
]);
|
||||
expect(serialized).not.toContain(prompt);
|
||||
expect(serialized).not.toContain('private-account-id');
|
||||
expect(serialized).not.toContain(path.join(fixture.projectDir, '.pi'));
|
||||
expect(serialized).not.toContain(path.join(fixture.projectDir, '.agents'));
|
||||
});
|
||||
|
||||
it('rejects unknown skills and unsafe managed path segments', async () => {
|
||||
const fixture = await fixtureRoot();
|
||||
await expect(resolveExplicitCodingSkillPaths(fixture.skillsDir, ['not-bundled']))
|
||||
.rejects.toThrow('Unknown bundled coding skill');
|
||||
await expect(materializePiAgentResources({
|
||||
userDataDir: fixture.userDataDir,
|
||||
projectId: '../outside',
|
||||
agentId: 'agent-1',
|
||||
prompt: '',
|
||||
skillIds: [],
|
||||
bundledSkillsDir: fixture.skillsDir,
|
||||
revision: { provider: 1, resources: 1 },
|
||||
})).rejects.toThrow('Project id');
|
||||
});
|
||||
|
||||
it('uses the same vendor-neutral resource source in development and packaged apps', () => {
|
||||
expect(resolveBundledCodingSkillsDir({
|
||||
isPackaged: false,
|
||||
resourcesPath: 'C:\\Program Files\\Makelore\\resources',
|
||||
appPath: 'D:\\source\\makelore',
|
||||
})).toBe(path.join('D:\\source\\makelore', 'resources', 'coding-skills'));
|
||||
expect(resolveBundledCodingSkillsDir({
|
||||
isPackaged: true,
|
||||
resourcesPath: 'C:\\Program Files\\Makelore\\resources',
|
||||
appPath: 'unused',
|
||||
})).toBe(path.join('C:\\Program Files\\Makelore\\resources', 'resources', 'coding-skills'));
|
||||
expect(getPiManagedPaths('D:\\user-data').rootDir)
|
||||
.toBe(path.join(path.resolve('D:\\user-data'), 'coding-runtime', 'pi'));
|
||||
});
|
||||
|
||||
it('packages coding skills through the vendor-neutral resources bundle only', async () => {
|
||||
const builder = YAML.parse(await readFile('electron-builder.yml', 'utf8')) as {
|
||||
extraResources: Array<{ from: string; to: string }>;
|
||||
};
|
||||
expect(builder.extraResources).toContainEqual(expect.objectContaining({
|
||||
from: 'resources/',
|
||||
to: 'resources/',
|
||||
}));
|
||||
expect(builder.extraResources).not.toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ from: '.opencode/skills/' }),
|
||||
expect.objectContaining({ to: 'course-skills/' }),
|
||||
]));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user