feat: unify Docker entrypoint

This commit is contained in:
Pi Web Agent
2026-06-29 11:27:47 +00:00
parent 4a1136eac2
commit bd28c93cfe
35 changed files with 2316 additions and 118 deletions
+56
View File
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
import { DefaultPackageManager, getAgentDir, 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 { piWebDockerCommand } from "../docker/piWebDockerCommandPlan.js";
import { parsePiWebComponentStatus, parsePiWebRuntimeComponent } from "../shared/piWebStatusParsing.js";
import { SessionDaemonClient } from "../sessiond/sessionDaemonClient.js";
@@ -188,6 +189,8 @@ function parsePackageInfo(value: unknown, path: string): PackageInfo | undefined
}
async function detectPiWebInstallation(): Promise<PiWebInstallationInfo> {
const docker = detectDockerInstallation();
if (docker !== undefined) return docker;
const root = packageRootPath();
const realRoot = await realPathOrSelf(root);
const piPackage = await detectPiPackageInstallation(realRoot, root);
@@ -197,6 +200,46 @@ async function detectPiWebInstallation(): Promise<PiWebInstallationInfo> {
return { kind: "local", path: root };
}
function detectDockerInstallation(): PiWebInstallationInfo | undefined {
if (!isTruthyEnv("PI_WEB_DOCKER_RUNTIME")) return undefined;
const dockerMode = dockerModeFromEnv(process.env["PI_WEB_DOCKER_MODE"]) ?? inferredDockerModeFromRoots();
const path = dockerRootPathFromEnv(dockerMode);
return {
kind: "docker",
...(path === undefined ? {} : { path }),
...(dockerMode === undefined ? {} : { dockerMode }),
};
}
function dockerModeFromEnv(value: string | undefined): PiWebInstallationInfo["dockerMode"] | undefined {
return value === "runtime" || value === "dev" ? value : undefined;
}
function inferredDockerModeFromRoots(): PiWebInstallationInfo["dockerMode"] | undefined {
if (firstNonEmptyEnv("PI_WEB_DOCKER_DEV_REPO_ROOT") !== undefined) return "dev";
if (firstNonEmptyEnv("PI_WEB_DOCKER_INSTALL_DIR") !== undefined) return "runtime";
return undefined;
}
function dockerRootPathFromEnv(mode: PiWebInstallationInfo["dockerMode"] | undefined): string | undefined {
if (mode === "dev") return firstNonEmptyEnv("PI_WEB_DOCKER_DEV_REPO_ROOT", "PI_WEB_DOCKER_INSTALL_DIR");
if (mode === "runtime") return firstNonEmptyEnv("PI_WEB_DOCKER_INSTALL_DIR", "PI_WEB_DOCKER_DEV_REPO_ROOT");
return firstNonEmptyEnv("PI_WEB_DOCKER_INSTALL_DIR", "PI_WEB_DOCKER_DEV_REPO_ROOT");
}
function firstNonEmptyEnv(...keys: string[]): string | undefined {
for (const key of keys) {
const value = process.env[key];
if (value !== undefined && value !== "") return value;
}
return undefined;
}
function isTruthyEnv(key: string): boolean {
const value = process.env[key];
return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
}
async function detectPiPackageInstallation(realRoot: string, displayPath: string): Promise<PiWebInstallationInfo | undefined> {
try {
const agentDir = getAgentDir();
@@ -377,6 +420,8 @@ async function fetchLatestNpmVersion(currentVersion: string): Promise<string> {
async function commandsFor(components: PiWebStatusResponse["components"]): Promise<PiWebStatusResponse["commands"]> {
const installation = preferredInstallation(components);
if (installation?.kind === "docker") return dockerCommands(installation);
const [serviceCommands, cliCommands] = await Promise.all([
nativeServiceCommands(),
piWebCliCommands(installation),
@@ -399,10 +444,21 @@ async function commandsFor(components: PiWebStatusResponse["components"]): Promi
function preferredInstallation(components: PiWebStatusResponse["components"]): PiWebInstallationInfo | undefined {
const web = components.web.installation;
const sessiond = components.sessiond.installation;
if (web?.kind === "docker" || sessiond?.kind === "docker") return web?.kind === "docker" ? web : sessiond;
if (web?.kind === "local" || sessiond?.kind === "local") return web?.kind === "local" ? web : sessiond;
return web ?? sessiond;
}
function dockerCommands(installation: PiWebInstallationInfo): PiWebStatusResponse["commands"] {
return {
update: piWebDockerCommand(installation.dockerMode, "update"),
restart: piWebDockerCommand(installation.dockerMode, "restart"),
restartWeb: piWebDockerCommand(installation.dockerMode, "restart-web"),
restartSessiond: piWebDockerCommand(installation.dockerMode, "restart-sessiond"),
status: piWebDockerCommand(installation.dockerMode, "status"),
};
}
async function piWebCliCommands(installation: PiWebInstallationInfo | undefined): Promise<NativeServiceCommands> {
if (installation?.kind !== "npm-global" || !(await hasCommand("pi-web"))) return {};
return { restart: "pi-web restart", status: "pi-web status" };