feat: add OMP runtime support

This commit is contained in:
Jeff Scott Ward
2026-06-26 12:49:58 -04:00
parent 4605a4f1d8
commit 84a485d62e
26 changed files with 639 additions and 83 deletions
+38 -21
View File
@@ -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> {