fix(packages): load bundled Pi runtime for local installs
This commit is contained in:
@@ -118,12 +118,24 @@ describe('DevicePackageManager', () => {
|
||||
expect(preview.warnings.join(' ')).toContain('网络');
|
||||
});
|
||||
|
||||
it('runs remote Pi installation with lifecycle scripts disabled and reports declared scripts', async () => {
|
||||
it('uses the bundled Pi runtime for remote installation when the app Pi graph is unavailable', async () => {
|
||||
const root = await temporaryRoot('remote');
|
||||
const runtimeRoot = path.join(root, 'pi-runtime');
|
||||
const cliPath = path.join(runtimeRoot, 'dist', 'cli.js');
|
||||
await writeJson(path.join(runtimeRoot, 'package.json'), { type: 'module' });
|
||||
await mkdir(path.dirname(cliPath), { recursive: true });
|
||||
await writeFile(path.join(path.dirname(cliPath), 'index.js'), [
|
||||
"import path from 'node:path';",
|
||||
'export class SettingsManager { static inMemory() { return {}; } }',
|
||||
'export class DefaultPackageManager {',
|
||||
' constructor(options) { this.agentDir = options.agentDir; }',
|
||||
" getInstalledPath() { return path.join(this.agentDir, 'npm', 'node_modules', 'fixture-installed'); }",
|
||||
'}',
|
||||
].join('\n'), 'utf8');
|
||||
const calls: DevicePackageInstallInput[] = [];
|
||||
const runInstall = vi.fn(async (input: DevicePackageInstallInput) => {
|
||||
calls.push(input);
|
||||
const packageRoot = path.join(input.agentDir, 'npm', 'node_modules', 'pi-web-search');
|
||||
const packageRoot = path.join(input.agentDir, 'npm', 'node_modules', 'fixture-installed');
|
||||
await writeJson(path.join(packageRoot, 'package.json'), {
|
||||
name: 'pi-web-search',
|
||||
version: '2.4.0',
|
||||
@@ -135,7 +147,7 @@ describe('DevicePackageManager', () => {
|
||||
const manager = new DevicePackageManager({
|
||||
rootDir: path.join(root, 'store'),
|
||||
executablePath: 'Makelore.exe',
|
||||
cliPath: 'pi-cli.js',
|
||||
cliPath,
|
||||
npmCliPath: 'npm-cli.js',
|
||||
runInstall,
|
||||
now: () => NOW,
|
||||
|
||||
@@ -11,6 +11,14 @@ import { PiProcessBudget } from '../../electron/coding-runtime/pi/worker-pool';
|
||||
import type { AgentBrowserModule } from '../../electron/agent-browser';
|
||||
import { CodingAttachmentStore } from '../../electron/coding-projects/attachment-store';
|
||||
import { PiProductTools } from '../../electron/coding-runtime/pi/product-tools';
|
||||
import {
|
||||
DevicePackageError,
|
||||
type DevicePackageManager,
|
||||
} from '../../electron/coding-packages/device-package-manager';
|
||||
import {
|
||||
DEVICE_PACKAGE_TOOL_DEFINITIONS,
|
||||
DevicePackageTools,
|
||||
} from '../../electron/coding-packages/device-package-tools';
|
||||
import { CodingCapabilityRegistryImpl } from '../../electron/coding-plugins/registry';
|
||||
import type { PluginPolicyClientState } from '../../electron/services/plugin-policy-client';
|
||||
import {
|
||||
@@ -48,6 +56,79 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('Makelore Pi extension bundle', () => {
|
||||
it('preserves a closed Device Package failure through the real worker bridge', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-device-package-error-'));
|
||||
roots.push(root);
|
||||
const manager = {
|
||||
async prepare() {
|
||||
throw new DevicePackageError(
|
||||
'local_package_dependency_failed',
|
||||
'Bundled Pi package manager is unavailable',
|
||||
);
|
||||
},
|
||||
} as unknown as DevicePackageManager;
|
||||
const host = new PiManagedExtensionHost();
|
||||
host.configureProductTools(new PiProductTools({
|
||||
browser: {} as AgentBrowserModule,
|
||||
attachments: new CodingAttachmentStore(path.join(root, 'attachments')),
|
||||
bundledSkillsDir: path.resolve('resources/coding-skills'),
|
||||
devicePackageTools: new DevicePackageTools(manager),
|
||||
}));
|
||||
hosts.push(host);
|
||||
const prepareTool = DEVICE_PACKAGE_TOOL_DEFINITIONS.find(
|
||||
({ name }) => name === 'local_package_prepare',
|
||||
);
|
||||
if (!prepareTool) throw new Error('Device Package prepare definition missing');
|
||||
const worker = await host.registerWorker({
|
||||
conversationId: 'device-package-conversation',
|
||||
generation: 1,
|
||||
projectId: 'project-a',
|
||||
projectPath: root,
|
||||
extensionsDir: root,
|
||||
tools: [prepareTool],
|
||||
});
|
||||
await host.bindRun('device-package-conversation', 1, 'device-package-run');
|
||||
const previous = {
|
||||
bridge: process.env.MAKELORE_PI_BRIDGE_URL,
|
||||
token: process.env.MAKELORE_PI_WORKER_TOKEN,
|
||||
context: process.env.MAKELORE_PI_CONTEXT_FILE,
|
||||
role: process.env.MAKELORE_PI_WORKER_ROLE,
|
||||
};
|
||||
Object.assign(process.env, worker.env);
|
||||
try {
|
||||
const module = await import(
|
||||
/* @vite-ignore */ `${pathToFileURL(worker.extensionPath).href}?device-package=${Date.now()}`
|
||||
) as {
|
||||
default(factory: {
|
||||
registerTool(tool: ExtensionTool): void;
|
||||
on(event: string, handler: ExtensionHandler): void;
|
||||
}): void | Promise<void>;
|
||||
};
|
||||
const tools = new Map<string, ExtensionTool>();
|
||||
await module.default({
|
||||
registerTool: (tool) => tools.set(tool.name, tool),
|
||||
on: () => undefined,
|
||||
});
|
||||
|
||||
await expect(tools.get('local_package_prepare')?.execute?.(
|
||||
'prepare-a',
|
||||
{ source: 'npm:pi-web-search' },
|
||||
new AbortController().signal,
|
||||
)).rejects.toThrow(
|
||||
'local_package_dependency_failed: Bundled Pi package manager is unavailable',
|
||||
);
|
||||
} finally {
|
||||
for (const [key, value] of Object.entries(previous)) {
|
||||
const environmentKey = key === 'bridge' ? 'MAKELORE_PI_BRIDGE_URL'
|
||||
: key === 'token' ? 'MAKELORE_PI_WORKER_TOKEN'
|
||||
: key === 'context' ? 'MAKELORE_PI_CONTEXT_FILE'
|
||||
: 'MAKELORE_PI_WORKER_ROLE';
|
||||
if (value === undefined) delete process.env[environmentKey];
|
||||
else process.env[environmentKey] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('hydrates persisted Pi identity through reconnect and event replay into the capability registry', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-persisted-identity-'));
|
||||
roots.push(root);
|
||||
|
||||
@@ -289,7 +289,7 @@ describe('managed Pi worker opener', () => {
|
||||
expect(argv).toContain('grilling');
|
||||
expect(argv).toContain('--session-id');
|
||||
expect(argv).toContain('--extension');
|
||||
expect(argv).toContain('makelore-runtime-v4.mjs');
|
||||
expect(argv).toContain('makelore-runtime-v5.mjs');
|
||||
expect(options.additionalArgs?.filter((argument) => argument === '--extension')).toHaveLength(2);
|
||||
expect(options.additionalArgs).toEqual(expect.arrayContaining([
|
||||
'--skill', deviceSkillPath, '--extension', deviceExtensionPath,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
verifyBundledCodingPluginResources,
|
||||
defaultProductExecutable,
|
||||
readPackagedMarketplaceTrustSource,
|
||||
verifyPackagedDevicePackageRuntime,
|
||||
verifyPackagedMarketplaceClientArtifact,
|
||||
validatePiArtifactMetadata,
|
||||
verifyMarketplaceClientArtifact,
|
||||
@@ -326,6 +327,41 @@ describe('final Pi product artifact verification', () => {
|
||||
await expect(readPackagedMarketplaceTrustSource(appAsar)).rejects.toThrow('trust source');
|
||||
});
|
||||
|
||||
it('rejects the incomplete app Pi graph and requires the bundled runtime package manager', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-device-package-runtime-asar-'));
|
||||
roots.push(root);
|
||||
const source = path.join(root, 'source');
|
||||
await mkdir(path.join(source, 'dist-electron'), { recursive: true });
|
||||
await writeFile(path.join(source, 'package.json'), JSON.stringify({ main: 'dist-electron/main.js' }));
|
||||
await writeFile(
|
||||
path.join(source, 'dist-electron', 'main.js'),
|
||||
'export async function prepare() { return await import("@earendil-works/pi-coding-agent"); }',
|
||||
);
|
||||
const appAsar = path.join(root, 'app.asar');
|
||||
await createAsarFixture(source, appAsar);
|
||||
const runtimePlatform = {
|
||||
devicePackageManager: { defaultPackageManager: 'function', settingsManager: 'function' },
|
||||
};
|
||||
|
||||
await expect(verifyPackagedDevicePackageRuntime(appAsar, runtimePlatform))
|
||||
.rejects.toThrow('incomplete app graph');
|
||||
|
||||
await writeFile(
|
||||
path.join(source, 'dist-electron', 'main.js'),
|
||||
'export const devicePackageManagerAuthority = "bundled-pi-runtime";',
|
||||
);
|
||||
const cleanAsar = path.join(root, 'clean.asar');
|
||||
await createAsarFixture(source, cleanAsar);
|
||||
await expect(verifyPackagedDevicePackageRuntime(cleanAsar, runtimePlatform)).resolves.toEqual({
|
||||
authority: 'bundled-pi-runtime',
|
||||
appAsarRootImport: false,
|
||||
exports: runtimePlatform.devicePackageManager,
|
||||
result: 'pass',
|
||||
});
|
||||
await expect(verifyPackagedDevicePackageRuntime(cleanAsar, { devicePackageManager: null }))
|
||||
.rejects.toThrow('cannot load the Device Package manager');
|
||||
});
|
||||
|
||||
it('binds Marketplace route, Renderer, and effective markers to the package main graph', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-marketplace-contract-asar-'));
|
||||
roots.push(root);
|
||||
|
||||
Reference in New Issue
Block a user