mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
8483fe23f9
## Summary Adds anonymous, opt-out telemetry for Argent installer, update/uninstall flows, CLI telemetry controls, tool-server lifecycle, and tool invocation health signals. ## What Changed - Added a new private `@argent/telemetry` package - Introduced typed telemetry events with runtime property allowlisting/sanitization. - Added lazy anonymous identity creation in `~/.argent/telemetry-id`. - Added consent handling via: `argent telemetry enable`, `argent telemetry disable`, `argent telemetry status` - Added local debug logging with `ARGENT_TELEMETRY_DEBUG=1`. - Tracks installer funnel events for init, global install/update decisions, editor selection, allowlist decisions, skill installation, rules/agents copy, update, and uninstall. - Tracks tool-server start/stop and tool invocation/complete/fail events. - Adds per-tool invocation IDs so request metadata can be correlated without relying on ordering. - Records only coarse platform metadata (`ios` / `android`) for device tools - Ensures short-lived commands drain telemetry with bounded shutdown before exit. - Wires telemetry package into bundling and injects full Argent CLI version metadata at build time. - Added central `FAILURE_CODES` and `FailureSignal` helpers in `@argent/registry`. - Attached structured, sanitized failure metadata across registry errors, tool-server tools/services, HTTP validation/dependency gates, installer flows, and `argent run`. - Added telemetry allowlisting for safe failure fields only: static error codes/stages, coarse area/kind/command, bounded exit codes, allowlisted signals/spawn codes, and coarse network failure classes. - Split CLI-local `argent run` failures into `cli:run_fail` so they are not double-counted as tool failures. - Added installer telemetry for failed package actions, update/uninstall failures, rules/agents copy results, and skill refresh results. - Made telemetry consent writes atomic and kept telemetry status side-effect free via `peekAnonId()`. - Adds a coarse, anonymous signal to tool telemetry: which AI coding tool is driving the MCP server (Claude Code, Codex, Cursor, Gemini, VS Code, Windsurf, Zed, opencode, Copilot, or other). ## Tests Added / Updated - Telemetry unit coverage for consent precedence, identity creation, base props, sanitization, PostHog config, debug logging, shutdown, opt-out, and forget behavior. - Registry/tool-server tests for invocation IDs, platform metadata boundaries, startup failure flushing, and tool lifecycle telemetry. - Installer tests for final telemetry drain, update action classification, and uninstall identity cleanup ordering. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import * as fs from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
import { scopeHome, snapshotEnv } from "./helpers.js";
|
|
import { emitDebugPayload, isDebugEnabled } from "../src/debug.js";
|
|
import { debugLogPath } from "../src/paths.js";
|
|
|
|
describe("debug", () => {
|
|
scopeHome();
|
|
|
|
it("isDebugEnabled honours ARGENT_TELEMETRY_DEBUG=1", () => {
|
|
const restore = snapshotEnv(["ARGENT_TELEMETRY_DEBUG"]);
|
|
try {
|
|
delete process.env.ARGENT_TELEMETRY_DEBUG;
|
|
expect(isDebugEnabled()).toBe(false);
|
|
process.env.ARGENT_TELEMETRY_DEBUG = "1";
|
|
expect(isDebugEnabled()).toBe(true);
|
|
process.env.ARGENT_TELEMETRY_DEBUG = "true";
|
|
expect(isDebugEnabled()).toBe(true);
|
|
process.env.ARGENT_TELEMETRY_DEBUG = "0";
|
|
expect(isDebugEnabled()).toBe(false);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
it("emitDebugPayload appends to ~/.argent/telemetry-debug.log when debug is on", () => {
|
|
const restore = snapshotEnv(["ARGENT_TELEMETRY_DEBUG"]);
|
|
try {
|
|
process.env.ARGENT_TELEMETRY_DEBUG = "1";
|
|
emitDebugPayload({
|
|
event: "test:event",
|
|
distinctId: "00000000-0000-0000-0000-000000000000",
|
|
properties: { foo: "bar" },
|
|
ts: "2026-05-25T00:00:00.000Z",
|
|
});
|
|
const contents = fs.readFileSync(debugLogPath(), "utf8");
|
|
expect(contents).toContain("test:event");
|
|
expect(contents).toContain('"foo":"bar"');
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
it("emitDebugPayload is a no-op when debug is off (file is not created)", () => {
|
|
const restore = snapshotEnv(["ARGENT_TELEMETRY_DEBUG"]);
|
|
try {
|
|
delete process.env.ARGENT_TELEMETRY_DEBUG;
|
|
emitDebugPayload({
|
|
event: "test:event",
|
|
distinctId: "00000000-0000-0000-0000-000000000000",
|
|
properties: {},
|
|
ts: "2026-05-25T00:00:00.000Z",
|
|
});
|
|
expect(fs.existsSync(debugLogPath())).toBe(false);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
it("emitDebugPayload does not throw when properties contain circular values", () => {
|
|
const restore = snapshotEnv(["ARGENT_TELEMETRY_DEBUG"]);
|
|
try {
|
|
process.env.ARGENT_TELEMETRY_DEBUG = "1";
|
|
const circular: Record<string, unknown> = {};
|
|
circular.self = circular;
|
|
expect(() =>
|
|
emitDebugPayload({
|
|
event: "test:event",
|
|
distinctId: "00000000-0000-0000-0000-000000000000",
|
|
properties: circular,
|
|
ts: "2026-05-25T00:00:00.000Z",
|
|
})
|
|
).not.toThrow();
|
|
const contents = fs.readFileSync(debugLogPath(), "utf8");
|
|
expect(contents).toContain("debug_payload_serialization_error");
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
it("emitDebugPayload does not throw when properties contain BigInt", () => {
|
|
const restore = snapshotEnv(["ARGENT_TELEMETRY_DEBUG"]);
|
|
try {
|
|
process.env.ARGENT_TELEMETRY_DEBUG = "1";
|
|
expect(() =>
|
|
emitDebugPayload({
|
|
event: "test:event",
|
|
distinctId: "00000000-0000-0000-0000-000000000000",
|
|
properties: { value: 1n },
|
|
ts: "2026-05-25T00:00:00.000Z",
|
|
})
|
|
).not.toThrow();
|
|
const contents = fs.readFileSync(debugLogPath(), "utf8");
|
|
expect(contents).toContain("debug_payload_serialization_error");
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
});
|