feat(cli): support targeting running containerized openclaw instances (#52651)

Signed-off-by: sallyom <somalley@redhat.com>
This commit is contained in:
Sally O'Malley
2026-03-24 10:17:17 -04:00
committed by GitHub
parent dd11bdd003
commit 91adc5e718
24 changed files with 1484 additions and 39 deletions

View File

@@ -67,6 +67,26 @@ describe("runServiceRestart token drift", () => {
stubEmptyGatewayEnv();
});
it("prints the container restart hint when restart is requested for a not-loaded service", async () => {
service.isLoaded.mockResolvedValue(false);
vi.stubEnv("OPENCLAW_CONTAINER_HINT", "openclaw-demo-container");
await runServiceRestart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [
"Restart the container or the service that manages it for openclaw-demo-container.",
"openclaw gateway install",
],
opts: { json: false },
});
expect(runtimeLogs).toContain("Gateway service not loaded.");
expect(runtimeLogs).toContain(
"Start with: Restart the container or the service that manages it for openclaw-demo-container.",
);
});
it("emits drift warning when enabled", async () => {
await runServiceRestart(createServiceRunArgs(true));

View File

@@ -19,6 +19,7 @@ import {
type DaemonActionResponse,
emitDaemonActionJson,
} from "./response.js";
import { filterContainerGenericHints } from "./shared.js";
type DaemonLifecycleOptions = {
json?: boolean;
@@ -81,7 +82,9 @@ async function handleServiceNotLoaded(params: {
json: boolean;
emit: ReturnType<typeof createActionIO>["emit"];
}) {
const hints = await maybeAugmentSystemdHints(params.renderStartHints());
const hints = filterContainerGenericHints(
await maybeAugmentSystemdHints(params.renderStartHints()),
);
params.emit({
ok: true,
result: "not-loaded",

View File

@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import { theme } from "../../terminal/theme.js";
import { resolveRuntimeStatusColor } from "./shared.js";
import {
filterContainerGenericHints,
renderGatewayServiceStartHints,
resolveRuntimeStatusColor,
} from "./shared.js";
describe("resolveRuntimeStatusColor", () => {
it("maps known runtime states to expected theme colors", () => {
@@ -14,3 +18,55 @@ describe("resolveRuntimeStatusColor", () => {
expect(resolveRuntimeStatusColor(undefined)).toBe(theme.muted);
});
});
describe("renderGatewayServiceStartHints", () => {
it("prepends a single container restart hint when OPENCLAW_CONTAINER is set", () => {
expect(
renderGatewayServiceStartHints({
OPENCLAW_CONTAINER: "openclaw-demo-container",
} as NodeJS.ProcessEnv),
).toEqual(
expect.arrayContaining([
"Restart the container or the service that manages it for openclaw-demo-container.",
]),
);
});
it("prepends a single container restart hint when OPENCLAW_CONTAINER_HINT is set", () => {
expect(
renderGatewayServiceStartHints({
OPENCLAW_CONTAINER_HINT: "openclaw-demo-container",
} as NodeJS.ProcessEnv),
).toEqual(
expect.arrayContaining([
"Restart the container or the service that manages it for openclaw-demo-container.",
]),
);
});
});
describe("filterContainerGenericHints", () => {
it("drops the generic container foreground hint when OPENCLAW_CONTAINER is set", () => {
expect(
filterContainerGenericHints(
[
"systemd user services are unavailable; install/enable systemd or run the gateway under your supervisor.",
"If you're in a container, run the gateway in the foreground instead of `openclaw gateway`.",
],
{ OPENCLAW_CONTAINER: "openclaw-demo-container" } as NodeJS.ProcessEnv,
),
).toEqual([]);
});
it("drops the generic container foreground hint when OPENCLAW_CONTAINER_HINT is set", () => {
expect(
filterContainerGenericHints(
[
"systemd user services are unavailable; install/enable systemd or run the gateway under your supervisor.",
"If you're in a container, run the gateway in the foreground instead of `openclaw gateway`.",
],
{ OPENCLAW_CONTAINER_HINT: "openclaw-demo-container" } as NodeJS.ProcessEnv,
),
).toEqual([]);
});
});

View File

@@ -181,11 +181,30 @@ export function renderRuntimeHints(
export function renderGatewayServiceStartHints(env: NodeJS.ProcessEnv = process.env): string[] {
const profile = env.OPENCLAW_PROFILE;
return buildPlatformServiceStartHints({
const container = env.OPENCLAW_CONTAINER_HINT?.trim() || env.OPENCLAW_CONTAINER?.trim();
const hints = buildPlatformServiceStartHints({
installCommand: formatCliCommand("openclaw gateway install", env),
startCommand: formatCliCommand("openclaw gateway", env),
launchAgentPlistPath: `~/Library/LaunchAgents/${resolveGatewayLaunchAgentLabel(profile)}.plist`,
systemdServiceName: resolveGatewaySystemdServiceName(profile),
windowsTaskName: resolveGatewayWindowsTaskName(profile),
});
if (!container) {
return hints;
}
return [`Restart the container or the service that manages it for ${container}.`];
}
export function filterContainerGenericHints(
hints: string[],
env: NodeJS.ProcessEnv = process.env,
): string[] {
if (!(env.OPENCLAW_CONTAINER_HINT?.trim() || env.OPENCLAW_CONTAINER?.trim())) {
return hints;
}
return hints.filter(
(hint) =>
!hint.includes("If you're in a container, run the gateway in the foreground instead of") &&
!hint.includes("systemd user services are unavailable; install/enable systemd"),
);
}