fix: honor gateway command env in status reads

This commit is contained in:
Peter Steinberger 2026-03-13 20:50:32 +00:00
parent 377b42c92b
commit 9747da8682
No known key found for this signature in database
5 changed files with 91 additions and 11 deletions

View File

@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai
- Agents/tool warnings: distinguish gated core tools like `apply_patch` from plugin-only unknown entries in `tools.profile` warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin. - Agents/tool warnings: distinguish gated core tools like `apply_patch` from plugin-only unknown entries in `tools.profile` warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin.
- Slack/probe: keep `auth.test()` bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss. - Slack/probe: keep `auth.test()` bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss.
- Dashboard/chat UI: restore the `chat-new-messages` class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han. - Dashboard/chat UI: restore the `chat-new-messages` class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han.
- Windows/gateway status: reuse the installed service command environment when reading runtime status, so startup-fallback gateways keep reporting the configured port and running state in `gateway status --json` instead of falling back to `gateway port unknown`.
## 2026.3.12 ## 2026.3.12

View File

@ -190,6 +190,37 @@ describe("gatherDaemonStatus", () => {
expect(status.rpc?.url).toBe("wss://override.example:18790"); expect(status.rpc?.url).toBe("wss://override.example:18790");
}); });
it("reuses command environment when reading runtime status", async () => {
serviceReadCommand.mockResolvedValueOnce({
programArguments: ["/bin/node", "cli", "gateway", "--port", "19001"],
environment: {
OPENCLAW_GATEWAY_PORT: "19001",
OPENCLAW_CONFIG_PATH: "/tmp/openclaw-daemon/openclaw.json",
OPENCLAW_STATE_DIR: "/tmp/openclaw-daemon",
} as Record<string, string>,
});
serviceReadRuntime.mockImplementationOnce(async (env?: NodeJS.ProcessEnv) => ({
status: env?.OPENCLAW_GATEWAY_PORT === "19001" ? "running" : "unknown",
detail: env?.OPENCLAW_GATEWAY_PORT ?? "missing-port",
}));
const status = await gatherDaemonStatus({
rpc: {},
probe: false,
deep: false,
});
expect(serviceReadRuntime).toHaveBeenCalledWith(
expect.objectContaining({
OPENCLAW_GATEWAY_PORT: "19001",
}),
);
expect(status.service.runtime).toMatchObject({
status: "running",
detail: "19001",
});
});
it("resolves daemon gateway auth password SecretRef values before probing", async () => { it("resolves daemon gateway auth password SecretRef values before probing", async () => {
daemonLoadedConfig = { daemonLoadedConfig = {
gateway: { gateway: {

View File

@ -258,17 +258,21 @@ export async function gatherDaemonStatus(
} & FindExtraGatewayServicesOptions, } & FindExtraGatewayServicesOptions,
): Promise<DaemonStatus> { ): Promise<DaemonStatus> {
const service = resolveGatewayService(); const service = resolveGatewayService();
const [loaded, command, runtime] = await Promise.all([ const command = await service.readCommand(process.env).catch(() => null);
service.isLoaded({ env: process.env }).catch(() => false), const serviceEnv = command?.environment
service.readCommand(process.env).catch(() => null), ? ({
service.readRuntime(process.env).catch((err) => ({ status: "unknown", detail: String(err) })), ...process.env,
...command.environment,
} satisfies NodeJS.ProcessEnv)
: process.env;
const [loaded, runtime] = await Promise.all([
service.isLoaded({ env: serviceEnv }).catch(() => false),
service.readRuntime(serviceEnv).catch((err) => ({ status: "unknown", detail: String(err) })),
]); ]);
const configAudit = await auditGatewayServiceConfig({ const configAudit = await auditGatewayServiceConfig({
env: process.env, env: process.env,
command, command,
}); });
const serviceEnv = command?.environment ?? undefined;
const { const {
mergedDaemonEnv, mergedDaemonEnv,
cliCfg, cliCfg,
@ -276,7 +280,7 @@ export async function gatherDaemonStatus(
cliConfigSummary, cliConfigSummary,
daemonConfigSummary, daemonConfigSummary,
configMismatch, configMismatch,
} = await loadDaemonConfigContext(serviceEnv); } = await loadDaemonConfigContext(command?.environment);
const { gateway, daemonPort, cliPort, probeUrlOverride } = await resolveGatewayStatusSummary({ const { gateway, daemonPort, cliPort, probeUrlOverride } = await resolveGatewayStatusSummary({
cliCfg, cliCfg,
daemonCfg, daemonCfg,

View File

@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import type { GatewayService } from "../daemon/service.js"; import type { GatewayService } from "../daemon/service.js";
import type { GatewayServiceEnvArgs } from "../daemon/service.js";
import { readServiceStatusSummary } from "./status.service-summary.js"; import { readServiceStatusSummary } from "./status.service-summary.js";
function createService(overrides: Partial<GatewayService>): GatewayService { function createService(overrides: Partial<GatewayService>): GatewayService {
@ -57,4 +58,41 @@ describe("readServiceStatusSummary", () => {
expect(summary.externallyManaged).toBe(false); expect(summary.externallyManaged).toBe(false);
expect(summary.loadedText).toBe("disabled"); expect(summary.loadedText).toBe("disabled");
}); });
it("passes command environment to runtime and loaded checks", async () => {
const isLoaded = vi.fn(async ({ env }: GatewayServiceEnvArgs) => {
return env?.OPENCLAW_GATEWAY_PORT === "18789";
});
const readRuntime = vi.fn(async (env?: NodeJS.ProcessEnv) => ({
status: env?.OPENCLAW_GATEWAY_PORT === "18789" ? ("running" as const) : ("unknown" as const),
}));
const summary = await readServiceStatusSummary(
createService({
isLoaded,
readCommand: vi.fn(async () => ({
programArguments: ["openclaw", "gateway", "run", "--port", "18789"],
environment: { OPENCLAW_GATEWAY_PORT: "18789" },
})),
readRuntime,
}),
"Daemon",
);
expect(isLoaded).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
OPENCLAW_GATEWAY_PORT: "18789",
}),
}),
);
expect(readRuntime).toHaveBeenCalledWith(
expect.objectContaining({
OPENCLAW_GATEWAY_PORT: "18789",
}),
);
expect(summary.installed).toBe(true);
expect(summary.loaded).toBe(true);
expect(summary.runtime).toMatchObject({ status: "running" });
});
}); });

View File

@ -16,10 +16,16 @@ export async function readServiceStatusSummary(
fallbackLabel: string, fallbackLabel: string,
): Promise<ServiceStatusSummary> { ): Promise<ServiceStatusSummary> {
try { try {
const [loaded, runtime, command] = await Promise.all([ const command = await service.readCommand(process.env).catch(() => null);
service.isLoaded({ env: process.env }).catch(() => false), const serviceEnv = command?.environment
service.readRuntime(process.env).catch(() => undefined), ? ({
service.readCommand(process.env).catch(() => null), ...process.env,
...command.environment,
} satisfies NodeJS.ProcessEnv)
: process.env;
const [loaded, runtime] = await Promise.all([
service.isLoaded({ env: serviceEnv }).catch(() => false),
service.readRuntime(serviceEnv).catch(() => undefined),
]); ]);
const managedByOpenClaw = command != null; const managedByOpenClaw = command != null;
const externallyManaged = !managedByOpenClaw && runtime?.status === "running"; const externallyManaged = !managedByOpenClaw && runtime?.status === "running";