























@@ -9,6 +9,10 @@ const mocks = vi.hoisted(() => ({
99disposeBundleRuntime: vi.fn(),
1010loadModelCatalog: vi.fn(async (): Promise<Array<Record<string, unknown>>> => []),
1111normalizeProviderToolSchemasWithPlugin: vi.fn(),
12+buildGatewayProbeConnectionDetails: vi.fn(),
13+probeGatewayStatus: vi.fn(),
14+readGatewayServiceState: vi.fn(),
15+resolveGatewayService: vi.fn(() => ({ label: "openclaw-gateway" })),
1216resolvePluginProviders: vi.fn((): Array<Record<string, unknown>> => []),
1317resolveDefaultModelForAgent: vi.fn(() => ({ provider: "openai", model: "gpt-5.5" })),
1418}));
@@ -35,6 +39,19 @@ vi.mock("../agents/agent-tools.js", () => ({
3539createOpenClawCodingTools: mocks.createOpenClawCodingTools,
3640}));
374142+vi.mock("../gateway/call.js", () => ({
43+buildGatewayProbeConnectionDetails: mocks.buildGatewayProbeConnectionDetails,
44+}));
45+46+vi.mock("../cli/daemon-cli/probe.js", () => ({
47+probeGatewayStatus: mocks.probeGatewayStatus,
48+}));
49+50+vi.mock("../daemon/service.js", () => ({
51+readGatewayServiceState: mocks.readGatewayServiceState,
52+resolveGatewayService: mocks.resolveGatewayService,
53+}));
54+3855vi.mock("../plugins/provider-runtime.js", () => ({
3956inspectProviderToolSchemasWithPlugin: () => [],
4057normalizeProviderToolSchemasWithPlugin: mocks.normalizeProviderToolSchemasWithPlugin,
@@ -48,8 +65,12 @@ vi.mock("../plugins/providers.runtime.js", () => ({
4865resolvePluginProviders: mocks.resolvePluginProviders,
4966}));
506751-const { collectProviderCatalogProjectionFindings, collectRuntimeToolSchemaFindings } =
52-await import("./doctor-core-checks.runtime.js");
68+const {
69+ collectGatewayDaemonFindings,
70+ collectGatewayHealthFindings,
71+ collectProviderCatalogProjectionFindings,
72+ collectRuntimeToolSchemaFindings,
73+} = await import("./doctor-core-checks.runtime.js");
53745475function tool(name: string, parameters: unknown): AnyAgentTool {
5576return {
@@ -79,6 +100,22 @@ describe("doctor runtime tool schema checks", () => {
79100mocks.normalizeProviderToolSchemasWithPlugin
80101.mockReset()
81102.mockImplementation(({ context }) => context.tools);
103+mocks.buildGatewayProbeConnectionDetails.mockReset().mockResolvedValue({
104+url: "http://127.0.0.1:5829",
105+});
106+mocks.probeGatewayStatus.mockReset().mockResolvedValue({
107+ok: true,
108+server: { version: "2026.6.26" },
109+});
110+mocks.readGatewayServiceState.mockReset().mockResolvedValue({
111+installed: true,
112+loaded: true,
113+running: true,
114+env: {},
115+command: { programArguments: ["openclaw", "gateway"], sourcePath: "/tmp/gateway.service" },
116+runtime: { status: "running" },
117+});
118+mocks.resolveGatewayService.mockClear();
82119mocks.resolvePluginProviders.mockReset().mockReturnValue([]);
83120mocks.resolveDefaultModelForAgent.mockClear();
84121});
@@ -503,6 +540,100 @@ describe("doctor runtime tool schema checks", () => {
503540});
504541});
505542543+describe("doctor gateway runtime checks", () => {
544+beforeEach(() => {
545+mocks.buildGatewayProbeConnectionDetails.mockReset().mockResolvedValue({
546+url: "http://127.0.0.1:5829",
547+});
548+mocks.probeGatewayStatus.mockReset().mockResolvedValue({
549+ok: true,
550+server: { version: "2026.6.26" },
551+});
552+mocks.readGatewayServiceState.mockReset().mockResolvedValue({
553+installed: true,
554+loaded: true,
555+running: true,
556+env: {},
557+command: { programArguments: ["openclaw", "gateway"], sourcePath: "/tmp/gateway.service" },
558+runtime: { status: "running" },
559+});
560+mocks.resolveGatewayService.mockReset().mockReturnValue({ label: "openclaw-gateway" });
561+});
562+563+it("reports unreachable gateway health probes", async () => {
564+mocks.probeGatewayStatus.mockResolvedValueOnce({
565+ok: false,
566+error: "connect ECONNREFUSED 127.0.0.1:5829",
567+});
568+569+await expect(
570+collectGatewayHealthFindings({ cfg: { gateway: { mode: "local" } } }),
571+).resolves.toContainEqual({
572+checkId: "core/doctor/gateway-health",
573+severity: "warning",
574+message: "Gateway is not reachable: connect ECONNREFUSED 127.0.0.1:5829",
575+path: "gateway.mode",
576+target: "http://127.0.0.1:5829",
577+fixHint:
578+"Start the Gateway service or run `openclaw doctor --fix` for service repair prompts.",
579+});
580+});
581+582+it("redacts sensitive remote gateway URLs from health finding targets", async () => {
583+mocks.buildGatewayProbeConnectionDetails.mockResolvedValueOnce({
584+url: "wss://user:pass@gateway.example.test/rpc?token=secret&safe=value",
585+});
586+mocks.probeGatewayStatus.mockResolvedValueOnce({
587+ok: false,
588+error: "remote gateway did not answer",
589+});
590+591+const findings = await collectGatewayHealthFindings({
592+cfg: { gateway: { mode: "remote", remote: { url: "wss://gateway.example.test/rpc" } } },
593+});
594+595+expect(findings).toContainEqual({
596+checkId: "core/doctor/gateway-health",
597+severity: "warning",
598+message: "Gateway is not reachable: remote gateway did not answer",
599+path: "gateway.remote.url",
600+target: "wss://***:***@gateway.example.test/rpc?token=***&safe=value",
601+fixHint: "Verify the remote Gateway URL, network path, TLS settings, and credentials.",
602+});
603+expect(JSON.stringify(findings)).not.toContain("user:pass");
604+expect(JSON.stringify(findings)).not.toContain("token=secret");
605+});
606+607+it("reports missing local gateway daemon service", async () => {
608+mocks.readGatewayServiceState.mockResolvedValueOnce({
609+installed: false,
610+loaded: false,
611+running: false,
612+env: {},
613+command: null,
614+});
615+616+await expect(
617+collectGatewayDaemonFindings({ cfg: { gateway: { mode: "local" } } }),
618+).resolves.toContainEqual({
619+checkId: "core/doctor/gateway-daemon",
620+severity: "warning",
621+message: "Gateway service is not installed.",
622+path: "gateway.mode",
623+target: "openclaw-gateway",
624+fixHint: "Run `openclaw doctor --fix` or `openclaw gateway install` to install it.",
625+});
626+});
627+628+it("skips daemon findings for remote gateway mode", async () => {
629+await expect(
630+collectGatewayDaemonFindings({ cfg: { gateway: { mode: "remote" } } }),
631+).resolves.toEqual([]);
632+633+expect(mocks.readGatewayServiceState).not.toHaveBeenCalled();
634+});
635+});
636+506637describe("doctor provider catalog projection checks", () => {
507638beforeEach(() => {
508639mocks.resolvePluginProviders.mockReset().mockReturnValue([]);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。