huaweicloud-devkit 1.1.2-next.0 → 1.1.2-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +18 -18
- package/README.md +182 -38
- package/README.zh-CN.md +180 -36
- package/bin/dsh-postinstall.cjs +77 -0
- package/bin/setup.cjs +7 -4
- package/cordis.patch.yml +13 -0
- package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
- package/integrations/atomcode/hooks.json +9 -0
- package/integrations/dsh/hook-plugin.mjs +148 -0
- package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
- package/integrations/hermes/manifest.yaml +43 -0
- package/integrations/opencode/hooks/skill-tracker.js +115 -0
- package/integrations/opencode/opencode.json +1 -4
- package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
- package/package.json +74 -43
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +23 -33
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +2 -5
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
- package/plugins/huaweicloud-core/assets/icon.png +0 -0
- package/plugins/huaweicloud-core/assets/logo-dark.png +0 -0
- package/plugins/huaweicloud-core/assets/logo.png +0 -0
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +104 -28
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/safety/policy.json +75 -99
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +196 -35
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +38 -34
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +38 -32
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +40 -31
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +45 -37
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +57 -6
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +44 -39
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
- package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +46 -45
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +71 -61
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +776 -44
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +84 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +100 -0
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +36 -29
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +57 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +22 -15
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +23 -20
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +21 -2
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +30 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +68 -54
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +36 -34
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +142 -5
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +217 -5
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
- package/plugins/huaweicloud-core/src/auth/service.mjs +57 -2
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +366 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +203 -16
- package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
- package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +154 -99
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -12
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +25 -26
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +943 -10
- package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
- package/plugins/huaweicloud-core/src/search-market.mjs +49 -14
- package/plugins/huaweicloud-core/src/setup-cli.mjs +3091 -498
- package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
- package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
- package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
- package/plugins/huaweicloud-core/src/tools.mjs +1272 -146
- package/plugins/huaweicloud-core/src/update-check.mjs +383 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +40 -13
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
statSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
appendFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
import { homedir, hostname, type as osType, networkInterfaces, release as osRelease } from 'node:os';
|
|
13
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
const PLUGIN_DIR = join(__dirname, '..', '..');
|
|
19
|
+
const AGENT_TELEMETRY_DIR = join(PLUGIN_DIR, 'telemetry');
|
|
20
|
+
const GLOBAL_TELEMETRY_DIR = join(homedir(), '.huaweicloud-devkit', 'telemetry');
|
|
21
|
+
|
|
22
|
+
let PLUGIN_VERSION = '0.0.0';
|
|
23
|
+
try {
|
|
24
|
+
const pkg1 = join(PLUGIN_DIR, 'package.json');
|
|
25
|
+
const pkg2 = join(PLUGIN_DIR, '..', '..', 'package.json');
|
|
26
|
+
for (const p of [pkg1, pkg2]) {
|
|
27
|
+
if (existsSync(p)) {
|
|
28
|
+
const v = JSON.parse(readFileSync(p, 'utf8')).version;
|
|
29
|
+
if (v) {
|
|
30
|
+
PLUGIN_VERSION = v;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
function hookEventsPath() {
|
|
37
|
+
return join(AGENT_TELEMETRY_DIR, 'hook-events.jsonl');
|
|
38
|
+
}
|
|
39
|
+
function installStampPath() {
|
|
40
|
+
return join(AGENT_TELEMETRY_DIR, 'install-stamp');
|
|
41
|
+
}
|
|
42
|
+
function installCounterPath() {
|
|
43
|
+
return join(AGENT_TELEMETRY_DIR, 'install-counter');
|
|
44
|
+
}
|
|
45
|
+
function dauStampPath() {
|
|
46
|
+
return join(AGENT_TELEMETRY_DIR, 'dau-stamp');
|
|
47
|
+
}
|
|
48
|
+
function firstUseStampPath() {
|
|
49
|
+
return join(AGENT_TELEMETRY_DIR, 'first-use-stamp');
|
|
50
|
+
}
|
|
51
|
+
const MACHINE_FINGER_PATH = join(GLOBAL_TELEMETRY_DIR, 'machine-finger');
|
|
52
|
+
const INSTALLATION_ID_PATH = join(GLOBAL_TELEMETRY_DIR, 'installation-id');
|
|
53
|
+
const USER_HASH_PATH = join(GLOBAL_TELEMETRY_DIR, 'user-hash');
|
|
54
|
+
|
|
55
|
+
const MAX_QUEUE_SIZE = 500;
|
|
56
|
+
const FLUSH_INTERVAL_MS = 60_000;
|
|
57
|
+
const BATCH_SIZE = 100;
|
|
58
|
+
const FETCH_TIMEOUT_MS = 5000;
|
|
59
|
+
|
|
60
|
+
const DEFAULT_ENDPOINT = 'https://devkit.huaweicloud.com/rest/developer/server/hdkitservice/telemetry/events';
|
|
61
|
+
|
|
62
|
+
let eventQueue = [];
|
|
63
|
+
let isFlushing = false;
|
|
64
|
+
let flushTimer = null;
|
|
65
|
+
let lastCheckedDate = '';
|
|
66
|
+
let installId = null;
|
|
67
|
+
let userHash = null;
|
|
68
|
+
let agentHarness = 'unknown';
|
|
69
|
+
let agentVersion = '0.0.0';
|
|
70
|
+
const osTypeStr = osType();
|
|
71
|
+
const osVersionStr = osRelease();
|
|
72
|
+
|
|
73
|
+
const DEBUG = process.env.HUAWEICLOUD_DEVKIT_DEBUG === 'true';
|
|
74
|
+
function debugLogPath() {
|
|
75
|
+
return join(AGENT_TELEMETRY_DIR, 'telemetry-debug.log');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function debugLog(msg) {
|
|
79
|
+
if (!DEBUG) return;
|
|
80
|
+
try {
|
|
81
|
+
const path = debugLogPath();
|
|
82
|
+
ensureDir(dirname(path));
|
|
83
|
+
appendFileSync(path, `${new Date().toISOString()} ${msg}\n`, 'utf8');
|
|
84
|
+
} catch (_) {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ensureDir(dirPath = GLOBAL_TELEMETRY_DIR) {
|
|
88
|
+
if (!existsSync(dirPath)) {
|
|
89
|
+
mkdirSync(dirPath, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readTextFile(filePath) {
|
|
94
|
+
try {
|
|
95
|
+
return readFileSync(filePath, 'utf8').trim();
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeTextFile(filePath, content) {
|
|
102
|
+
ensureDir(dirname(filePath));
|
|
103
|
+
writeFileSync(filePath, content, 'utf8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function touchFile(filePath) {
|
|
107
|
+
ensureDir(dirname(filePath));
|
|
108
|
+
writeFileSync(filePath, '', 'utf8');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function stampExists(filePath) {
|
|
112
|
+
return existsSync(filePath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getStampUTCDate(filePath) {
|
|
116
|
+
try {
|
|
117
|
+
return new Date(statSync(filePath).mtime).toISOString().slice(0, 10);
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getUTCToday() {
|
|
124
|
+
return new Date().toISOString().slice(0, 10);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateMachineFinger() {
|
|
128
|
+
const host = hostname();
|
|
129
|
+
const nets = networkInterfaces();
|
|
130
|
+
let firstMac = '';
|
|
131
|
+
for (const key of Object.keys(nets).sort((a, b) => a.localeCompare(b))) {
|
|
132
|
+
const iface = nets[key].find((a) => a.mac && a.mac !== '00:00:00:00:00:00');
|
|
133
|
+
if (iface) {
|
|
134
|
+
firstMac = iface.mac;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const factor = `${host}|${firstMac}|${osTypeStr}|${homedir()}`;
|
|
139
|
+
return createHash('sha256').update(factor).digest('hex');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function generateOrRecoverInstallId() {
|
|
143
|
+
ensureDir();
|
|
144
|
+
if (existsSync(INSTALLATION_ID_PATH)) {
|
|
145
|
+
const cached = readTextFile(INSTALLATION_ID_PATH);
|
|
146
|
+
if (cached) return cached;
|
|
147
|
+
}
|
|
148
|
+
const finger = existsSync(MACHINE_FINGER_PATH) ? readTextFile(MACHINE_FINGER_PATH) : generateMachineFinger();
|
|
149
|
+
if (!finger) {
|
|
150
|
+
const fallback = randomUUID();
|
|
151
|
+
writeTextFile(INSTALLATION_ID_PATH, fallback);
|
|
152
|
+
return fallback;
|
|
153
|
+
}
|
|
154
|
+
writeTextFile(MACHINE_FINGER_PATH, finger);
|
|
155
|
+
const id = createHash('sha256').update(finger).digest('hex');
|
|
156
|
+
writeTextFile(INSTALLATION_ID_PATH, id);
|
|
157
|
+
return id;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function loadUserHash() {
|
|
161
|
+
if (existsSync(USER_HASH_PATH)) {
|
|
162
|
+
const cached = readTextFile(USER_HASH_PATH);
|
|
163
|
+
if (cached) userHash = cached;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function isTelemetryEnabled() {
|
|
168
|
+
return process.env.HUAWEICLOUD_DEVKIT_TELEMETRY !== 'off';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getEndpoint() {
|
|
172
|
+
return process.env.HUAWEICLOUD_DEVKIT_TELEMETRY_ENDPOINT || DEFAULT_ENDPOINT;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function capabilityFromKey(key) {
|
|
176
|
+
if (key.startsWith('tool:')) return 'mcp';
|
|
177
|
+
if (key.startsWith('cli:')) return 'cli';
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildEvent(raw) {
|
|
182
|
+
const event = {
|
|
183
|
+
key: raw.key,
|
|
184
|
+
value: raw.value,
|
|
185
|
+
installId: installId,
|
|
186
|
+
userHash: userHash,
|
|
187
|
+
version: PLUGIN_VERSION,
|
|
188
|
+
harness: agentHarness,
|
|
189
|
+
agentVersion: agentVersion,
|
|
190
|
+
os: osTypeStr,
|
|
191
|
+
osVersion: osVersionStr,
|
|
192
|
+
};
|
|
193
|
+
const cap = raw.capability || capabilityFromKey(raw.key);
|
|
194
|
+
if (cap) event.capability = cap;
|
|
195
|
+
return event;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function enqueueEvent(raw) {
|
|
199
|
+
if (!isTelemetryEnabled()) return;
|
|
200
|
+
if (!installId) return;
|
|
201
|
+
|
|
202
|
+
ingestHookEvents();
|
|
203
|
+
|
|
204
|
+
checkDauPing();
|
|
205
|
+
|
|
206
|
+
const event = buildEvent(raw);
|
|
207
|
+
eventQueue.push(event);
|
|
208
|
+
|
|
209
|
+
if (eventQueue.length > MAX_QUEUE_SIZE) {
|
|
210
|
+
eventQueue = eventQueue.slice(eventQueue.length - MAX_QUEUE_SIZE);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (eventQueue.length >= BATCH_SIZE) setImmediate(() => flushEvents());
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function checkDauPing() {
|
|
217
|
+
const today = getUTCToday();
|
|
218
|
+
if (today === lastCheckedDate) return;
|
|
219
|
+
lastCheckedDate = today;
|
|
220
|
+
if (!stampExists(dauStampPath()) || getStampUTCDate(dauStampPath()) !== today) {
|
|
221
|
+
eventQueue.unshift(buildEvent({ key: 'dau:active_today', value: '1' }));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function shouldSendFirstUsePing() {
|
|
226
|
+
return !stampExists(firstUseStampPath());
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function trackInstall() {
|
|
230
|
+
if (!isTelemetryEnabled()) return;
|
|
231
|
+
ensureDir(AGENT_TELEMETRY_DIR);
|
|
232
|
+
|
|
233
|
+
let count = 0;
|
|
234
|
+
const existing = readTextFile(installCounterPath());
|
|
235
|
+
if (existing) count = parseInt(existing, 10) || 0;
|
|
236
|
+
writeTextFile(installCounterPath(), String(count + 1));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function consumeInstallCounter() {
|
|
240
|
+
ensureDir(AGENT_TELEMETRY_DIR);
|
|
241
|
+
const existing = readTextFile(installCounterPath());
|
|
242
|
+
if (!existing) return 0;
|
|
243
|
+
const count = parseInt(existing, 10) || 0;
|
|
244
|
+
if (count <= 0) return 0;
|
|
245
|
+
writeTextFile(installCounterPath(), '0');
|
|
246
|
+
return count;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function ingestHookEvents() {
|
|
250
|
+
const hookPath = hookEventsPath();
|
|
251
|
+
const processingPath = hookPath + '.processing';
|
|
252
|
+
if (existsSync(processingPath)) {
|
|
253
|
+
try {
|
|
254
|
+
const lines = readFileSync(processingPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
255
|
+
for (const line of lines) {
|
|
256
|
+
try {
|
|
257
|
+
const parsed = JSON.parse(line);
|
|
258
|
+
eventQueue.push(buildEvent(parsed));
|
|
259
|
+
} catch {}
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
} finally {
|
|
263
|
+
try {
|
|
264
|
+
unlinkSync(processingPath);
|
|
265
|
+
} catch {}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!existsSync(hookPath)) return;
|
|
269
|
+
try {
|
|
270
|
+
renameSync(hookPath, processingPath);
|
|
271
|
+
} catch {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function trackToolInvoke(toolName, value = '1') {
|
|
277
|
+
if (!isTelemetryEnabled()) return;
|
|
278
|
+
enqueueEvent({ key: `tool:${toolName}`, value });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function trackSkillRetrieve(skillName) {
|
|
282
|
+
if (!isTelemetryEnabled()) return;
|
|
283
|
+
enqueueEvent({ key: 'skill:retrieve', value: skillName });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function trackSandboxConnect() {
|
|
287
|
+
if (!isTelemetryEnabled()) return;
|
|
288
|
+
enqueueEvent({ key: 'sandbox:connect', value: '1' });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function trackSandboxDisconnect() {
|
|
292
|
+
if (!isTelemetryEnabled()) return;
|
|
293
|
+
enqueueEvent({ key: 'sandbox:disconnect', value: '1' });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function cacheUserHash(hash) {
|
|
297
|
+
if (!hash) return;
|
|
298
|
+
userHash = hash;
|
|
299
|
+
ensureDir();
|
|
300
|
+
writeTextFile(USER_HASH_PATH, hash);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function flushEvents() {
|
|
304
|
+
if (isFlushing) return;
|
|
305
|
+
if (eventQueue.length === 0) return;
|
|
306
|
+
isFlushing = true;
|
|
307
|
+
|
|
308
|
+
const batch = eventQueue.splice(0, BATCH_SIZE);
|
|
309
|
+
const keys = batch.map((e) => e.key).join(',');
|
|
310
|
+
debugLog(
|
|
311
|
+
`FLUSH start events=${batch.length} harness=${batch[0].harness} agentVersion=${batch[0].agentVersion} keys=[${keys}]`,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const endpoint = getEndpoint();
|
|
315
|
+
const controller = new AbortController();
|
|
316
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
317
|
+
|
|
318
|
+
fetch(endpoint, {
|
|
319
|
+
method: 'POST',
|
|
320
|
+
headers: { 'Content-Type': 'application/json' },
|
|
321
|
+
body: JSON.stringify(batch),
|
|
322
|
+
signal: controller.signal,
|
|
323
|
+
})
|
|
324
|
+
.then((resp) => {
|
|
325
|
+
clearTimeout(timer);
|
|
326
|
+
debugLog(`POST status=${resp.status} events=${batch.length}`);
|
|
327
|
+
if (resp.ok) {
|
|
328
|
+
for (const event of batch) {
|
|
329
|
+
if (event.key === 'dau:active_today') touchFile(dauStampPath());
|
|
330
|
+
if (event.key === 'plugin:install') touchFile(installStampPath());
|
|
331
|
+
if (event.key === 'plugin:first_use') touchFile(firstUseStampPath());
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
eventQueue = [...batch, ...eventQueue];
|
|
335
|
+
}
|
|
336
|
+
isFlushing = false;
|
|
337
|
+
})
|
|
338
|
+
.catch((error) => {
|
|
339
|
+
clearTimeout(timer);
|
|
340
|
+
debugLog(`POST FAIL err=${error.message} events=${batch.length}`);
|
|
341
|
+
eventQueue = [...batch, ...eventQueue];
|
|
342
|
+
isFlushing = false;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function initTelemetry({ harness, version }) {
|
|
347
|
+
installId = generateOrRecoverInstallId();
|
|
348
|
+
agentHarness = harness || 'unknown';
|
|
349
|
+
agentVersion = version || '0.0.0';
|
|
350
|
+
|
|
351
|
+
loadUserHash();
|
|
352
|
+
|
|
353
|
+
if (!isTelemetryEnabled()) return;
|
|
354
|
+
|
|
355
|
+
if (!stampExists(installStampPath()) && !existsSync(installCounterPath())) {
|
|
356
|
+
trackInstall();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const pendingInstalls = consumeInstallCounter();
|
|
360
|
+
for (let i = 0; i < pendingInstalls; i++) {
|
|
361
|
+
enqueueEvent({ key: 'plugin:install', value: '1' });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (shouldSendFirstUsePing()) {
|
|
365
|
+
enqueueEvent({ key: 'plugin:first_use', value: '1' });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (flushTimer) clearInterval(flushTimer);
|
|
369
|
+
flushTimer = setInterval(() => {
|
|
370
|
+
ingestHookEvents();
|
|
371
|
+
checkDauPing();
|
|
372
|
+
if (eventQueue.length > 0) setImmediate(() => flushEvents());
|
|
373
|
+
}, FLUSH_INTERVAL_MS);
|
|
374
|
+
flushTimer.unref();
|
|
375
|
+
|
|
376
|
+
ingestHookEvents();
|
|
377
|
+
if (eventQueue.length > 0) setImmediate(() => flushEvents());
|
|
378
|
+
}
|