Archived
feat: add OMP runtime support
This commit is contained in:
+38
-21
@@ -5,11 +5,12 @@ import { promisify } from "node:util";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join, relative, resolve, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { DefaultPackageManager, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
|
||||
import { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
||||
import type { PiWebCapability, PiWebComponentStatus, PiWebInstallationInfo, PiWebReleaseStatus, PiWebRuntimeComponent, PiWebRuntimeResponse, PiWebServiceComponent, PiWebStatusMessage, PiWebStatusResponse, PiWebVersionResponse } from "../shared/apiTypes.js";
|
||||
import { effectivePiWebCapabilities, WEB_RUNTIME_CAPABILITIES } from "../shared/capabilities.js";
|
||||
import { parsePiWebComponentStatus, parsePiWebRuntimeComponent } from "../shared/piWebStatusParsing.js";
|
||||
import { SessionDaemonClient } from "../sessiond/sessionDaemonClient.js";
|
||||
import { effectiveAgentConfig } from "../config.js";
|
||||
|
||||
const PI_WEB_PACKAGE_NAME = "@jmfederico/pi-web";
|
||||
const PI_WEB_NPM_SOURCE = `npm:${PI_WEB_PACKAGE_NAME}`;
|
||||
@@ -73,6 +74,22 @@ interface PiWebStatusDaemon {
|
||||
request(method: string, path: string, body?: unknown): Promise<{ statusCode: number; headers: Record<string, string>; body: string }>;
|
||||
}
|
||||
|
||||
interface PiWebStatusOptions {
|
||||
agentCommand?: string;
|
||||
agentDir?: string;
|
||||
hasCommand?: (command: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
function effectiveStatusAgentConfig(options: PiWebStatusOptions): { command: string; dir: string } {
|
||||
const agent = effectiveAgentConfig(process.env, {
|
||||
agent: {
|
||||
...(options.agentCommand === undefined ? {} : { command: options.agentCommand }),
|
||||
...(options.agentDir === undefined ? {} : { dir: options.agentDir }),
|
||||
},
|
||||
});
|
||||
return { command: agent.command, dir: agent.dir };
|
||||
}
|
||||
|
||||
let latestReleaseCache: { checkedAtMs: number; latestVersion?: string; error?: string } | undefined;
|
||||
|
||||
const runtimePackageInfo = readPackageInfoSync();
|
||||
@@ -98,10 +115,10 @@ export async function getPiWebRuntime(daemon: PiWebStatusDaemon = new SessionDae
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPiWebComponentStatus(component: PiWebServiceComponent): Promise<PiWebComponentStatus> {
|
||||
export async function getPiWebComponentStatus(component: PiWebServiceComponent, options: PiWebStatusOptions = {}): Promise<PiWebComponentStatus> {
|
||||
const [installed, installation] = await Promise.all([
|
||||
readInstalledPackageInfo(),
|
||||
detectPiWebInstallation(),
|
||||
detectPiWebInstallation(options.agentDir),
|
||||
]);
|
||||
const runtimeVersion = runtimePackageInfo?.version ?? DEFAULT_VERSION;
|
||||
const installedVersion = installed?.version;
|
||||
@@ -116,10 +133,10 @@ export async function getPiWebComponentStatus(component: PiWebServiceComponent):
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPiWebVersionStatus(daemon: PiWebStatusDaemon = new SessionDaemonClient()): Promise<PiWebVersionResponse> {
|
||||
export async function getPiWebVersionStatus(daemon: PiWebStatusDaemon = new SessionDaemonClient(), options: PiWebStatusOptions = {}): Promise<PiWebVersionResponse> {
|
||||
const [web, sessiond] = await Promise.all([
|
||||
getPiWebComponentStatus("web"),
|
||||
getSessiondComponentStatus(daemon),
|
||||
getPiWebComponentStatus("web", options),
|
||||
getSessiondComponentStatus(daemon, options),
|
||||
]);
|
||||
return {
|
||||
packageName: PI_WEB_PACKAGE_NAME,
|
||||
@@ -128,12 +145,13 @@ export async function getPiWebVersionStatus(daemon: PiWebStatusDaemon = new Sess
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPiWebStatus(daemon: PiWebStatusDaemon = new SessionDaemonClient()): Promise<PiWebStatusResponse> {
|
||||
const versionStatus = await getPiWebVersionStatus(daemon);
|
||||
export async function getPiWebStatus(daemon: PiWebStatusDaemon = new SessionDaemonClient(), options: PiWebStatusOptions = {}): Promise<PiWebStatusResponse> {
|
||||
const agent = effectiveStatusAgentConfig(options);
|
||||
const versionStatus = await getPiWebVersionStatus(daemon, { ...options, agentDir: agent.dir });
|
||||
const { web, sessiond } = versionStatus.components;
|
||||
const release = await getLatestReleaseStatus(web.installedVersion ?? web.runtimeVersion ?? DEFAULT_VERSION);
|
||||
const components = { web, sessiond };
|
||||
const commands = await commandsFor(components);
|
||||
const commands = await commandsFor(components, { agentCommand: agent.command, hasCommand: options.hasCommand ?? hasCommand });
|
||||
const messages = buildMessages(components, release, commands);
|
||||
return {
|
||||
...versionStatus,
|
||||
@@ -187,19 +205,18 @@ function parsePackageInfo(value: unknown, path: string): PackageInfo | undefined
|
||||
return { name, version, path };
|
||||
}
|
||||
|
||||
async function detectPiWebInstallation(): Promise<PiWebInstallationInfo> {
|
||||
async function detectPiWebInstallation(agentDir = effectiveAgentConfig().dir): Promise<PiWebInstallationInfo> {
|
||||
const root = packageRootPath();
|
||||
const realRoot = await realPathOrSelf(root);
|
||||
const piPackage = await detectPiPackageInstallation(realRoot, root);
|
||||
const piPackage = await detectPiPackageInstallation(realRoot, root, agentDir);
|
||||
if (piPackage !== undefined) return piPackage;
|
||||
const npmGlobal = await detectNpmGlobalInstallation(realRoot, root);
|
||||
if (npmGlobal !== undefined) return npmGlobal;
|
||||
return { kind: "local", path: root };
|
||||
}
|
||||
|
||||
async function detectPiPackageInstallation(realRoot: string, displayPath: string): Promise<PiWebInstallationInfo | undefined> {
|
||||
async function detectPiPackageInstallation(realRoot: string, displayPath: string, agentDir: string): Promise<PiWebInstallationInfo | undefined> {
|
||||
try {
|
||||
const agentDir = getAgentDir();
|
||||
const packageManager = new DefaultPackageManager({
|
||||
cwd: process.cwd(),
|
||||
agentDir,
|
||||
@@ -267,7 +284,7 @@ async function getSessiondRuntimeComponent(daemon: PiWebStatusDaemon): Promise<P
|
||||
}
|
||||
}
|
||||
|
||||
async function getSessiondComponentStatus(daemon: PiWebStatusDaemon): Promise<PiWebComponentStatus> {
|
||||
async function getSessiondComponentStatus(daemon: PiWebStatusDaemon, options: PiWebStatusOptions = {}): Promise<PiWebComponentStatus> {
|
||||
try {
|
||||
const upstream = await daemon.request("GET", "/runtime");
|
||||
if (upstream.statusCode < 200 || upstream.statusCode >= 300) {
|
||||
@@ -278,7 +295,7 @@ async function getSessiondComponentStatus(daemon: PiWebStatusDaemon): Promise<Pi
|
||||
if (legacyVersion !== undefined) return legacyVersion;
|
||||
const runtime = parsePiWebRuntimeComponent(parsed);
|
||||
if (runtime?.available !== true) return await legacySessiondComponentStatus(daemon) ?? unavailableSessiond(runtime?.error ?? "runtime response did not include valid runtime information");
|
||||
const status = await getPiWebComponentStatus("sessiond");
|
||||
const status = await getPiWebComponentStatus("sessiond", options);
|
||||
return { ...status, ...(runtime.runtimeVersion === undefined ? {} : { runtimeVersion: runtime.runtimeVersion }), available: true };
|
||||
} catch (error) {
|
||||
return unavailableSessiond(error instanceof Error ? error.message : String(error));
|
||||
@@ -375,7 +392,7 @@ async function fetchLatestNpmVersion(currentVersion: string): Promise<string> {
|
||||
return version;
|
||||
}
|
||||
|
||||
async function commandsFor(components: PiWebStatusResponse["components"]): Promise<PiWebStatusResponse["commands"]> {
|
||||
async function commandsFor(components: PiWebStatusResponse["components"], options: { agentCommand: string; hasCommand: (command: string) => Promise<boolean> }): Promise<PiWebStatusResponse["commands"]> {
|
||||
const installation = preferredInstallation(components);
|
||||
const [serviceCommands, cliCommands] = await Promise.all([
|
||||
nativeServiceCommands(),
|
||||
@@ -385,7 +402,7 @@ async function commandsFor(components: PiWebStatusResponse["components"]): Promi
|
||||
const restartWeb = serviceCommands.restartWeb ?? cliCommands.restart;
|
||||
const restartSessiond = serviceCommands.restartSessiond ?? cliCommands.restart;
|
||||
const status = serviceCommands.status ?? cliCommands.status;
|
||||
const update = await updateCommandFor(installation, restart);
|
||||
const update = await updateCommandFor(installation, restart, options);
|
||||
|
||||
return {
|
||||
...(update === undefined ? {} : { update }),
|
||||
@@ -413,11 +430,11 @@ function restartCommandFor(installation: PiWebInstallationInfo | undefined, serv
|
||||
return cliCommands.restart ?? serviceCommands.restart;
|
||||
}
|
||||
|
||||
async function updateCommandFor(installation: PiWebInstallationInfo | undefined, restartCommand: string | undefined): Promise<string | undefined> {
|
||||
export async function updateCommandFor(installation: PiWebInstallationInfo | undefined, restartCommand: string | undefined, options: { agentCommand: string; hasCommand: (command: string) => Promise<boolean> }): Promise<string | undefined> {
|
||||
if (restartCommand === undefined) return undefined;
|
||||
if (installation?.kind === "pi-package") {
|
||||
if (!(await hasCommand("pi"))) return undefined;
|
||||
return `pi update ${installation.source ?? PI_WEB_NPM_SOURCE} && ${restartCommand}`;
|
||||
if (!(await options.hasCommand(options.agentCommand))) return undefined;
|
||||
return `${shellQuote(options.agentCommand)} update ${shellQuote(installation.source ?? PI_WEB_NPM_SOURCE)} && ${restartCommand}`;
|
||||
}
|
||||
if (installation?.kind === "local" && installation.path !== undefined) {
|
||||
if (!(await hasCommand("npm")) || !(await isGitCheckoutWithUpstream(installation.path))) return undefined;
|
||||
@@ -497,7 +514,7 @@ async function isGitCheckoutWithUpstream(path: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
function hasCommand(command: string): Promise<boolean> {
|
||||
return commandSucceeds("/usr/bin/env", ["sh", "-c", `command -v ${command}`]);
|
||||
return commandSucceeds("/usr/bin/env", ["sh", "-c", `command -v ${shellQuote(command)}`]);
|
||||
}
|
||||
|
||||
async function commandSucceeds(command: string, args: string[]): Promise<boolean> {
|
||||
|
||||
Reference in New Issue
Block a user