fix(packages): load bundled Pi runtime for local installs
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { cp, mkdir, readFile, rename, rm, stat } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { atomicWriteJson } from '../coding-projects/atomic-json';
|
||||
import {
|
||||
@@ -53,6 +54,17 @@ export interface DevicePackageInstallInput {
|
||||
|
||||
export type DevicePackageInstallRunner = (input: DevicePackageInstallInput) => Promise<void>;
|
||||
|
||||
interface PiPackageManagerModule {
|
||||
DefaultPackageManager: new (input: {
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
settingsManager: unknown;
|
||||
}) => { getInstalledPath(source: string, scope: 'user'): string | undefined };
|
||||
SettingsManager: {
|
||||
inMemory(user: Record<string, unknown>, project: { projectTrusted: boolean }): unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DevicePackageManagerOptions {
|
||||
rootDir: string;
|
||||
executablePath?: string;
|
||||
@@ -208,6 +220,23 @@ async function gitHead(directory: string): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadBundledPiPackageManager(cliPath: string): Promise<PiPackageManagerModule> {
|
||||
const entryPath = path.join(path.dirname(path.resolve(cliPath)), 'index.js');
|
||||
try {
|
||||
const loaded = createRequire(entryPath)(entryPath) as Partial<PiPackageManagerModule>;
|
||||
if (typeof loaded.DefaultPackageManager !== 'function'
|
||||
|| typeof loaded.SettingsManager?.inMemory !== 'function') {
|
||||
throw new Error('Pi package manager exports are unavailable');
|
||||
}
|
||||
return loaded as PiPackageManagerModule;
|
||||
} catch {
|
||||
throw new DevicePackageError(
|
||||
'local_package_dependency_failed',
|
||||
'Bundled Pi package manager is unavailable',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class DevicePackageManager {
|
||||
private readonly rootDir: string;
|
||||
private readonly packagesDir: string;
|
||||
@@ -270,18 +299,23 @@ export class DevicePackageManager {
|
||||
}
|
||||
const agentDir = path.join(planRoot, 'pi-agent');
|
||||
const cwd = path.join(planRoot, 'project');
|
||||
await this.runInstall({
|
||||
source: source.spec,
|
||||
executablePath,
|
||||
cliPath,
|
||||
...(this.options.npmCliPath ? { npmCliPath: this.options.npmCliPath } : {}),
|
||||
agentDir,
|
||||
cwd,
|
||||
env: installEnvironment(agentDir),
|
||||
});
|
||||
const { DefaultPackageManager, SettingsManager } = await import(
|
||||
'@earendil-works/pi-coding-agent'
|
||||
);
|
||||
try {
|
||||
await this.runInstall({
|
||||
source: source.spec,
|
||||
executablePath,
|
||||
cliPath,
|
||||
...(this.options.npmCliPath ? { npmCliPath: this.options.npmCliPath } : {}),
|
||||
agentDir,
|
||||
cwd,
|
||||
env: installEnvironment(agentDir),
|
||||
});
|
||||
} catch {
|
||||
throw new DevicePackageError(
|
||||
'local_package_dependency_failed',
|
||||
'Pi could not install this package source',
|
||||
);
|
||||
}
|
||||
const { DefaultPackageManager, SettingsManager } = await loadBundledPiPackageManager(cliPath);
|
||||
const manager = new DefaultPackageManager({
|
||||
cwd,
|
||||
agentDir,
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { PiProductTools } from './product-tools';
|
||||
import type { CodingPluginToolDefinition } from '../../../shared/coding-plugins';
|
||||
import type { PiSkillEntry } from './resource-loader';
|
||||
import type { EffectivePluginSnapshot } from '../../coding-plugins/effective-resolver';
|
||||
import { DevicePackageError } from '../../coding-packages/device-package-manager';
|
||||
|
||||
const MAX_REQUEST_BYTES = 64 * 1024;
|
||||
const PRODUCT_TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,63}$/u;
|
||||
@@ -483,7 +484,14 @@ export class PiManagedExtensionHost {
|
||||
response.removeListener('close', cancel);
|
||||
record.waiters.delete(value.resourceId);
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
if (!response.writableEnded && error instanceof DevicePackageError) {
|
||||
this.respond(response, 400, {
|
||||
code: error.code,
|
||||
error: error.message.slice(0, 512),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!response.writableEnded) this.respond(response, 400, { error: 'Bridge request failed' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'node:path';
|
||||
import { atomicWriteText } from '../../../coding-projects/atomic-json';
|
||||
|
||||
export const MAKELORE_PI_EXTENSION_VERSION = 4;
|
||||
export const MAKELORE_PI_EXTENSION_VERSION = 5;
|
||||
export const MAKELORE_PI_EXTENSION_FILENAME = `makelore-runtime-v${MAKELORE_PI_EXTENSION_VERSION}.mjs`;
|
||||
|
||||
const BUNDLE_SOURCE = String.raw`
|
||||
@@ -93,7 +93,15 @@ export function createMakeloreRuntime(runtimeDefaults = {}) {
|
||||
signal,
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(result.error || 'Makelore runtime bridge rejected the request');
|
||||
if (!response.ok) {
|
||||
const message = typeof result.error === 'string' && result.error
|
||||
? result.error
|
||||
: 'Makelore runtime bridge rejected the request';
|
||||
const code = typeof result.code === 'string' && /^local_package_[a-z_]+$/.test(result.code)
|
||||
? result.code
|
||||
: '';
|
||||
throw new Error(code ? code + ': ' + message : message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user