diff --git a/extensions/diagnostics-otel/src/service.test.ts b/extensions/diagnostics-otel/src/service.test.ts index 299ba5a47c..f4e08a4cda 100644 --- a/extensions/diagnostics-otel/src/service.test.ts +++ b/extensions/diagnostics-otel/src/service.test.ts @@ -113,6 +113,35 @@ import type { OpenClawPluginServiceContext } from "openclaw/plugin-sdk"; import { emitDiagnosticEvent } from "openclaw/plugin-sdk"; import { createDiagnosticsOtelService } from "./service.js"; +function createLogger() { + return { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }; +} + +function createTraceOnlyContext(endpoint: string): OpenClawPluginServiceContext { + return { + config: { + diagnostics: { + enabled: true, + otel: { + enabled: true, + endpoint, + protocol: "http/protobuf", + traces: true, + metrics: false, + logs: false, + }, + }, + }, + logger: createLogger(), + stateDir: "/tmp/openclaw-diagnostics-otel-test", + }; +} + describe("diagnostics-otel service", () => { beforeEach(() => { telemetryState.counters.clear(); @@ -151,12 +180,7 @@ describe("diagnostics-otel service", () => { }, }, }, - logger: { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }, + logger: createLogger(), stateDir: "/tmp/openclaw-diagnostics-otel-test", }; await service.start(ctx); @@ -236,97 +260,41 @@ describe("diagnostics-otel service", () => { test("appends signal path when endpoint contains non-signal /v1 segment", async () => { const service = createDiagnosticsOtelService(); - await service.start({ - config: { - diagnostics: { - enabled: true, - otel: { - enabled: true, - endpoint: "https://www.comet.com/opik/api/v1/private/otel", - protocol: "http/protobuf", - traces: true, - metrics: false, - logs: false, - }, - }, - }, - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, - }); + const ctx = createTraceOnlyContext("https://www.comet.com/opik/api/v1/private/otel"); + await service.start(ctx); const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined; expect(options?.url).toBe("https://www.comet.com/opik/api/v1/private/otel/v1/traces"); - await service.stop?.(); + await service.stop?.(ctx); }); test("keeps already signal-qualified endpoint unchanged", async () => { const service = createDiagnosticsOtelService(); - await service.start({ - config: { - diagnostics: { - enabled: true, - otel: { - enabled: true, - endpoint: "https://collector.example.com/v1/traces", - protocol: "http/protobuf", - traces: true, - metrics: false, - logs: false, - }, - }, - }, - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, - }); + const ctx = createTraceOnlyContext("https://collector.example.com/v1/traces"); + await service.start(ctx); const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined; expect(options?.url).toBe("https://collector.example.com/v1/traces"); - await service.stop?.(); + await service.stop?.(ctx); }); test("keeps signal-qualified endpoint unchanged when it has query params", async () => { const service = createDiagnosticsOtelService(); - await service.start({ - config: { - diagnostics: { - enabled: true, - otel: { - enabled: true, - endpoint: "https://collector.example.com/v1/traces?timeout=30s", - protocol: "http/protobuf", - traces: true, - metrics: false, - logs: false, - }, - }, - }, - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, - }); + const ctx = createTraceOnlyContext("https://collector.example.com/v1/traces?timeout=30s"); + await service.start(ctx); const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined; expect(options?.url).toBe("https://collector.example.com/v1/traces?timeout=30s"); - await service.stop?.(); + await service.stop?.(ctx); }); test("keeps signal-qualified endpoint unchanged when signal path casing differs", async () => { const service = createDiagnosticsOtelService(); - await service.start({ - config: { - diagnostics: { - enabled: true, - otel: { - enabled: true, - endpoint: "https://collector.example.com/v1/Traces", - protocol: "http/protobuf", - traces: true, - metrics: false, - logs: false, - }, - }, - }, - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, - }); + const ctx = createTraceOnlyContext("https://collector.example.com/v1/Traces"); + await service.start(ctx); const options = traceExporterCtor.mock.calls[0]?.[0] as { url?: string } | undefined; expect(options?.url).toBe("https://collector.example.com/v1/Traces"); - await service.stop?.(); + await service.stop?.(ctx); }); }); diff --git a/src/auto-reply/reply/stage-sandbox-media.ts b/src/auto-reply/reply/stage-sandbox-media.ts index 3147dbe8d4..73a2f69e94 100644 --- a/src/auto-reply/reply/stage-sandbox-media.ts +++ b/src/auto-reply/reply/stage-sandbox-media.ts @@ -2,14 +2,14 @@ import { spawn } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import type { OpenClawConfig } from "../../config/config.js"; -import type { MsgContext, TemplateContext } from "../templating.js"; import { assertSandboxPath } from "../../agents/sandbox-paths.js"; import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js"; +import type { OpenClawConfig } from "../../config/config.js"; import { logVerbose } from "../../globals.js"; import { normalizeScpRemoteHost } from "../../infra/scp-host.js"; import { getMediaDir } from "../../media/store.js"; import { CONFIG_DIR } from "../../utils.js"; +import type { MsgContext, TemplateContext } from "../templating.js"; export async function stageSandboxMedia(params: { ctx: MsgContext; diff --git a/src/daemon/launchd.test.ts b/src/daemon/launchd.test.ts index 279c91d925..ff824f4121 100644 --- a/src/daemon/launchd.test.ts +++ b/src/daemon/launchd.test.ts @@ -11,6 +11,7 @@ import { const state = vi.hoisted(() => ({ launchctlCalls: [] as string[][], listOutput: "", + bootstrapError: "", dirs: new Set(), files: new Map(), })); @@ -33,6 +34,9 @@ vi.mock("./exec-file.js", () => ({ if (call[0] === "list") { return { stdout: state.listOutput, stderr: "", code: 0 }; } + if (call[0] === "bootstrap" && state.bootstrapError) { + return { stdout: "", stderr: state.bootstrapError, code: 1 }; + } return { stdout: "", stderr: "", code: 0 }; }), })); @@ -66,6 +70,7 @@ vi.mock("node:fs/promises", async (importOriginal) => { beforeEach(() => { state.launchctlCalls.length = 0; state.listOutput = ""; + state.bootstrapError = ""; state.dirs.clear(); state.files.clear(); vi.clearAllMocks(); @@ -173,64 +178,24 @@ describe("launchd install", () => { }); it("shows actionable guidance when launchctl gui domain does not support bootstrap", async () => { - const originalPath = process.env.PATH; - const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-launchctl-test-")); + state.bootstrapError = "Bootstrap failed: 125: Domain does not support specified action"; + const env: Record = { + HOME: "/Users/test", + OPENCLAW_PROFILE: "default", + }; + let message = ""; try { - const binDir = path.join(tmpDir, "bin"); - const homeDir = path.join(tmpDir, "home"); - await fs.mkdir(binDir, { recursive: true }); - await fs.mkdir(homeDir, { recursive: true }); - - const stubJsPath = path.join(binDir, "launchctl.js"); - await fs.writeFile( - stubJsPath, - [ - "const args = process.argv.slice(2);", - 'if (args[0] === "bootstrap") {', - ' process.stderr.write("Bootstrap failed: 125: Domain does not support specified action\\n");', - " process.exit(1);", - "}", - "process.exit(0);", - "", - ].join("\n"), - "utf8", - ); - - if (process.platform === "win32") { - await fs.writeFile( - path.join(binDir, "launchctl.cmd"), - `@echo off\r\nnode "%~dp0\\launchctl.js" %*\r\n`, - "utf8", - ); - } else { - const shPath = path.join(binDir, "launchctl"); - await fs.writeFile(shPath, `#!/bin/sh\nnode "$(dirname "$0")/launchctl.js" "$@"\n`, "utf8"); - await fs.chmod(shPath, 0o755); - } - - process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`; - - const env: Record = { - HOME: homeDir, - OPENCLAW_PROFILE: "default", - }; - let message = ""; - try { - await installLaunchAgent({ - env, - stdout: new PassThrough(), - programArguments: ["node", "-e", "process.exit(0)"], - }); - } catch (error) { - message = String(error); - } - expect(message).toContain("logged-in macOS GUI session"); - expect(message).toContain("wrong user (including sudo)"); - expect(message).toContain("https://docs.openclaw.ai/gateway"); - } finally { - process.env.PATH = originalPath; - await fs.rm(tmpDir, { recursive: true, force: true }); + await installLaunchAgent({ + env, + stdout: new PassThrough(), + programArguments: ["node", "-e", "process.exit(0)"], + }); + } catch (error) { + message = String(error); } + expect(message).toContain("logged-in macOS GUI session"); + expect(message).toContain("wrong user (including sudo)"); + expect(message).toContain("https://docs.openclaw.ai/gateway"); }); }); diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 8a8abce155..a21da04a20 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -1,5 +1,4 @@ import fs from "node:fs/promises"; -import type { IMessagePayload, MonitorIMessageOpts } from "./types.js"; import { resolveHumanDelayConfig } from "../../agents/identity.js"; import { resolveTextChunkLimit } from "../../auto-reply/chunk.js"; import { hasControlCommand } from "../../auto-reply/command-detection.js"; @@ -41,6 +40,7 @@ import { } from "./inbound-processing.js"; import { parseIMessageNotification } from "./parse-notification.js"; import { normalizeAllowList, resolveRuntime } from "./runtime.js"; +import type { IMessagePayload, MonitorIMessageOpts } from "./types.js"; /** * Try to detect remote host from an SSH wrapper script like: