mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix: send owner-qualified install telemetry (#102377)
This commit is contained in:
@@ -108,20 +108,23 @@ describe("openclaw skills install ClawHub GitHub-backed E2E", () => {
|
||||
const registry = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
||||
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-clawhub-cli-e2e-"));
|
||||
try {
|
||||
const result = await spawnOpenClaw(["skills", "install", "aiq-deploy", "--global"], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"),
|
||||
OPENCLAW_CLAWHUB_URL: registry,
|
||||
OPENCLAW_CLAWHUB_TOKEN: "test-token",
|
||||
OPENCLAW_CLAWHUB_GITHUB_CODELOAD_BASE_URL: registry,
|
||||
CLAWHUB_DISABLE_TELEMETRY: "",
|
||||
CLAWDHUB_DISABLE_TELEMETRY: "",
|
||||
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
||||
const result = await spawnOpenClaw(
|
||||
["skills", "install", "@demo-owner/aiq-deploy", "--global"],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"),
|
||||
OPENCLAW_CLAWHUB_URL: registry,
|
||||
OPENCLAW_CLAWHUB_TOKEN: "test-token",
|
||||
OPENCLAW_CLAWHUB_GITHUB_CODELOAD_BASE_URL: registry,
|
||||
CLAWHUB_DISABLE_TELEMETRY: "",
|
||||
CLAWDHUB_DISABLE_TELEMETRY: "",
|
||||
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
expect(result.status, result.stderr || result.stdout).toBe(0);
|
||||
await expect(
|
||||
@@ -137,11 +140,10 @@ describe("openclaw skills install ClawHub GitHub-backed E2E", () => {
|
||||
throw new Error(`Expected one install telemetry request, saw: ${requestLog.join(", ")}`);
|
||||
}
|
||||
expect(telemetryBodies[0]).toMatchObject({
|
||||
roots: [
|
||||
{
|
||||
skills: [{ slug: "aiq-deploy", version: commit }],
|
||||
},
|
||||
],
|
||||
event: "install",
|
||||
slug: "aiq-deploy",
|
||||
ownerHandle: "demo-owner",
|
||||
version: commit,
|
||||
});
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => {
|
||||
|
||||
+11
-33
@@ -404,10 +404,6 @@ export type ClawHubDownloadResult = {
|
||||
cleanup: () => Promise<void>;
|
||||
};
|
||||
|
||||
export type ClawHubInstallTelemetrySkill = {
|
||||
version?: string | null;
|
||||
};
|
||||
|
||||
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
||||
|
||||
type ClawHubRequestParams = {
|
||||
@@ -1619,8 +1615,9 @@ export async function downloadClawHubGitHubSkillArchive(params: {
|
||||
export async function reportClawHubSkillInstallTelemetry(params: {
|
||||
baseUrl?: string;
|
||||
token?: string;
|
||||
root: string;
|
||||
skills: Record<string, ClawHubInstallTelemetrySkill>;
|
||||
slug: string;
|
||||
ownerHandle?: string;
|
||||
version?: string | null;
|
||||
timeoutMs?: number;
|
||||
fetchImpl?: FetchLike;
|
||||
}): Promise<void> {
|
||||
@@ -1628,12 +1625,10 @@ export async function reportClawHubSkillInstallTelemetry(params: {
|
||||
if (!token || isClawHubTelemetryDisabled()) {
|
||||
return;
|
||||
}
|
||||
const skills = Object.entries(params.skills)
|
||||
.map(([slug, entry]) => ({
|
||||
slug,
|
||||
version: entry.version ?? null,
|
||||
}))
|
||||
.filter((entry) => entry.slug.length > 0);
|
||||
const slug = params.slug.trim();
|
||||
if (!slug) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { response, url, hasToken } = await clawhubRequest({
|
||||
baseUrl: params.baseUrl,
|
||||
@@ -1643,13 +1638,10 @@ export async function reportClawHubSkillInstallTelemetry(params: {
|
||||
timeoutMs: params.timeoutMs,
|
||||
fetchImpl: params.fetchImpl,
|
||||
json: {
|
||||
roots: [
|
||||
{
|
||||
rootId: digestSha256Hex(path.resolve(params.root)),
|
||||
label: formatTelemetryRootLabel(params.root),
|
||||
skills,
|
||||
},
|
||||
],
|
||||
event: "install",
|
||||
slug,
|
||||
...(params.ownerHandle ? { ownerHandle: params.ownerHandle } : {}),
|
||||
version: params.version ?? undefined,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -1665,20 +1657,6 @@ function isClawHubTelemetryDisabled(): boolean {
|
||||
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
|
||||
}
|
||||
|
||||
function formatTelemetryRootLabel(root: string): string {
|
||||
const home = os.homedir();
|
||||
const absolute = path.resolve(root);
|
||||
if (absolute === home) {
|
||||
return "~";
|
||||
}
|
||||
const normalized = absolute.replaceAll("\\", "/");
|
||||
const normalizedHome = home.replaceAll("\\", "/");
|
||||
const withinHome = normalized.startsWith(`${normalizedHome}/`);
|
||||
const stripped = withinHome ? normalized.slice(normalizedHome.length + 1) : normalized;
|
||||
const tail = stripped.split("/").filter(Boolean).slice(-2).join("/");
|
||||
return withinHome ? `~/${tail}` : tail || absolute;
|
||||
}
|
||||
|
||||
/** Resolves the preferred latest package version from detail metadata. */
|
||||
export function resolveLatestVersionFromPackage(detail: ClawHubPackageDetail): string | null {
|
||||
return detail.package?.latestVersion ?? detail.package?.tags?.latest ?? null;
|
||||
|
||||
@@ -318,23 +318,9 @@ describe("skills-clawhub", () => {
|
||||
expect(archiveCleanupMock).toHaveBeenCalledTimes(1);
|
||||
expect(reportClawHubSkillInstallTelemetryMock).toHaveBeenCalledWith({
|
||||
baseUrl: undefined,
|
||||
root: "/tmp/workspace",
|
||||
skills: expect.objectContaining({
|
||||
agentreceipt: expect.objectContaining({
|
||||
version: "1.0.0",
|
||||
installedAt: expect.any(Number),
|
||||
registry: "https://clawhub.ai",
|
||||
}),
|
||||
}),
|
||||
slug: "agentreceipt",
|
||||
version: "1.0.0",
|
||||
});
|
||||
const telemetrySkills = reportClawHubSkillInstallTelemetryMock.mock.calls[0]?.[0]?.skills as
|
||||
| Record<string, Record<string, unknown>>
|
||||
| undefined;
|
||||
expect(Object.keys(telemetrySkills?.agentreceipt ?? {}).toSorted()).toEqual([
|
||||
"installedAt",
|
||||
"registry",
|
||||
"version",
|
||||
]);
|
||||
});
|
||||
|
||||
it("bypasses ClawHub trust checks for official skill install resolutions", async () => {
|
||||
@@ -895,6 +881,12 @@ describe("skills-clawhub", () => {
|
||||
ownerHandle: "demo-owner",
|
||||
installedVersion: "1.0.0",
|
||||
});
|
||||
expect(reportClawHubSkillInstallTelemetryMock).toHaveBeenCalledWith({
|
||||
baseUrl: undefined,
|
||||
slug: "weather",
|
||||
ownerHandle: "demo-owner",
|
||||
version: "1.0.0",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not require acknowledgement for owner-qualified clean skills missing only cards", async () => {
|
||||
|
||||
@@ -466,21 +466,6 @@ function buildDownloadedArtifactLock(
|
||||
};
|
||||
}
|
||||
|
||||
function buildInstallTelemetrySkills(
|
||||
skills: ClawHubSkillsLockfile["skills"],
|
||||
): Record<string, { version: string; installedAt: number; registry?: string }> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(skills).map(([slug, entry]) => [
|
||||
slug,
|
||||
{
|
||||
version: entry.version,
|
||||
installedAt: entry.installedAt,
|
||||
...(entry.registry ? { registry: entry.registry } : {}),
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function snapshotClawHubSkillVerification(
|
||||
verification: ClawHubSkillVerificationResponse,
|
||||
): ClawHubSkillVerificationLock {
|
||||
@@ -1508,8 +1493,9 @@ async function performClawHubSkillInstall(
|
||||
await writeClawHubSkillsLockfile(params.workspaceDir, lock);
|
||||
await reportClawHubSkillInstallTelemetry({
|
||||
baseUrl: params.baseUrl,
|
||||
root: params.workspaceDir,
|
||||
skills: buildInstallTelemetrySkills(lock.skills),
|
||||
slug: params.slug,
|
||||
...(params.ownerHandle ? { ownerHandle: params.ownerHandle } : {}),
|
||||
version,
|
||||
}).catch(() => undefined);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user