@ainyc/canonry 4.13.3 → 4.15.0

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.
@@ -3,8 +3,9 @@ import {
3
3
  canonryMcpTools,
4
4
  configExists,
5
5
  loadConfig,
6
+ loadConfigRaw,
6
7
  saveConfigPatch
7
- } from "./chunk-5NYG5EC7.js";
8
+ } from "./chunk-C32VL5BB.js";
8
9
  import {
9
10
  DEFAULT_RUN_HISTORY_LIMIT,
10
11
  IntelligenceService,
@@ -155,11 +156,19 @@ import {
155
156
 
156
157
  // src/telemetry.ts
157
158
  import crypto from "crypto";
159
+ import os from "os";
158
160
  import { createRequire } from "module";
159
161
  var _require = createRequire(import.meta.url);
160
162
  var { version: VERSION } = _require("../package.json");
161
163
  var TELEMETRY_ENDPOINT = "https://ainyc.ai/api/telemetry";
162
164
  var TIMEOUT_MS = 3e3;
165
+ var ANON_ID_ENV_VAR = "CANONRY_ANONYMOUS_ID";
166
+ var ANON_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
167
+ var SESSION_ID = crypto.randomUUID();
168
+ var CURRENT_SOURCE = "cli";
169
+ function setTelemetrySource(source) {
170
+ CURRENT_SOURCE = source;
171
+ }
163
172
  function isTelemetryEnabled() {
164
173
  if (process.env.CANONRY_TELEMETRY_DISABLED === "1") return false;
165
174
  if (process.env.DO_NOT_TRACK === "1") return false;
@@ -173,18 +182,66 @@ function isTelemetryEnabled() {
173
182
  }
174
183
  }
175
184
  function getOrCreateAnonymousId() {
176
- if (!configExists()) return void 0;
185
+ const fromEnv = readEnvAnonymousId();
186
+ if (fromEnv) return fromEnv;
187
+ if (configExists()) {
188
+ try {
189
+ const config = loadConfig();
190
+ if (config.anonymousId) return config.anonymousId;
191
+ const id = crypto.randomUUID();
192
+ config.anonymousId = id;
193
+ try {
194
+ saveConfigPatch(config);
195
+ } catch {
196
+ return getDeterministicAnonymousId();
197
+ }
198
+ return id;
199
+ } catch {
200
+ return getDeterministicAnonymousId();
201
+ }
202
+ }
203
+ return getDeterministicAnonymousId();
204
+ }
205
+ function readEnvAnonymousId() {
206
+ const raw = process.env[ANON_ID_ENV_VAR]?.trim();
207
+ if (!raw) return void 0;
208
+ if (!ANON_ID_PATTERN.test(raw)) {
209
+ return void 0;
210
+ }
211
+ return raw.toLowerCase();
212
+ }
213
+ function getDeterministicAnonymousId() {
177
214
  try {
178
- const config = loadConfig();
179
- if (config.anonymousId) return config.anonymousId;
180
- const id = crypto.randomUUID();
181
- config.anonymousId = id;
182
- saveConfigPatch(config);
183
- return id;
215
+ const hostname = os.hostname() || "";
216
+ const mac = firstNonInternalMac();
217
+ const seed = `canonry-anon:${hostname}:${mac}`;
218
+ const hex = crypto.createHash("sha256").update(seed).digest("hex");
219
+ const a = hex.slice(0, 8);
220
+ const b = hex.slice(8, 12);
221
+ const c = "5" + hex.slice(13, 16);
222
+ const dHi = (parseInt(hex.slice(16, 18), 16) & 63 | 128).toString(16).padStart(2, "0");
223
+ const d = dHi + hex.slice(18, 20);
224
+ const e = hex.slice(20, 32);
225
+ return `${a}-${b}-${c}-${d}-${e}`;
184
226
  } catch {
185
227
  return void 0;
186
228
  }
187
229
  }
230
+ function firstNonInternalMac() {
231
+ try {
232
+ const interfaces = os.networkInterfaces();
233
+ for (const ifaces of Object.values(interfaces)) {
234
+ if (!ifaces) continue;
235
+ for (const iface of ifaces) {
236
+ if (iface.internal) continue;
237
+ if (!iface.mac || iface.mac === "00:00:00:00:00:00") continue;
238
+ return iface.mac;
239
+ }
240
+ }
241
+ } catch {
242
+ }
243
+ return "no-mac";
244
+ }
188
245
  function isFirstRun() {
189
246
  if (!configExists()) return false;
190
247
  try {
@@ -199,19 +256,42 @@ function showFirstRunNotice() {
199
256
  "\nCanonry collects anonymous telemetry to prioritize features.\nDisable any time: canonry telemetry disable\nLearn more: https://ainyc.ai/telemetry\n\n"
200
257
  );
201
258
  }
202
- function trackEvent(event, properties) {
259
+ function detectAndTrackUpgrade() {
260
+ if (!isTelemetryEnabled()) return;
261
+ if (!configExists()) return;
262
+ let lastSeen;
263
+ try {
264
+ const raw = loadConfigRaw();
265
+ lastSeen = raw?.lastSeenVersion;
266
+ } catch {
267
+ return;
268
+ }
269
+ if (lastSeen === VERSION) return;
270
+ try {
271
+ saveConfigPatch({ lastSeenVersion: VERSION });
272
+ } catch {
273
+ return;
274
+ }
275
+ if (!lastSeen) return;
276
+ trackEvent("cli.upgraded", { fromVersion: lastSeen, toVersion: VERSION });
277
+ }
278
+ function trackEvent(event, properties, options) {
203
279
  if (!isTelemetryEnabled()) return;
204
280
  const anonymousId = getOrCreateAnonymousId();
205
281
  if (!anonymousId) return;
206
282
  const payload = {
207
283
  anonymousId,
284
+ sessionId: SESSION_ID,
285
+ source: options?.source ?? CURRENT_SOURCE,
208
286
  event,
209
287
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
210
288
  version: VERSION,
211
289
  nodeVersion: process.versions.node,
212
290
  os: process.platform,
213
291
  arch: process.arch,
214
- properties
292
+ ...options?.sourceContext ? { sourceContext: options.sourceContext } : {},
293
+ ...options?.errorCode ? { errorCode: options.errorCode } : {},
294
+ ...properties ? { properties } : {}
215
295
  };
216
296
  const controller = new AbortController();
217
297
  const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
@@ -227,7 +307,7 @@ function trackEvent(event, properties) {
227
307
 
228
308
  // src/server.ts
229
309
  import { createRequire as createRequire3 } from "module";
230
- import crypto30 from "crypto";
310
+ import crypto31 from "crypto";
231
311
  import fs12 from "fs";
232
312
  import path14 from "path";
233
313
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -12476,10 +12556,10 @@ async function bingRoutes(app, opts) {
12476
12556
  // ../api-routes/src/cdp.ts
12477
12557
  import fs from "fs";
12478
12558
  import path from "path";
12479
- import os from "os";
12559
+ import os2 from "os";
12480
12560
  import { eq as eq20, and as and8 } from "drizzle-orm";
12481
12561
  function getScreenshotDir() {
12482
- return path.join(os.homedir(), ".canonry", "screenshots");
12562
+ return path.join(os2.homedir(), ".canonry", "screenshots");
12483
12563
  }
12484
12564
  async function cdpRoutes(app, opts) {
12485
12565
  app.get("/screenshots/:snapshotId", async (request, reply) => {
@@ -15106,13 +15186,13 @@ import crypto18 from "crypto";
15106
15186
  import { and as and11, asc as asc2, desc as desc11, eq as eq22, sql as sql6 } from "drizzle-orm";
15107
15187
 
15108
15188
  // ../integration-commoncrawl/src/constants.ts
15109
- import os2 from "os";
15189
+ import os3 from "os";
15110
15190
  import path2 from "path";
15111
15191
  var CC_BASE_URL = "https://data.commoncrawl.org/projects/hyperlinkgraph";
15112
- var PLUGIN_DIR = path2.join(os2.homedir(), ".canonry", "plugins");
15192
+ var PLUGIN_DIR = path2.join(os3.homedir(), ".canonry", "plugins");
15113
15193
  var PLUGIN_PKG_JSON = path2.join(PLUGIN_DIR, "package.json");
15114
15194
  var DUCKDB_SPEC = process.env.CANONRY_DUCKDB_SPEC ?? "@duckdb/node-api@1.4.4-r.3";
15115
- var CC_CACHE_DIR = process.env.CANONRY_CC_CACHE_DIR ?? path2.join(os2.homedir(), ".canonry", "cache", "commoncrawl");
15195
+ var CC_CACHE_DIR = process.env.CANONRY_CC_CACHE_DIR ?? path2.join(os3.homedir(), ".canonry", "cache", "commoncrawl");
15116
15196
  var RELEASE_ID_REGEX = /^cc-main-(\d{4})-(jan-feb-mar|apr-may-jun|jul-aug-sep|oct-nov-dec)$/;
15117
15197
  function ccReleasePaths(release) {
15118
15198
  const base = `${CC_BASE_URL}/${release}/domain`;
@@ -17629,7 +17709,7 @@ async function apiRoutes(app, opts) {
17629
17709
  }
17630
17710
 
17631
17711
  // src/server.ts
17632
- import os5 from "os";
17712
+ import os6 from "os";
17633
17713
 
17634
17714
  // ../provider-gemini/src/normalize.ts
17635
17715
  import { GoogleGenAI } from "@google/genai";
@@ -19018,7 +19098,7 @@ var localAdapter = {
19018
19098
 
19019
19099
  // ../provider-cdp/src/adapter.ts
19020
19100
  import path8 from "path";
19021
- import os3 from "os";
19101
+ import os4 from "os";
19022
19102
 
19023
19103
  // ../provider-cdp/src/connection.ts
19024
19104
  import CDP from "chrome-remote-interface";
@@ -19482,7 +19562,7 @@ function getConnection(config) {
19482
19562
  return conn;
19483
19563
  }
19484
19564
  function getScreenshotDir2() {
19485
- return path8.join(os3.homedir(), ".canonry", "screenshots");
19565
+ return path8.join(os4.homedir(), ".canonry", "screenshots");
19486
19566
  }
19487
19567
  var cdpChatgptAdapter = {
19488
19568
  name: "cdp:chatgpt",
@@ -20114,12 +20194,52 @@ function removeWordpressConnection(config, projectName) {
20114
20194
  }
20115
20195
 
20116
20196
  // src/job-runner.ts
20117
- import crypto21 from "crypto";
20197
+ import crypto22 from "crypto";
20118
20198
  import fs7 from "fs";
20119
20199
  import path9 from "path";
20120
- import os4 from "os";
20200
+ import os5 from "os";
20121
20201
  import { and as and12, eq as eq24, inArray as inArray7, sql as sql8 } from "drizzle-orm";
20122
20202
 
20203
+ // src/run-telemetry.ts
20204
+ import crypto21 from "crypto";
20205
+ function extractRegistrableHost(input) {
20206
+ if (!input) return null;
20207
+ const trimmed = input.trim();
20208
+ if (!trimmed) return null;
20209
+ let host;
20210
+ try {
20211
+ const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
20212
+ host = new URL(candidate).hostname;
20213
+ } catch {
20214
+ return null;
20215
+ }
20216
+ host = host.toLowerCase();
20217
+ if (host.startsWith("www.")) host = host.slice(4);
20218
+ if (!host || !host.includes(".")) return null;
20219
+ return host;
20220
+ }
20221
+ function hashDomain(input) {
20222
+ const host = extractRegistrableHost(input);
20223
+ if (!host) return null;
20224
+ return crypto21.createHash("sha256").update(host).digest("hex");
20225
+ }
20226
+ function buildRunCompletedProps(input) {
20227
+ const totalMs = input.phases?.total_ms ?? Date.now() - input.startTime;
20228
+ const props = {
20229
+ status: input.status,
20230
+ providerCount: input.providerCount,
20231
+ providers: [...input.providers],
20232
+ queryCount: input.queryCount,
20233
+ durationMs: totalMs
20234
+ };
20235
+ if (input.trigger) props.trigger = input.trigger;
20236
+ const domainHash = hashDomain(input.canonicalDomain ?? null);
20237
+ if (domainHash) props.domainHash = domainHash;
20238
+ if (input.phases) props.phases = input.phases;
20239
+ if (input.location) props.location = input.location;
20240
+ return props;
20241
+ }
20242
+
20123
20243
  // src/citation-utils.ts
20124
20244
  function domainMatches(domain, canonicalDomain) {
20125
20245
  const normalized = normalizeProjectDomain(canonicalDomain);
@@ -20366,6 +20486,50 @@ function resolveProviderFanout() {
20366
20486
  const parsed = Number.parseInt(raw, 10);
20367
20487
  return Number.isFinite(parsed) && parsed > 0 ? parsed : PROVIDER_FANOUT_DEFAULT;
20368
20488
  }
20489
+ function classifyRunAbortReason(message) {
20490
+ if (/^No providers configured\b/.test(message)) return "no_provider";
20491
+ if (/^Project [^ ]+ not found$/.test(message)) return "project_not_found";
20492
+ if (/^Daily quota exceeded\b/.test(message)) return "quota_exceeded";
20493
+ if (/^Run [^ ]+ not found$/.test(message)) return "run_not_found";
20494
+ if (/^Run [^ ]+ is not executable\b/.test(message)) return "run_not_executable";
20495
+ return void 0;
20496
+ }
20497
+ function classifyProviderErrors(errors) {
20498
+ const codes = /* @__PURE__ */ new Set();
20499
+ for (const message of errors.values()) {
20500
+ codes.add(classifyOneProviderError(message));
20501
+ }
20502
+ const priority = [
20503
+ "PROVIDER_AUTH",
20504
+ "RATE_LIMITED",
20505
+ "TIMEOUT",
20506
+ "NETWORK",
20507
+ "PARSE_ERROR",
20508
+ "UNKNOWN"
20509
+ ];
20510
+ for (const code of priority) {
20511
+ if (codes.has(code)) return code;
20512
+ }
20513
+ return "UNKNOWN";
20514
+ }
20515
+ function classifyOneProviderError(message) {
20516
+ if (/\b401\b|\b403\b|unauthorized|forbidden|invalid[_ -]?api[_ -]?key|missing[_ -]?api[_ -]?key|authentication/i.test(message)) {
20517
+ return "PROVIDER_AUTH";
20518
+ }
20519
+ if (/\b429\b|rate[_ -]?limit|too many requests|quota[_ -]?exceeded/i.test(message)) {
20520
+ return "RATE_LIMITED";
20521
+ }
20522
+ if (/timeout|timed out|ETIMEDOUT/i.test(message)) {
20523
+ return "TIMEOUT";
20524
+ }
20525
+ if (/ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|network|fetch failed|socket hang up/i.test(message)) {
20526
+ return "NETWORK";
20527
+ }
20528
+ if (/parse|unexpected token|invalid json|malformed|JSON\.parse/i.test(message)) {
20529
+ return "PARSE_ERROR";
20530
+ }
20531
+ return "UNKNOWN";
20532
+ }
20369
20533
  var JobRunner = class {
20370
20534
  db;
20371
20535
  registry;
@@ -20386,20 +20550,26 @@ var JobRunner = class {
20386
20550
  async executeRun(runId, projectId, providerOverride, locationOverride) {
20387
20551
  const now = (/* @__PURE__ */ new Date()).toISOString();
20388
20552
  const startTime = Date.now();
20553
+ let providerCallStart;
20554
+ let providerCallEnd;
20389
20555
  let runLocation;
20390
20556
  let activeProviders = [];
20391
20557
  let projectQueries = [];
20558
+ let runTrigger;
20559
+ let canonicalDomain;
20392
20560
  const providerDispatchCounts = /* @__PURE__ */ new Map();
20393
20561
  try {
20394
20562
  const existingRun = this.getRunState(runId);
20395
20563
  if (!existingRun) {
20396
20564
  throw new Error(`Run ${runId} not found`);
20397
20565
  }
20566
+ runTrigger = existingRun.trigger ?? void 0;
20398
20567
  if (existingRun.status === "cancelled") {
20399
20568
  this.handleCancelledRun(runId, projectId, startTime, {
20400
20569
  providerCount: 0,
20401
20570
  providers: [],
20402
- queryCount: 0
20571
+ queryCount: 0,
20572
+ ...runTrigger ? { trigger: runTrigger } : {}
20403
20573
  });
20404
20574
  return;
20405
20575
  }
@@ -20414,6 +20584,7 @@ var JobRunner = class {
20414
20584
  if (!project) {
20415
20585
  throw new Error(`Project ${projectId} not found`);
20416
20586
  }
20587
+ canonicalDomain = project.canonicalDomain;
20417
20588
  if (locationOverride === null) {
20418
20589
  runLocation = void 0;
20419
20590
  } else if (locationOverride) {
@@ -20441,7 +20612,9 @@ var JobRunner = class {
20441
20612
  providerCount: activeProviders.length,
20442
20613
  providers: activeProviders.map((provider) => provider.adapter.name),
20443
20614
  queryCount: projectQueries.length,
20444
- ...runLocation ? { location: runLocation.label } : {}
20615
+ ...runLocation ? { location: runLocation.label } : {},
20616
+ ...runTrigger ? { trigger: runTrigger } : {},
20617
+ ...canonicalDomain ? { canonicalDomain } : {}
20445
20618
  };
20446
20619
  const queriesPerProvider = projectQueries.length;
20447
20620
  const todayPeriod = getCurrentUsageDay();
@@ -20507,8 +20680,8 @@ var JobRunner = class {
20507
20680
  );
20508
20681
  let screenshotRelPath = null;
20509
20682
  if (raw.screenshotPath && fs7.existsSync(raw.screenshotPath)) {
20510
- const snapshotId = crypto21.randomUUID();
20511
- const screenshotDir = path9.join(os4.homedir(), ".canonry", "screenshots", runId);
20683
+ const snapshotId = crypto22.randomUUID();
20684
+ const screenshotDir = path9.join(os5.homedir(), ".canonry", "screenshots", runId);
20512
20685
  if (!fs7.existsSync(screenshotDir)) fs7.mkdirSync(screenshotDir, { recursive: true });
20513
20686
  const destPath = path9.join(screenshotDir, `${snapshotId}.png`);
20514
20687
  fs7.renameSync(raw.screenshotPath, destPath);
@@ -20537,7 +20710,7 @@ var JobRunner = class {
20537
20710
  }).run();
20538
20711
  } else {
20539
20712
  this.db.insert(querySnapshots).values({
20540
- id: crypto21.randomUUID(),
20713
+ id: crypto22.randomUUID(),
20541
20714
  runId,
20542
20715
  queryId: q.id,
20543
20716
  provider: providerName,
@@ -20573,6 +20746,7 @@ var JobRunner = class {
20573
20746
  }
20574
20747
  }
20575
20748
  };
20749
+ providerCallStart = Date.now();
20576
20750
  await runWithConcurrency(apiProviders, resolveProviderFanout(), async (registeredProvider) => {
20577
20751
  await Promise.all(projectQueries.map(async (q) => {
20578
20752
  await processQueryForProvider(registeredProvider, q);
@@ -20583,6 +20757,7 @@ var JobRunner = class {
20583
20757
  await processQueryForProvider(registeredProvider, q);
20584
20758
  }
20585
20759
  }
20760
+ providerCallEnd = Date.now();
20586
20761
  this.throwIfRunCancelled(runId);
20587
20762
  const allFailed = totalSnapshotsInserted === 0 && providerErrors.size > 0;
20588
20763
  const someFailed = providerErrors.size > 0;
@@ -20597,14 +20772,23 @@ var JobRunner = class {
20597
20772
  }
20598
20773
  this.flushProviderUsage(projectId, providerDispatchCounts);
20599
20774
  const finalStatus = allFailed ? "failed" : someFailed ? "partial" : "completed";
20600
- trackEvent("run.completed", {
20601
- status: finalStatus,
20602
- providerCount: executionContext.providerCount,
20603
- providers: executionContext.providers,
20604
- queryCount: executionContext.queryCount,
20605
- durationMs: Date.now() - startTime,
20606
- ...executionContext.location ? { location: executionContext.location } : {}
20607
- });
20775
+ const failureCode = providerErrors.size > 0 ? classifyProviderErrors(providerErrors) : void 0;
20776
+ const phases = buildPhases({ startTime, providerCallStart, providerCallEnd });
20777
+ trackEvent(
20778
+ "run.completed",
20779
+ buildRunCompletedProps({
20780
+ status: finalStatus,
20781
+ providerCount: executionContext.providerCount,
20782
+ providers: executionContext.providers,
20783
+ queryCount: executionContext.queryCount,
20784
+ startTime,
20785
+ trigger: executionContext.trigger,
20786
+ canonicalDomain: executionContext.canonicalDomain,
20787
+ phases,
20788
+ location: executionContext.location
20789
+ }),
20790
+ failureCode ? { errorCode: failureCode } : void 0
20791
+ );
20608
20792
  this.incrementUsage(projectId, "runs", 1);
20609
20793
  if (this.onRunCompleted) {
20610
20794
  this.onRunCompleted(runId, projectId).catch((err) => {
@@ -20616,7 +20800,9 @@ var JobRunner = class {
20616
20800
  providerCount: activeProviders.length,
20617
20801
  providers: activeProviders.map((provider) => provider.adapter.name),
20618
20802
  queryCount: projectQueries.length,
20619
- ...runLocation ? { location: runLocation.label } : {}
20803
+ ...runLocation ? { location: runLocation.label } : {},
20804
+ ...runTrigger ? { trigger: runTrigger } : {},
20805
+ ...canonicalDomain ? { canonicalDomain } : {}
20620
20806
  };
20621
20807
  if (err instanceof RunCancelledError || this.isRunCancelled(runId)) {
20622
20808
  this.flushProviderUsage(projectId, providerDispatchCounts);
@@ -20630,14 +20816,38 @@ var JobRunner = class {
20630
20816
  error: errorMessage
20631
20817
  }).where(eq24(runs.id, runId)).run();
20632
20818
  this.flushProviderUsage(projectId, providerDispatchCounts);
20633
- trackEvent("run.completed", {
20634
- status: "failed",
20635
- providerCount: executionContext.providerCount,
20636
- providers: executionContext.providers,
20637
- queryCount: executionContext.queryCount,
20638
- durationMs: Date.now() - startTime,
20639
- ...executionContext.location ? { location: executionContext.location } : {}
20640
- });
20819
+ const abortReason = classifyRunAbortReason(errorMessage);
20820
+ const phases = buildPhases({ startTime, providerCallStart, providerCallEnd });
20821
+ if (abortReason) {
20822
+ const domainHash = hashDomain(executionContext.canonicalDomain ?? null);
20823
+ trackEvent("run.aborted", {
20824
+ reason: abortReason,
20825
+ providerCount: executionContext.providerCount,
20826
+ providers: executionContext.providers,
20827
+ queryCount: executionContext.queryCount,
20828
+ durationMs: Date.now() - startTime,
20829
+ ...executionContext.trigger ? { trigger: executionContext.trigger } : {},
20830
+ ...domainHash ? { domainHash } : {},
20831
+ ...phases ? { phases } : {},
20832
+ ...executionContext.location ? { location: executionContext.location } : {}
20833
+ });
20834
+ } else {
20835
+ trackEvent(
20836
+ "run.completed",
20837
+ buildRunCompletedProps({
20838
+ status: "failed",
20839
+ providerCount: executionContext.providerCount,
20840
+ providers: executionContext.providers,
20841
+ queryCount: executionContext.queryCount,
20842
+ startTime,
20843
+ trigger: executionContext.trigger,
20844
+ canonicalDomain: executionContext.canonicalDomain,
20845
+ phases,
20846
+ location: executionContext.location
20847
+ }),
20848
+ { errorCode: "UNKNOWN" }
20849
+ );
20850
+ }
20641
20851
  if (this.onRunCompleted) {
20642
20852
  this.onRunCompleted(runId, projectId).catch((notifErr) => {
20643
20853
  log.error("notification.callback-failed", { runId, error: notifErr instanceof Error ? notifErr.message : String(notifErr) });
@@ -20649,7 +20859,7 @@ var JobRunner = class {
20649
20859
  const now = (/* @__PURE__ */ new Date()).toISOString();
20650
20860
  const period = now.slice(0, 10);
20651
20861
  this.db.insert(usageCounters).values({
20652
- id: crypto21.randomUUID(),
20862
+ id: crypto22.randomUUID(),
20653
20863
  scope,
20654
20864
  period,
20655
20865
  metric,
@@ -20670,7 +20880,8 @@ var JobRunner = class {
20670
20880
  return this.db.select({
20671
20881
  status: runs.status,
20672
20882
  finishedAt: runs.finishedAt,
20673
- error: runs.error
20883
+ error: runs.error,
20884
+ trigger: runs.trigger
20674
20885
  }).from(runs).where(eq24(runs.id, runId)).get();
20675
20886
  }
20676
20887
  isRunCancelled(runId) {
@@ -20689,14 +20900,20 @@ var JobRunner = class {
20689
20900
  error: currentRun.error ?? "Cancelled by user"
20690
20901
  }).where(eq24(runs.id, runId)).run();
20691
20902
  }
20692
- trackEvent("run.completed", {
20693
- status: "cancelled",
20694
- providerCount: context.providerCount,
20695
- providers: context.providers,
20696
- queryCount: context.queryCount,
20697
- durationMs: Date.now() - startTime,
20698
- ...context.location ? { location: context.location } : {}
20699
- });
20903
+ trackEvent(
20904
+ "run.completed",
20905
+ buildRunCompletedProps({
20906
+ status: "cancelled",
20907
+ providerCount: context.providerCount,
20908
+ providers: context.providers,
20909
+ queryCount: context.queryCount,
20910
+ startTime,
20911
+ trigger: context.trigger,
20912
+ canonicalDomain: context.canonicalDomain,
20913
+ location: context.location
20914
+ }),
20915
+ { errorCode: "RUN_CANCELLED" }
20916
+ );
20700
20917
  if (this.onRunCompleted) {
20701
20918
  this.onRunCompleted(runId, projectId).catch((err) => {
20702
20919
  log.error("notification.callback-failed", { runId, error: err instanceof Error ? err.message : String(err) });
@@ -20707,9 +20924,18 @@ var JobRunner = class {
20707
20924
  function getCurrentUsageDay() {
20708
20925
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
20709
20926
  }
20927
+ function buildPhases(input) {
20928
+ const total_ms = Date.now() - input.startTime;
20929
+ if (input.providerCallStart === void 0) {
20930
+ return { setup_ms: total_ms, provider_call_ms: 0, total_ms };
20931
+ }
20932
+ const setup_ms = input.providerCallStart - input.startTime;
20933
+ const provider_call_ms = (input.providerCallEnd ?? Date.now()) - input.providerCallStart;
20934
+ return { setup_ms, provider_call_ms, total_ms };
20935
+ }
20710
20936
 
20711
20937
  // src/gsc-sync.ts
20712
- import crypto22 from "crypto";
20938
+ import crypto23 from "crypto";
20713
20939
  import { eq as eq25, and as and13, sql as sql9 } from "drizzle-orm";
20714
20940
  var log2 = createLogger("GscSync");
20715
20941
  function formatDate3(d) {
@@ -20775,7 +21001,7 @@ async function executeGscSync(db, runId, projectId, opts) {
20775
21001
  for (const row of batch) {
20776
21002
  const [query, page, country, device, date] = row.keys;
20777
21003
  db.insert(gscSearchData).values({
20778
- id: crypto22.randomUUID(),
21004
+ id: crypto23.randomUUID(),
20779
21005
  projectId,
20780
21006
  syncRunId: runId,
20781
21007
  date: date ?? "",
@@ -20809,7 +21035,7 @@ async function executeGscSync(db, runId, projectId, opts) {
20809
21035
  const rich = ir.richResultsResult;
20810
21036
  const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
20811
21037
  db.insert(gscUrlInspections).values({
20812
- id: crypto22.randomUUID(),
21038
+ id: crypto23.randomUUID(),
20813
21039
  projectId,
20814
21040
  syncRunId: runId,
20815
21041
  url: pageUrl,
@@ -20853,7 +21079,7 @@ async function executeGscSync(db, runId, projectId, opts) {
20853
21079
  const snapshotDate = formatDate3(/* @__PURE__ */ new Date());
20854
21080
  db.delete(gscCoverageSnapshots).where(and13(eq25(gscCoverageSnapshots.projectId, projectId), eq25(gscCoverageSnapshots.date, snapshotDate))).run();
20855
21081
  db.insert(gscCoverageSnapshots).values({
20856
- id: crypto22.randomUUID(),
21082
+ id: crypto23.randomUUID(),
20857
21083
  projectId,
20858
21084
  syncRunId: runId,
20859
21085
  date: snapshotDate,
@@ -20873,7 +21099,7 @@ async function executeGscSync(db, runId, projectId, opts) {
20873
21099
  }
20874
21100
 
20875
21101
  // src/gsc-inspect-sitemap.ts
20876
- import crypto23 from "crypto";
21102
+ import crypto24 from "crypto";
20877
21103
  import { eq as eq26, and as and14 } from "drizzle-orm";
20878
21104
 
20879
21105
  // src/sitemap-parser.ts
@@ -21042,7 +21268,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
21042
21268
  const rich = ir.richResultsResult;
21043
21269
  const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
21044
21270
  db.insert(gscUrlInspections).values({
21045
- id: crypto23.randomUUID(),
21271
+ id: crypto24.randomUUID(),
21046
21272
  projectId,
21047
21273
  syncRunId: runId,
21048
21274
  url: pageUrl,
@@ -21092,7 +21318,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
21092
21318
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
21093
21319
  db.delete(gscCoverageSnapshots).where(and14(eq26(gscCoverageSnapshots.projectId, projectId), eq26(gscCoverageSnapshots.date, snapshotDate))).run();
21094
21320
  db.insert(gscCoverageSnapshots).values({
21095
- id: crypto23.randomUUID(),
21321
+ id: crypto24.randomUUID(),
21096
21322
  projectId,
21097
21323
  syncRunId: runId,
21098
21324
  date: snapshotDate,
@@ -21113,7 +21339,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
21113
21339
  }
21114
21340
 
21115
21341
  // src/bing-inspect-sitemap.ts
21116
- import crypto24 from "crypto";
21342
+ import crypto25 from "crypto";
21117
21343
  import { eq as eq27, desc as desc12 } from "drizzle-orm";
21118
21344
  var log5 = createLogger("BingInspectSitemap");
21119
21345
  function parseBingDate2(value) {
@@ -21201,7 +21427,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
21201
21427
  derivedInIndex = false;
21202
21428
  }
21203
21429
  db.insert(bingUrlInspections).values({
21204
- id: crypto24.randomUUID(),
21430
+ id: crypto25.randomUUID(),
21205
21431
  projectId,
21206
21432
  url: pageUrl,
21207
21433
  httpCode,
@@ -21259,7 +21485,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
21259
21485
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
21260
21486
  const snapNow = (/* @__PURE__ */ new Date()).toISOString();
21261
21487
  db.insert(bingCoverageSnapshots).values({
21262
- id: crypto24.randomUUID(),
21488
+ id: crypto25.randomUUID(),
21263
21489
  projectId,
21264
21490
  syncRunId: runId,
21265
21491
  date: snapshotDate,
@@ -21299,7 +21525,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
21299
21525
  }
21300
21526
 
21301
21527
  // src/commoncrawl-sync.ts
21302
- import crypto25 from "crypto";
21528
+ import crypto26 from "crypto";
21303
21529
  import path10 from "path";
21304
21530
  import { and as and15, eq as eq28, sql as sql10 } from "drizzle-orm";
21305
21531
  var log6 = createLogger("CommonCrawlSync");
@@ -21374,7 +21600,7 @@ async function executeReleaseSync(db, syncId, opts) {
21374
21600
  if (!projectIds) continue;
21375
21601
  for (const projectId of projectIds) {
21376
21602
  expanded.push({
21377
- id: crypto25.randomUUID(),
21603
+ id: crypto26.randomUUID(),
21378
21604
  projectId,
21379
21605
  releaseSyncId: syncId,
21380
21606
  release,
@@ -21394,7 +21620,7 @@ async function executeReleaseSync(db, syncId, opts) {
21394
21620
  const projectRows = rowsByProject.get(p.id) ?? [];
21395
21621
  const summary = computeSummary(projectRows);
21396
21622
  tx.insert(backlinkSummaries).values({
21397
- id: crypto25.randomUUID(),
21623
+ id: crypto26.randomUUID(),
21398
21624
  projectId: p.id,
21399
21625
  releaseSyncId: syncId,
21400
21626
  release,
@@ -21490,7 +21716,7 @@ function computeSummary(rows) {
21490
21716
  }
21491
21717
 
21492
21718
  // src/backlink-extract.ts
21493
- import crypto26 from "crypto";
21719
+ import crypto27 from "crypto";
21494
21720
  import fs8 from "fs";
21495
21721
  import { and as and16, desc as desc13, eq as eq29 } from "drizzle-orm";
21496
21722
  var log7 = createLogger("BacklinkExtract");
@@ -21542,7 +21768,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
21542
21768
  ).run();
21543
21769
  if (rows.length > 0) {
21544
21770
  const values = rows.map((r) => ({
21545
- id: crypto26.randomUUID(),
21771
+ id: crypto27.randomUUID(),
21546
21772
  projectId,
21547
21773
  releaseSyncId: syncId,
21548
21774
  release,
@@ -21555,7 +21781,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
21555
21781
  }
21556
21782
  const summary = computeSummary2(rows);
21557
21783
  tx.insert(backlinkSummaries).values({
21558
- id: crypto26.randomUUID(),
21784
+ id: crypto27.randomUUID(),
21559
21785
  projectId,
21560
21786
  releaseSyncId: syncId,
21561
21787
  release,
@@ -21795,7 +22021,7 @@ var Scheduler = class {
21795
22021
 
21796
22022
  // src/notifier.ts
21797
22023
  import { eq as eq31, desc as desc14, and as and17, or as or4 } from "drizzle-orm";
21798
- import crypto27 from "crypto";
22024
+ import crypto28 from "crypto";
21799
22025
  var log9 = createLogger("Notifier");
21800
22026
  var Notifier = class {
21801
22027
  db;
@@ -21977,7 +22203,7 @@ var Notifier = class {
21977
22203
  }
21978
22204
  logDelivery(projectId, notificationId, event, status, error) {
21979
22205
  this.db.insert(auditLog).values({
21980
- id: crypto27.randomUUID(),
22206
+ id: crypto28.randomUUID(),
21981
22207
  projectId,
21982
22208
  actor: "scheduler",
21983
22209
  action: `notification.${status}`,
@@ -22035,7 +22261,7 @@ var RunCoordinator = class {
22035
22261
  };
22036
22262
 
22037
22263
  // src/agent/session-registry.ts
22038
- import crypto29 from "crypto";
22264
+ import crypto30 from "crypto";
22039
22265
  import { eq as eq33 } from "drizzle-orm";
22040
22266
 
22041
22267
  // src/agent/session.ts
@@ -22385,7 +22611,7 @@ function resolveSessionProviderAndModel(config, opts) {
22385
22611
  }
22386
22612
 
22387
22613
  // src/agent/memory-store.ts
22388
- import crypto28 from "crypto";
22614
+ import crypto29 from "crypto";
22389
22615
  import { and as and18, desc as desc15, eq as eq32, like as like2, sql as sql11 } from "drizzle-orm";
22390
22616
  var COMPACTION_KEY_PREFIX = "compaction:";
22391
22617
  var COMPACTION_NOTES_PER_SESSION = 3;
@@ -22414,7 +22640,7 @@ function upsertMemoryEntry(db, args) {
22414
22640
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
22415
22641
  }
22416
22642
  const now = (/* @__PURE__ */ new Date()).toISOString();
22417
- const id = crypto28.randomUUID();
22643
+ const id = crypto29.randomUUID();
22418
22644
  db.insert(agentMemory).values({
22419
22645
  id,
22420
22646
  projectId: args.projectId,
@@ -22451,7 +22677,7 @@ function writeCompactionNote(db, args) {
22451
22677
  }
22452
22678
  const now = (/* @__PURE__ */ new Date()).toISOString();
22453
22679
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
22454
- const id = crypto28.randomUUID();
22680
+ const id = crypto29.randomUUID();
22455
22681
  let inserted;
22456
22682
  db.transaction((tx) => {
22457
22683
  tx.insert(agentMemory).values({
@@ -23042,7 +23268,7 @@ ${lines.join("\n")}
23042
23268
  insertRow(params) {
23043
23269
  const now = (/* @__PURE__ */ new Date()).toISOString();
23044
23270
  this.opts.db.insert(agentSessions).values({
23045
- id: crypto29.randomUUID(),
23271
+ id: crypto30.randomUUID(),
23046
23272
  projectId: params.projectId,
23047
23273
  systemPrompt: params.systemPrompt,
23048
23274
  modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
@@ -23959,7 +24185,7 @@ function summarizeProviderConfig(provider, config) {
23959
24185
  };
23960
24186
  }
23961
24187
  function hashApiKey(key) {
23962
- return crypto30.createHash("sha256").update(key).digest("hex");
24188
+ return crypto31.createHash("sha256").update(key).digest("hex");
23963
24189
  }
23964
24190
  function parseCookies2(header) {
23965
24191
  if (!header) return {};
@@ -24129,7 +24355,7 @@ async function createServer(opts) {
24129
24355
  );
24130
24356
  jobRunner.onRunCompleted = (runId, projectId) => runCoordinator.onRunCompleted(runId, projectId);
24131
24357
  const snapshotService = new SnapshotService(registry);
24132
- const orphanedOpenClawDir = path14.join(os5.homedir(), ".openclaw-aero");
24358
+ const orphanedOpenClawDir = path14.join(os6.homedir(), ".openclaw-aero");
24133
24359
  if (fs12.existsSync(orphanedOpenClawDir)) {
24134
24360
  app.log.warn(
24135
24361
  { path: orphanedOpenClawDir },
@@ -24226,7 +24452,7 @@ async function createServer(opts) {
24226
24452
  return removed;
24227
24453
  }
24228
24454
  };
24229
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto30.randomBytes(32).toString("hex");
24455
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto31.randomBytes(32).toString("hex");
24230
24456
  const googleConnectionStore = {
24231
24457
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
24232
24458
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -24276,7 +24502,7 @@ async function createServer(opts) {
24276
24502
  if (!existing) {
24277
24503
  const prefix = opts.config.apiKey.slice(0, 12);
24278
24504
  opts.db.insert(apiKeys).values({
24279
- id: `key_${crypto30.randomBytes(8).toString("hex")}`,
24505
+ id: `key_${crypto31.randomBytes(8).toString("hex")}`,
24280
24506
  name: "default",
24281
24507
  keyHash,
24282
24508
  keyPrefix: prefix,
@@ -24300,7 +24526,7 @@ async function createServer(opts) {
24300
24526
  };
24301
24527
  const createSession = (apiKeyId) => {
24302
24528
  pruneExpiredSessions();
24303
- const sessionId = crypto30.randomBytes(32).toString("hex");
24529
+ const sessionId = crypto31.randomBytes(32).toString("hex");
24304
24530
  sessions.set(sessionId, {
24305
24531
  apiKeyId,
24306
24532
  expiresAt: Date.now() + SESSION_TTL_MS
@@ -24496,7 +24722,7 @@ async function createServer(opts) {
24496
24722
  deps: {
24497
24723
  enqueueAutoExtract: ({ projectId, release: r }) => {
24498
24724
  const now = (/* @__PURE__ */ new Date()).toISOString();
24499
- const runId = crypto30.randomUUID();
24725
+ const runId = crypto31.randomUUID();
24500
24726
  opts.db.insert(runs).values({
24501
24727
  id: runId,
24502
24728
  projectId,
@@ -24632,7 +24858,7 @@ async function createServer(opts) {
24632
24858
  const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
24633
24859
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
24634
24860
  opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
24635
- id: crypto30.randomUUID(),
24861
+ id: crypto31.randomUUID(),
24636
24862
  projectId,
24637
24863
  actor: "api",
24638
24864
  action: existing ? "provider.updated" : "provider.created",
@@ -24875,10 +25101,12 @@ function parseQueryResponse(raw, count) {
24875
25101
  }
24876
25102
 
24877
25103
  export {
25104
+ setTelemetrySource,
24878
25105
  isTelemetryEnabled,
24879
25106
  getOrCreateAnonymousId,
24880
25107
  isFirstRun,
24881
25108
  showFirstRunNotice,
25109
+ detectAndTrackUpgrade,
24882
25110
  trackEvent,
24883
25111
  reparseStoredResult2 as reparseStoredResult,
24884
25112
  reparseStoredResult3 as reparseStoredResult2,