@ainyc/canonry 5.1.2 → 5.1.4

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.
@@ -17,7 +17,7 @@ import {
17
17
  resolveOperatorApiKeyIds,
18
18
  runWithUsageTags,
19
19
  saveConfigPatch
20
- } from "./chunk-5K2NUQOV.js";
20
+ } from "./chunk-AEE5DGTE.js";
21
21
  import {
22
22
  CC_CACHE_DIR,
23
23
  DUCKDB_SPEC,
@@ -168,7 +168,7 @@ import {
168
168
  siteCrawlSnapshots,
169
169
  toAlertView,
170
170
  usageCounters
171
- } from "./chunk-WSYJ4IT7.js";
171
+ } from "./chunk-EBUX3C5H.js";
172
172
  import {
173
173
  AGENT_MEMORY_VALUE_MAX_BYTES,
174
174
  AGENT_NONE,
@@ -179,6 +179,7 @@ import {
179
179
  AdsCampaignBiddingTypes,
180
180
  AgentProviderIds,
181
181
  BacklinkSources,
182
+ CANONRY_NPM_PACKAGE_URL,
182
183
  CITED_URL_CAPTURE_VERSION,
183
184
  CcReleaseSyncStatuses,
184
185
  DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
@@ -239,6 +240,7 @@ import {
239
240
  classifyProviderErrorMessages,
240
241
  classifyTemplateLinkEdge,
241
242
  cloudflareQueueNameSchema,
243
+ compareSemver,
242
244
  computeBacklinkSummaryMetrics,
243
245
  contentActionLabel,
244
246
  contentBriefDtoSchema,
@@ -279,9 +281,11 @@ import {
279
281
  isAgentProviderId,
280
282
  isBrowserProvider,
281
283
  isGhostTelemetryEvent,
284
+ isInstallMethod,
282
285
  isReadOnlyKey,
283
286
  isRetryableHttpError,
284
287
  isSelfLink,
288
+ isStrictSemver,
285
289
  isVertexGroundingRedirect,
286
290
  mapWithConcurrency,
287
291
  maskTelemetryAnonymousId,
@@ -313,11 +317,14 @@ import {
313
317
  templateLinkUbiquityAvailable,
314
318
  textContainsBrandAlias,
315
319
  textContainsDomain,
320
+ updateCheckEnvOptOut,
321
+ upgradeCaveatFor,
322
+ upgradeCommandFor,
316
323
  usageSurfaceSchema,
317
324
  validationError,
318
325
  winnabilityClassLabel,
319
326
  withRetry
320
- } from "./chunk-5ZIUROAQ.js";
327
+ } from "./chunk-MMSPU72Z.js";
321
328
 
322
329
  // src/runtime-context.ts
323
330
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -569,47 +576,46 @@ function deliverEvent(event, properties, options, delivery = {}) {
569
576
  }
570
577
 
571
578
  // src/update-check.ts
579
+ import fs from "fs";
572
580
  import { createRequire as createRequire2 } from "module";
581
+ import { fileURLToPath } from "url";
573
582
  var _require2 = createRequire2(import.meta.url);
574
583
  var { version: PKG_VERSION } = _require2("../package.json");
575
584
  var PKG_NAME = "@canonry/canonry";
576
585
  var NPM_DIST_TAGS_URL = `https://registry.npmjs.org/-/package/${PKG_NAME}/dist-tags`;
577
- var NPM_PACKAGE_URL = `https://www.npmjs.com/package/${PKG_NAME}`;
586
+ var NPM_PACKAGE_URL = CANONRY_NPM_PACKAGE_URL;
578
587
  var FETCH_TIMEOUT_MS = 1500;
579
- function isUpdateCheckEnabled() {
580
- if (process.env.CANONRY_DISABLE_UPDATE_CHECK === "1") return false;
581
- if (process.env.DO_NOT_TRACK === "1") return false;
582
- if (process.env.CI) return false;
583
- if (!configExists()) return true;
588
+ var CONTAINER_MARKERS = ["/.dockerenv", "/run/.containerenv"];
589
+ function detectInstallMethod(opts) {
590
+ const declared = (opts?.env ?? process.env).CANONRY_INSTALL_METHOD;
591
+ if (isInstallMethod(declared)) return declared;
592
+ let modulePath = opts?.modulePath ?? fileURLToPath(import.meta.url);
584
593
  try {
585
- const raw = loadConfigRaw();
586
- return raw?.updateCheck !== false;
594
+ modulePath = fs.realpathSync(modulePath);
587
595
  } catch {
588
- return true;
589
596
  }
597
+ if (modulePath.replace(/\\/g, "/").includes("/Cellar/canonry/")) return "homebrew";
598
+ const exists = opts?.exists ?? fs.existsSync;
599
+ if (CONTAINER_MARKERS.some((marker) => exists(marker))) return "docker";
600
+ return "npm";
590
601
  }
591
- function compareSemver(a, b) {
592
- const parse = (v) => {
593
- const core = v.split(/[-+]/)[0];
594
- if (!core) return null;
595
- const parts = core.split(".");
596
- if (parts.length < 3) return null;
597
- const nums = [];
598
- for (let i = 0; i < 3; i++) {
599
- const n = Number(parts[i]);
600
- if (!Number.isInteger(n) || n < 0) return null;
601
- nums.push(n);
602
- }
603
- return [nums[0], nums[1], nums[2]];
604
- };
605
- const pa = parse(a);
606
- const pb = parse(b);
607
- if (!pa || !pb) return 0;
608
- for (let i = 0; i < 3; i++) {
609
- if (pa[i] > pb[i]) return 1;
610
- if (pa[i] < pb[i]) return -1;
602
+ var detectedInstallMethod;
603
+ function currentInstallMethod() {
604
+ detectedInstallMethod ??= detectInstallMethod();
605
+ return detectedInstallMethod;
606
+ }
607
+ function isUpdateCheckEnabled() {
608
+ return updateCheckDisabledReason() === null;
609
+ }
610
+ function updateCheckDisabledReason() {
611
+ const envOptOut = updateCheckEnvOptOut(process.env);
612
+ if (envOptOut) return envOptOut;
613
+ if (!configExists()) return null;
614
+ try {
615
+ return loadConfigRaw()?.updateCheck === false ? "config" : null;
616
+ } catch {
617
+ return null;
611
618
  }
612
- return 0;
613
619
  }
614
620
  async function fetchLatestVersion(opts) {
615
621
  const controller = new AbortController();
@@ -622,7 +628,7 @@ async function fetchLatestVersion(opts) {
622
628
  });
623
629
  if (!res.ok) return null;
624
630
  const data = await res.json();
625
- if (typeof data.latest !== "string") return null;
631
+ if (typeof data.latest !== "string" || !isStrictSemver(data.latest)) return null;
626
632
  return data.latest;
627
633
  } catch {
628
634
  return null;
@@ -630,13 +636,15 @@ async function fetchLatestVersion(opts) {
630
636
  clearTimeout(timeout);
631
637
  }
632
638
  }
633
- function buildUpdateAvailable(current, latest) {
639
+ function buildUpdateAvailable(current, latest, installMethod = currentInstallMethod()) {
640
+ if (!isStrictSemver(latest)) return null;
634
641
  if (compareSemver(latest, current) <= 0) return null;
635
642
  return {
636
643
  current,
637
644
  latest,
638
645
  url: NPM_PACKAGE_URL,
639
- upgradeCommand: `npm install -g ${PKG_NAME}`
646
+ upgradeCommand: upgradeCommandFor(installMethod),
647
+ installMethod
640
648
  };
641
649
  }
642
650
  async function checkLatestVersionForCli(opts) {
@@ -674,6 +682,45 @@ async function checkLatestVersionForCli(opts) {
674
682
  }
675
683
  return buildUpdateAvailable(PKG_VERSION, latest);
676
684
  }
685
+ function readCachedUpdateAvailable() {
686
+ if (!isUpdateCheckEnabled()) return null;
687
+ if (!configExists()) return null;
688
+ try {
689
+ const cachedLatest = loadConfigRaw()?.lastKnownLatestVersion;
690
+ if (typeof cachedLatest !== "string") return null;
691
+ return buildUpdateAvailable(PKG_VERSION, cachedLatest);
692
+ } catch {
693
+ return null;
694
+ }
695
+ }
696
+ var UPDATE_AVAILABLE_NOTICE_CODE = "UPDATE_AVAILABLE";
697
+ function formatUpdateNotice(update, opts) {
698
+ const caveat = upgradeCaveatFor(update.installMethod);
699
+ if (opts.format === "json" || opts.format === "jsonl") {
700
+ return `${JSON.stringify({
701
+ notice: {
702
+ code: UPDATE_AVAILABLE_NOTICE_CODE,
703
+ current: update.current,
704
+ latest: update.latest,
705
+ installMethod: update.installMethod,
706
+ upgradeCommand: update.upgradeCommand,
707
+ url: update.url,
708
+ ...caveat ? { note: caveat } : {}
709
+ }
710
+ })}
711
+ `;
712
+ }
713
+ if (opts.interactive) {
714
+ return `
715
+ \u2192 canonry ${update.latest} is available (you have ${update.current}).
716
+ Upgrade: ${update.upgradeCommand}
717
+ ` + (caveat ? ` ${caveat}
718
+ ` : "") + "\n";
719
+ }
720
+ const upgrade = update.installMethod === "docker" ? `Upgrade: ${update.upgradeCommand}.` : `Upgrade with \`${update.upgradeCommand}\`, then restart any running \`canonry serve\`.`;
721
+ return `[canonry] ${UPDATE_AVAILABLE_NOTICE_CODE}: canonry ${update.latest} is available (installed ${update.current}). ${upgrade} ${caveat ? `${caveat} ` : ""}Silence with CANONRY_DISABLE_UPDATE_CHECK=1.
722
+ `;
723
+ }
677
724
  var memoryCache = null;
678
725
  var inFlight = null;
679
726
  function startBackgroundRefresh(getNow) {
@@ -700,6 +747,22 @@ function checkLatestVersionForServer(opts) {
700
747
  if (!memoryCache || !memoryCache.latest) return null;
701
748
  return buildUpdateAvailable(PKG_VERSION, memoryCache.latest);
702
749
  }
750
+ function getServerUpdateStatus(opts) {
751
+ const installMethod = currentInstallMethod();
752
+ const base = { current: PKG_VERSION, installMethod, upgradeCommand: upgradeCommandFor(installMethod), url: NPM_PACKAGE_URL };
753
+ const disabledBy = updateCheckDisabledReason();
754
+ if (disabledBy) return { ...base, enabled: false, disabledBy, latest: null };
755
+ checkLatestVersionForServer(opts);
756
+ let latest = memoryCache?.latest ?? null;
757
+ if (!latest && configExists()) {
758
+ try {
759
+ const cached = loadConfigRaw()?.lastKnownLatestVersion;
760
+ if (typeof cached === "string") latest = cached;
761
+ } catch {
762
+ }
763
+ }
764
+ return { ...base, enabled: true, latest: latest && isStrictSemver(latest) ? latest : null };
765
+ }
703
766
 
704
767
  // src/google-marketing-runtime.ts
705
768
  import crypto3 from "crypto";
@@ -3470,9 +3533,9 @@ function createGoogleMarketingRuntime(options) {
3470
3533
  // src/server.ts
3471
3534
  import { createRequire as createRequire4 } from "module";
3472
3535
  import crypto27 from "crypto";
3473
- import fs7 from "fs";
3536
+ import fs8 from "fs";
3474
3537
  import path8 from "path";
3475
- import { fileURLToPath as fileURLToPath2 } from "url";
3538
+ import { fileURLToPath as fileURLToPath3 } from "url";
3476
3539
  import { and as and20, eq as eq26 } from "drizzle-orm";
3477
3540
  import Fastify from "fastify";
3478
3541
  import os4 from "os";
@@ -5393,12 +5456,12 @@ function sleep2(ms) {
5393
5456
  }
5394
5457
 
5395
5458
  // ../provider-cdp/src/screenshot.ts
5396
- import fs from "fs";
5459
+ import fs2 from "fs";
5397
5460
  import path from "path";
5398
5461
  async function captureElementScreenshot(client, selector, outputPath) {
5399
5462
  const dir = path.dirname(outputPath);
5400
- if (!fs.existsSync(dir)) {
5401
- fs.mkdirSync(dir, { recursive: true });
5463
+ if (!fs2.existsSync(dir)) {
5464
+ fs2.mkdirSync(dir, { recursive: true });
5402
5465
  }
5403
5466
  let clip;
5404
5467
  try {
@@ -5432,7 +5495,7 @@ async function captureElementScreenshot(client, selector, outputPath) {
5432
5495
  }
5433
5496
  const { data } = await client.Page.captureScreenshot(screenshotParams);
5434
5497
  const buffer = Buffer.from(data, "base64");
5435
- fs.writeFileSync(outputPath, buffer);
5498
+ fs2.writeFileSync(outputPath, buffer);
5436
5499
  return outputPath;
5437
5500
  }
5438
5501
 
@@ -6393,7 +6456,7 @@ function nonBlank(value) {
6393
6456
  return trimmed ? trimmed : void 0;
6394
6457
  }
6395
6458
  function resolveBuildCommit(env = process.env) {
6396
- const embedded = true ? "267b6410fa27a2bf032d4957047859e1f9eee6d1" : void 0;
6459
+ const embedded = true ? "56f0f163c861a247687db8c2087c41218f8ee982" : void 0;
6397
6460
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6398
6461
  }
6399
6462
  function resolveInstanceIdentity(env = process.env) {
@@ -6405,7 +6468,7 @@ function resolveInstanceIdentity(env = process.env) {
6405
6468
 
6406
6469
  // src/job-runner.ts
6407
6470
  import crypto6 from "crypto";
6408
- import fs2 from "fs";
6471
+ import fs3 from "fs";
6409
6472
  import path3 from "path";
6410
6473
  import os3 from "os";
6411
6474
  import { and as and2, eq as eq2, inArray, ne, sql as sql2 } from "drizzle-orm";
@@ -6973,12 +7036,12 @@ var JobRunner = class {
6973
7036
  allBrandNames
6974
7037
  );
6975
7038
  let screenshotRelPath = null;
6976
- if (raw.screenshotPath && fs2.existsSync(raw.screenshotPath)) {
7039
+ if (raw.screenshotPath && fs3.existsSync(raw.screenshotPath)) {
6977
7040
  const snapshotId = crypto6.randomUUID();
6978
7041
  const screenshotDir = path3.join(os3.homedir(), ".canonry", "screenshots", runId);
6979
- if (!fs2.existsSync(screenshotDir)) fs2.mkdirSync(screenshotDir, { recursive: true });
7042
+ if (!fs3.existsSync(screenshotDir)) fs3.mkdirSync(screenshotDir, { recursive: true });
6980
7043
  const destPath = path3.join(screenshotDir, `${snapshotId}.png`);
6981
- fs2.renameSync(raw.screenshotPath, destPath);
7044
+ fs3.renameSync(raw.screenshotPath, destPath);
6982
7045
  screenshotRelPath = `${runId}/${snapshotId}.png`;
6983
7046
  this.db.insert(querySnapshots).values({
6984
7047
  id: snapshotId,
@@ -7131,11 +7194,11 @@ var JobRunner = class {
7131
7194
  );
7132
7195
  const snapshotId = crypto6.randomUUID();
7133
7196
  let screenshotRelPath = null;
7134
- if (raw.screenshotPath && fs2.existsSync(raw.screenshotPath)) {
7197
+ if (raw.screenshotPath && fs3.existsSync(raw.screenshotPath)) {
7135
7198
  const screenshotDir = path3.join(os3.homedir(), ".canonry", "screenshots", runId);
7136
- if (!fs2.existsSync(screenshotDir)) fs2.mkdirSync(screenshotDir, { recursive: true });
7199
+ if (!fs3.existsSync(screenshotDir)) fs3.mkdirSync(screenshotDir, { recursive: true });
7137
7200
  const destPath = path3.join(screenshotDir, `${snapshotId}.png`);
7138
- fs2.renameSync(raw.screenshotPath, destPath);
7201
+ fs3.renameSync(raw.screenshotPath, destPath);
7139
7202
  screenshotRelPath = `${runId}/${snapshotId}.png`;
7140
7203
  }
7141
7204
  this.db.insert(querySnapshots).values({
@@ -9695,7 +9758,7 @@ function computeSummary(rows) {
9695
9758
 
9696
9759
  // src/backlink-extract.ts
9697
9760
  import crypto16 from "crypto";
9698
- import fs3 from "fs";
9761
+ import fs4 from "fs";
9699
9762
  import { and as and9, desc as desc5, eq as eq11 } from "drizzle-orm";
9700
9763
  var log11 = createLogger("BacklinkExtract");
9701
9764
  function defaultDeps3() {
@@ -9724,7 +9787,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
9724
9787
  if (!sync.vertexPath || !sync.edgesPath) {
9725
9788
  throw new Error(`Release ${sync.release} is missing cached file paths`);
9726
9789
  }
9727
- if (!fs3.existsSync(sync.vertexPath) || !fs3.existsSync(sync.edgesPath)) {
9790
+ if (!fs4.existsSync(sync.vertexPath) || !fs4.existsSync(sync.edgesPath)) {
9728
9791
  throw new Error(
9729
9792
  `Cache for release ${sync.release} is missing from disk (expected at ${sync.vertexPath}). The sync record exists in the database, but the ~16 GB dump was deleted or never present on this machine. Re-sync this release from the Backlinks admin page to restore the cache.`
9730
9793
  );
@@ -12319,7 +12382,7 @@ function readStoredGroundingSources(rawResponse) {
12319
12382
  return result;
12320
12383
  }
12321
12384
  async function backfillInsightsCommand(project, opts) {
12322
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-D2HY2S5F.js");
12385
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-25NQCETC.js");
12323
12386
  const config = loadConfig();
12324
12387
  const db = createClient(config.database);
12325
12388
  migrate(db);
@@ -14054,7 +14117,7 @@ import crypto25 from "crypto";
14054
14117
  import { eq as eq21 } from "drizzle-orm";
14055
14118
 
14056
14119
  // src/agent/session.ts
14057
- import fs6 from "fs";
14120
+ import fs7 from "fs";
14058
14121
  import path7 from "path";
14059
14122
  import { Agent } from "@mariozechner/pi-agent-core";
14060
14123
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
@@ -14298,25 +14361,25 @@ function buildAgentProvidersResponse(config) {
14298
14361
  }
14299
14362
 
14300
14363
  // src/agent/skill-paths.ts
14301
- import fs4 from "fs";
14364
+ import fs5 from "fs";
14302
14365
  import path5 from "path";
14303
- import { fileURLToPath } from "url";
14366
+ import { fileURLToPath as fileURLToPath2 } from "url";
14304
14367
  function resolveAeroSkillDir(pkgDir) {
14305
- const here = pkgDir ?? path5.dirname(fileURLToPath(import.meta.url));
14368
+ const here = pkgDir ?? path5.dirname(fileURLToPath2(import.meta.url));
14306
14369
  const candidates = [
14307
14370
  path5.join(here, "../assets/agent-workspace/skills/aero"),
14308
14371
  path5.join(here, "../../assets/agent-workspace/skills/aero"),
14309
14372
  path5.join(here, "../../../../skills/aero")
14310
14373
  ];
14311
14374
  for (const candidate of candidates) {
14312
- if (fs4.existsSync(path5.join(candidate, "SKILL.md"))) return candidate;
14375
+ if (fs5.existsSync(path5.join(candidate, "SKILL.md"))) return candidate;
14313
14376
  }
14314
14377
  throw new Error(`Aero skill not found. Searched:
14315
14378
  ${candidates.join("\n ")}`);
14316
14379
  }
14317
14380
 
14318
14381
  // src/agent/skill-tools.ts
14319
- import fs5 from "fs";
14382
+ import fs6 from "fs";
14320
14383
  import path6 from "path";
14321
14384
  import { Type } from "@sinclair/typebox";
14322
14385
  var MAX_DOC_CHARS = 2e4;
@@ -14339,12 +14402,12 @@ function parseDescription(body) {
14339
14402
  }
14340
14403
  function scanSkillDocs(skillDir) {
14341
14404
  const refsDir = path6.join(skillDir ?? resolveAeroSkillDir(), "references");
14342
- if (!fs5.existsSync(refsDir)) return [];
14405
+ if (!fs6.existsSync(refsDir)) return [];
14343
14406
  const entries = [];
14344
- for (const file of fs5.readdirSync(refsDir)) {
14407
+ for (const file of fs6.readdirSync(refsDir)) {
14345
14408
  if (!file.endsWith(".md")) continue;
14346
14409
  const filePath = path6.join(refsDir, file);
14347
- const body = fs5.readFileSync(filePath, "utf-8");
14410
+ const body = fs6.readFileSync(filePath, "utf-8");
14348
14411
  entries.push({
14349
14412
  slug: file.replace(/\.md$/, ""),
14350
14413
  description: parseDescription(body),
@@ -14388,7 +14451,7 @@ function buildReadSkillDocTool() {
14388
14451
  });
14389
14452
  }
14390
14453
  const filePath = path6.join(skillDir, "references", `${match.slug}.md`);
14391
- const content = fs5.readFileSync(filePath, "utf-8");
14454
+ const content = fs6.readFileSync(filePath, "utf-8");
14392
14455
  if (content.length > MAX_DOC_CHARS) {
14393
14456
  return textResult({
14394
14457
  slug: match.slug,
@@ -15049,9 +15112,9 @@ function ensureBuiltinsRegistered() {
15049
15112
  }
15050
15113
  function loadAeroSystemPrompt(pkgDir) {
15051
15114
  const skillDir = resolveAeroSkillDir(pkgDir);
15052
- const skillBody = fs6.readFileSync(path7.join(skillDir, "SKILL.md"), "utf-8");
15115
+ const skillBody = fs7.readFileSync(path7.join(skillDir, "SKILL.md"), "utf-8");
15053
15116
  const soulPath = path7.join(skillDir, "soul.md");
15054
- const base = fs6.existsSync(soulPath) ? `${fs6.readFileSync(soulPath, "utf-8").trimEnd()}
15117
+ const base = fs7.existsSync(soulPath) ? `${fs7.readFileSync(soulPath, "utf-8").trimEnd()}
15055
15118
 
15056
15119
  ---
15057
15120
 
@@ -15064,7 +15127,7 @@ function appendSystemPromptExtras(base, env = process.env) {
15064
15127
  const filePath = env.AERO_SYSTEM_PROMPT_FILE?.trim();
15065
15128
  if (filePath) {
15066
15129
  try {
15067
- fileBody = fs6.readFileSync(filePath, "utf-8").trim();
15130
+ fileBody = fs7.readFileSync(filePath, "utf-8").trim();
15068
15131
  } catch {
15069
15132
  fileBody = "";
15070
15133
  }
@@ -15551,9 +15614,13 @@ var SessionRegistry = class {
15551
15614
  if (cached) return cached;
15552
15615
  const projectId = this.resolveProjectId(projectName);
15553
15616
  const row = this.loadRow(projectId);
15617
+ const systemPrompt = loadAeroSystemPrompt();
15554
15618
  if (row) {
15555
15619
  const persistedMessages = parseJsonColumn(row.messages, []);
15556
15620
  const queued = parseJsonColumn(row.followUpQueue, []);
15621
+ if (row.systemPrompt !== systemPrompt) {
15622
+ this.persistPromptSnapshot(projectId, systemPrompt);
15623
+ }
15557
15624
  const effectiveProvider = preferences?.provider ?? row.modelProvider;
15558
15625
  const effectiveModelId = preferences?.modelId ?? row.modelId;
15559
15626
  if (preferences?.provider || preferences?.modelId) {
@@ -15569,7 +15636,7 @@ var SessionRegistry = class {
15569
15636
  config: this.opts.config,
15570
15637
  provider: effectiveProvider,
15571
15638
  modelId: effectiveModelId,
15572
- systemPromptOverride: this.buildHydratedSystemPrompt(projectId, row.systemPrompt),
15639
+ systemPromptOverride: this.buildHydratedSystemPrompt(projectId, systemPrompt),
15573
15640
  initialMessages: persistedMessages,
15574
15641
  toolScope: preferences?.toolScope,
15575
15642
  toolProfile: preferences?.toolProfile,
@@ -15589,7 +15656,6 @@ var SessionRegistry = class {
15589
15656
  return agent2;
15590
15657
  }
15591
15658
  const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
15592
- const systemPrompt = loadAeroSystemPrompt();
15593
15659
  const sessionId = crypto25.randomUUID();
15594
15660
  const agent = createAeroSession({
15595
15661
  projectName,
@@ -15612,7 +15678,7 @@ var SessionRegistry = class {
15612
15678
  this.insertRow({
15613
15679
  id: sessionId,
15614
15680
  projectId,
15615
- // Persist the raw (unhydrated) prompt so the DB remains canonical
15681
+ // Persist the raw (unhydrated) installed prompt snapshot
15616
15682
  // the `<memory>` block is rebuilt from the notes table on every load.
15617
15683
  systemPrompt,
15618
15684
  modelProvider: provider,
@@ -15702,6 +15768,13 @@ ${lines.join("\n")}
15702
15768
  if (agent.state.isStreaming) {
15703
15769
  throw agentBusy(projectName);
15704
15770
  }
15771
+ const projectId = this.resolveProjectId(projectName);
15772
+ const row = this.loadRow(projectId);
15773
+ const systemPrompt = loadAeroSystemPrompt();
15774
+ if (row && row.systemPrompt !== systemPrompt) {
15775
+ this.persistPromptSnapshot(projectId, systemPrompt);
15776
+ agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt);
15777
+ }
15705
15778
  this.alignToolSurface(projectName, agent, {
15706
15779
  scope: preferences?.toolScope ?? AeroToolScopes.all,
15707
15780
  profile: preferences?.toolProfile ?? AeroToolProfiles.default
@@ -15984,6 +16057,15 @@ ${lines.join("\n")}
15984
16057
  const now = (/* @__PURE__ */ new Date()).toISOString();
15985
16058
  this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq21(agentSessions.projectId, projectId)).run();
15986
16059
  }
16060
+ /**
16061
+ * Store a refreshed prompt snapshot without touching `updatedAt`, which the
16062
+ * transcript API reports as the conversation's last activity. Adopting a new
16063
+ * bundled skill is not activity, so it must not make every session look
16064
+ * recently used right after an upgrade.
16065
+ */
16066
+ persistPromptSnapshot(projectId, systemPrompt) {
16067
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq21(agentSessions.projectId, projectId)).run();
16068
+ }
15987
16069
  };
15988
16070
 
15989
16071
  // src/mcp-http.ts
@@ -16088,7 +16170,8 @@ function registerMcpHttpRoutes(scope, opts) {
16088
16170
  credentialScopes: scopes,
16089
16171
  operator: request.operatorAccess === true,
16090
16172
  tiers: segment.tiers,
16091
- clientFactory: () => client
16173
+ clientFactory: () => client,
16174
+ updateAvailable: opts.getUpdateAvailable
16092
16175
  });
16093
16176
  } catch (error) {
16094
16177
  if (sessionKey) revokeSessionKey(opts.db, sessionKey.id);
@@ -18141,7 +18224,7 @@ async function createServer(opts) {
18141
18224
  });
18142
18225
  };
18143
18226
  const orphanedOpenClawDir = path8.join(os4.homedir(), ".openclaw-aero");
18144
- if (fs7.existsSync(orphanedOpenClawDir)) {
18227
+ if (fs8.existsSync(orphanedOpenClawDir)) {
18145
18228
  app.log.warn(
18146
18229
  { path: orphanedOpenClawDir },
18147
18230
  "OpenClaw gateway is no longer used. Remove ~/.openclaw-aero/ manually to reclaim the directory."
@@ -19051,7 +19134,7 @@ async function createServer(opts) {
19051
19134
  const configPath = getConfigPath();
19052
19135
  return {
19053
19136
  databasePath: opts.config.database,
19054
- configPath: fs7.existsSync(configPath) ? configPath : null
19137
+ configPath: fs8.existsSync(configPath) ? configPath : null
19055
19138
  };
19056
19139
  })(),
19057
19140
  // Snapshot the bundled skill trees (version + file hashes) so the
@@ -19066,6 +19149,8 @@ async function createServer(opts) {
19066
19149
  }
19067
19150
  })(),
19068
19151
  getAgentPluginState: opts.getAgentPluginState,
19152
+ // Powers the `canonry.version.current` doctor check. Non-blocking.
19153
+ getUpdateStatus: () => getServerUpdateStatus(),
19069
19154
  // Local canonry serve runs on the operator's machine, where pointing a
19070
19155
  // webhook at localhost (Discord test container, Pipedream-mock dev server,
19071
19156
  // etc.) is a legitimate workflow. Default to allowing it for the local
@@ -19084,7 +19169,13 @@ async function createServer(opts) {
19084
19169
  credentials: credentialChecker,
19085
19170
  oauthResourceUrl: publicOrigin ? `${publicOrigin}${`${basePath ?? "/"}api/v1/mcp`.replace("//", "/")}` : void 0,
19086
19171
  registerAuthenticatedRoutes: async (scope) => {
19087
- registerMcpHttpRoutes(scope, { selfApiUrl: opts.config.apiUrl, issuer: publicOrigin, db: opts.db });
19172
+ registerMcpHttpRoutes(scope, {
19173
+ selfApiUrl: opts.config.apiUrl,
19174
+ issuer: publicOrigin,
19175
+ db: opts.db,
19176
+ // Same non-blocking cache as /health, so hosted agents see the notice too.
19177
+ getUpdateAvailable: () => checkLatestVersionForServer()
19178
+ });
19088
19179
  registerOAuthAdminRoutes(scope, { db: opts.db });
19089
19180
  if (!sessionRegistry) return;
19090
19181
  registerAgentRoutes(scope, { db: opts.db, sessionRegistry });
@@ -19627,9 +19718,9 @@ async function createServer(opts) {
19627
19718
  return snapshotService.createReport(input);
19628
19719
  }
19629
19720
  });
19630
- const dirname = path8.dirname(fileURLToPath2(import.meta.url));
19721
+ const dirname = path8.dirname(fileURLToPath3(import.meta.url));
19631
19722
  const assetsDir = opts.assetsDir ?? path8.join(dirname, "..", "assets");
19632
- if (fs7.existsSync(assetsDir)) {
19723
+ if (fs8.existsSync(assetsDir)) {
19633
19724
  const indexPath = path8.join(assetsDir, "index.html");
19634
19725
  const injectConfig = (html, projectTabsOverride, themeOverride, renderTokenOverride) => {
19635
19726
  const clientConfig = {};
@@ -19703,8 +19794,8 @@ async function createServer(opts) {
19703
19794
  }
19704
19795
  });
19705
19796
  const serveIndex = (_request, reply) => {
19706
- if (fs7.existsSync(indexPath)) {
19707
- const html = fs7.readFileSync(indexPath, "utf-8");
19797
+ if (fs8.existsSync(indexPath)) {
19798
+ const html = fs8.readFileSync(indexPath, "utf-8");
19708
19799
  return sendSpaDocument(reply, html);
19709
19800
  }
19710
19801
  return reply.status(404).send({ error: "Dashboard not built" });
@@ -19735,8 +19826,8 @@ async function createServer(opts) {
19735
19826
  if (basePath && !url.startsWith(basePath)) {
19736
19827
  return reply.status(404).send({ error: "Not found", path: request.url });
19737
19828
  }
19738
- if (fs7.existsSync(indexPath)) {
19739
- const html = fs7.readFileSync(indexPath, "utf-8");
19829
+ if (fs8.existsSync(indexPath)) {
19830
+ const html = fs8.readFileSync(indexPath, "utf-8");
19740
19831
  return sendSpaDocument(reply, html);
19741
19832
  }
19742
19833
  return reply.status(404).send({ error: "Not found" });
@@ -19900,6 +19991,8 @@ export {
19900
19991
  setGoogleAuthConfig,
19901
19992
  formatAuditFactorScore,
19902
19993
  checkLatestVersionForCli,
19994
+ readCachedUpdateAvailable,
19995
+ formatUpdateNotice,
19903
19996
  listAgentProviders,
19904
19997
  coerceAgentProvider,
19905
19998
  AERO_TOOL_PROFILES,