mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-25 17:01:15 +00:00
feat(linux): headless node device capabilities (camera, location, notifications) (#107193)
* feat(linux): add node device capabilities * fix(linux-node): actionable pending-approval error + node-host advertise integration test * fix(linux-node): map geoclue access-denied to LOCATION_DISABLED; floor camera maxWidth to avoid zero-height scale * fix(linux-node): clamp small camera maxWidth to 2 instead of default * docs(linux-node): clarify where-am-i -t is a process timeout, not update throttle * refactor(gateway): extract legacy-node filter + rejection hint to fit LOC ratchet; docs-map + deadcode baseline * fix(gateway): drop now-unused DEFAULT_DANGEROUS_NODE_COMMANDS import after hint extraction * test(node-host): drop imports orphaned by removed error-code test
This commit is contained in:
committed by
GitHub
parent
e8ad0466ff
commit
92cca9343e
115
src/node-host/linux-node-plugin.integration.test.ts
Normal file
115
src/node-host/linux-node-plugin.integration.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { reconcileNodePairingOnConnect } from "../gateway/node-connect-reconcile.js";
|
||||
import { resetPluginLoaderTestStateForTest } from "../plugins/loader.test-fixtures.js";
|
||||
import { testing as runtimeRegistryLoaderTesting } from "../plugins/runtime/runtime-registry-loader.js";
|
||||
import { listRegisteredNodeHostCapsAndCommands } from "./plugin-node-host.js";
|
||||
import { prepareNodeHostRuntime } from "./runtime.js";
|
||||
|
||||
const LINUX_NODE_COMMANDS = [
|
||||
"camera.clip",
|
||||
"camera.list",
|
||||
"camera.snap",
|
||||
"location.get",
|
||||
"system.notify",
|
||||
] as const;
|
||||
|
||||
function resetPluginState(): void {
|
||||
resetPluginLoaderTestStateForTest();
|
||||
runtimeRegistryLoaderTesting.resetPluginRegistryLoadedForTests();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllEnvs();
|
||||
resetPluginState();
|
||||
});
|
||||
|
||||
describe("linux-node node-host integration", () => {
|
||||
it("loads and advertises enabled commands through the node-host runtime", async () => {
|
||||
resetPluginState();
|
||||
const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
|
||||
if (!platformDescriptor) {
|
||||
throw new Error("process.platform descriptor unavailable");
|
||||
}
|
||||
Object.defineProperty(process, "platform", { ...platformDescriptor, value: "linux" });
|
||||
|
||||
const fakeBinDir = path.resolve(".artifacts", "linux-node-test-bin");
|
||||
const originalAccessSync = fs.accessSync.bind(fs);
|
||||
vi.spyOn(fs, "accessSync").mockImplementation((candidate, mode) => {
|
||||
if (path.dirname(path.resolve(String(candidate))) === fakeBinDir) {
|
||||
return;
|
||||
}
|
||||
return originalAccessSync(candidate, mode);
|
||||
});
|
||||
vi.stubEnv("PATH", `${fakeBinDir}${path.delimiter}${process.env.PATH ?? ""}`);
|
||||
vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", path.resolve("extensions"));
|
||||
vi.stubEnv("OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR", "1");
|
||||
vi.stubEnv("OPENCLAW_DISABLE_BUNDLED_PLUGINS", undefined);
|
||||
|
||||
const config: OpenClawConfig = {
|
||||
gateway: {
|
||||
nodes: {
|
||||
allowCommands: ["camera.snap", "camera.clip"],
|
||||
},
|
||||
},
|
||||
nodeHost: { skills: { enabled: false } },
|
||||
plugins: {
|
||||
allow: ["linux-node"],
|
||||
entries: {
|
||||
"linux-node": {
|
||||
enabled: true,
|
||||
config: {
|
||||
notify: { enabled: true },
|
||||
camera: { enabled: true },
|
||||
location: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const prepared = await prepareNodeHostRuntime({ config, env: process.env });
|
||||
const registered = listRegisteredNodeHostCapsAndCommands({ config, env: process.env });
|
||||
|
||||
expect(registered.commands).toEqual(LINUX_NODE_COMMANDS);
|
||||
expect(registered.caps).toEqual(["camera", "location"]);
|
||||
expect(prepared.manifest.commands).toEqual(expect.arrayContaining([...LINUX_NODE_COMMANDS]));
|
||||
|
||||
const requestPairing = vi.fn();
|
||||
const reconciliation = await reconcileNodePairingOnConnect({
|
||||
cfg: config,
|
||||
connectParams: {
|
||||
minProtocol: 1,
|
||||
maxProtocol: 1,
|
||||
client: {
|
||||
id: "node-host",
|
||||
version: "test",
|
||||
platform: "linux",
|
||||
deviceFamily: "Linux",
|
||||
mode: "node",
|
||||
},
|
||||
caps: registered.caps,
|
||||
commands: registered.commands,
|
||||
},
|
||||
pairedNode: {
|
||||
nodeId: "node-host",
|
||||
createdAtMs: 1,
|
||||
approvedAtMs: 1,
|
||||
caps: registered.caps,
|
||||
commands: registered.commands,
|
||||
},
|
||||
requestPairing,
|
||||
});
|
||||
|
||||
expect(reconciliation.declaredCommands).toEqual(LINUX_NODE_COMMANDS);
|
||||
expect(reconciliation.effectiveCommands).toEqual(LINUX_NODE_COMMANDS);
|
||||
expect(requestPairing).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", platformDescriptor);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user