diff --git a/CHANGELOG.md b/CHANGELOG.md index ba6b5285814..0fb4c9c93cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Models/custom provider headers: propagate `models.providers..headers` across inline, fallback, and registry-found model resolution so header-authenticated proxies consistently receive configured request headers. (#27490) thanks @Sid-Qin. +- Daemon/systemd install robustness: treat `systemctl --user is-enabled` exit-code-4 `not-found` responses as not-enabled by combining stderr/stdout detail parsing, so Ubuntu fresh installs no longer fail with `systemctl is-enabled unavailable`. (#33634) Thanks @Yuandiaodiaodiao. - Slack/system-event session routing: resolve reaction/member/pin/interaction system-event session keys through channel/account bindings (with sender-aware DM routing) so inbound Slack events target the correct agent session in multi-account setups instead of defaulting to `agent:main`. (#34045) Thanks @paulomcg, @daht-mad and @vincentkoc. - Gateway/HTTP tools invoke media compatibility: preserve raw media payload access for direct `/tools/invoke` clients by allowing media `nodes` invoke commands only in HTTP tool context, while keeping agent-context media invoke blocking to prevent base64 prompt bloat. (#34365) Thanks @obviyus. - Agents/Nodes media outputs: add dedicated `photos_latest` action handling, block media-returning `nodes invoke` commands, keep metadata-only `camera.list` invoke allowed, and normalize empty `photos_latest` results to a consistent response shape to prevent base64 context bloat. (#34332) Thanks @obviyus. diff --git a/src/daemon/systemd.test.ts b/src/daemon/systemd.test.ts index cfaf223c91d..e5cf1603674 100644 --- a/src/daemon/systemd.test.ts +++ b/src/daemon/systemd.test.ts @@ -90,6 +90,21 @@ describe("isSystemdServiceEnabled", () => { "systemctl is-enabled unavailable: Failed to connect to bus", ); }); + + it("returns false when systemctl is-enabled exits with code 4 (not-found)", async () => { + const { isSystemdServiceEnabled } = await import("./systemd.js"); + execFileMock.mockImplementationOnce((_cmd, _args, _opts, cb) => { + // On Ubuntu 24.04, `systemctl --user is-enabled ` exits with + // code 4 and prints "not-found" to stdout when the unit doesn't exist. + const err = new Error( + "Command failed: systemctl --user is-enabled openclaw-gateway.service", + ) as Error & { code?: number }; + err.code = 4; + cb(err, "not-found\n", ""); + }); + const result = await isSystemdServiceEnabled({ env: {} }); + expect(result).toBe(false); + }); }); describe("systemd runtime parsing", () => { diff --git a/src/daemon/systemd.ts b/src/daemon/systemd.ts index 9f073d382e6..ec80ea1bc7e 100644 --- a/src/daemon/systemd.ts +++ b/src/daemon/systemd.ts @@ -143,7 +143,10 @@ async function execSystemctl( } function readSystemctlDetail(result: { stdout: string; stderr: string }): string { - return (result.stderr || result.stdout || "").trim(); + // Concatenate both streams so pattern matchers (isSystemdUnitNotEnabled, + // isSystemctlMissing) can see the unit status from stdout even when + // execFileUtf8 populates stderr with the Node error message fallback. + return `${result.stderr} ${result.stdout}`.trim(); } function isSystemctlMissing(detail: string): boolean { diff --git a/src/plugin-sdk/root-alias.test.ts b/src/plugin-sdk/root-alias.test.ts index dd2cc10b1bb..6cffdd3c959 100644 --- a/src/plugin-sdk/root-alias.test.ts +++ b/src/plugin-sdk/root-alias.test.ts @@ -27,14 +27,14 @@ describe("plugin-sdk root alias", () => { expect(parsed.success).toBe(false); }); - it("loads legacy root exports lazily through the proxy", () => { + it("loads legacy root exports lazily through the proxy", { timeout: 240_000 }, () => { expect(typeof rootSdk.resolveControlCommandGate).toBe("function"); expect(typeof rootSdk.default).toBe("object"); expect(rootSdk.default).toBe(rootSdk); expect(rootSdk.__esModule).toBe(true); }); - it("preserves reflection semantics for lazily resolved exports", () => { + it("preserves reflection semantics for lazily resolved exports", { timeout: 240_000 }, () => { expect("resolveControlCommandGate" in rootSdk).toBe(true); const keys = Object.keys(rootSdk); expect(keys).toContain("resolveControlCommandGate");