@ainyc/canonry 4.188.5 → 4.189.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.
- package/assets/assets/{AuditHistoryPanel-BidWghRA.js → AuditHistoryPanel-CZ7ygQfy.js} +1 -1
- package/assets/assets/{BacklinksPage-BnD3QJGh.js → BacklinksPage-CYEYxjE8.js} +1 -1
- package/assets/assets/{HistoryPage-gIPJzBFJ.js → HistoryPage-CvAllKzD.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BFrE3x1E.js → MeasurementPropertyPage-BED9ak5a.js} +1 -1
- package/assets/assets/ProjectPage-Cgl5v16o.js +9 -0
- package/assets/assets/{RunRow-Q3ZUbaPe.js → RunRow-DUlFaUBp.js} +1 -1
- package/assets/assets/{RunsPage-Comm-TNN.js → RunsPage-BcbBe_aQ.js} +1 -1
- package/assets/assets/{SettingsPage-BCXBuXkb.js → SettingsPage-gCyOuoD_.js} +1 -1
- package/assets/assets/{SiteHealthSection-Pzebrg1V.js → SiteHealthSection-DJfFmT3n.js} +3 -3
- package/assets/assets/{TrafficPage-BDfDAaiV.js → TrafficPage-2ATExz7V.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-Da9rSFpW.js → TrafficSourceDetailPage-0PBMk9UH.js} +1 -1
- package/assets/assets/{extract-error-message-QpSUO14Z.js → extract-error-message-D8N7K1pb.js} +1 -1
- package/assets/assets/index-Bhwxe6W_.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-5G7c_8Fy.js → react-sigma_core.esm.min-CU1UA6VY.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-3HZELBZZ.js → chunk-CDI3YR57.js} +2587 -2569
- package/dist/{chunk-3WSOY5VU.js → chunk-HG6NLN7H.js} +327 -28
- package/dist/{chunk-YI7PHVUQ.js → chunk-J533DKYE.js} +2 -2
- package/dist/{chunk-AC3ZIRHR.js → chunk-QQLMRABM.js} +645 -502
- package/dist/{chunk-RYKQLQPS.js → chunk-YDYB3H4O.js} +1 -1
- package/dist/cli.js +48 -22
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-GMG2S55U.js → intelligence-service-M5TYY4TH.js} +2 -2
- package/dist/mcp.js +3 -3
- package/package.json +12 -12
- package/assets/assets/ProjectPage-DjxZiTRe.js +0 -9
- package/assets/assets/index-_RSAg-e8.js +0 -86
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
loadConfig,
|
|
15
15
|
loadConfigRaw,
|
|
16
16
|
saveConfigPatch
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-YDYB3H4O.js";
|
|
18
18
|
import {
|
|
19
19
|
CC_CACHE_DIR,
|
|
20
20
|
DUCKDB_SPEC,
|
|
@@ -120,6 +120,7 @@ import {
|
|
|
120
120
|
measurementRunCompleteness,
|
|
121
121
|
migrate,
|
|
122
122
|
nextRunFromCron,
|
|
123
|
+
nextRunFromSchedule,
|
|
123
124
|
notifications,
|
|
124
125
|
parseCookieHeader,
|
|
125
126
|
parseJsonColumn,
|
|
@@ -161,7 +162,7 @@ import {
|
|
|
161
162
|
siteCrawlSnapshots,
|
|
162
163
|
toAlertView,
|
|
163
164
|
usageCounters
|
|
164
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-QQLMRABM.js";
|
|
165
166
|
import {
|
|
166
167
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
167
168
|
AGENT_PROVIDER_IDS,
|
|
@@ -223,6 +224,7 @@ import {
|
|
|
223
224
|
bucketOnboardingCount,
|
|
224
225
|
buildRunErrorFromMessages,
|
|
225
226
|
buildSimpleMeasurementDefinition,
|
|
227
|
+
calendarRecurrenceSchema,
|
|
226
228
|
canonicalizeGtmAccountId,
|
|
227
229
|
canonicalizeGtmContainerId,
|
|
228
230
|
canonicalizeGtmResourceSelection,
|
|
@@ -303,7 +305,7 @@ import {
|
|
|
303
305
|
validationError,
|
|
304
306
|
winnabilityClassLabel,
|
|
305
307
|
withRetry
|
|
306
|
-
} from "./chunk-
|
|
308
|
+
} from "./chunk-CDI3YR57.js";
|
|
307
309
|
|
|
308
310
|
// src/telemetry.ts
|
|
309
311
|
import crypto from "crypto";
|
|
@@ -3760,6 +3762,35 @@ function createGeminiEmbedClient(apiKey, baseUrl) {
|
|
|
3760
3762
|
};
|
|
3761
3763
|
}
|
|
3762
3764
|
|
|
3765
|
+
// ../provider-gemini/src/list-models.ts
|
|
3766
|
+
function listModels(config, signal) {
|
|
3767
|
+
return withRetry(async () => {
|
|
3768
|
+
signal.throwIfAborted();
|
|
3769
|
+
const client = createClient2(config);
|
|
3770
|
+
const page = await client.models.list({ config: { pageSize: 100, queryBase: true, abortSignal: signal, httpOptions: { timeout: 2500 } } });
|
|
3771
|
+
const models = [];
|
|
3772
|
+
let seen = 0;
|
|
3773
|
+
for await (const model of page) {
|
|
3774
|
+
signal.throwIfAborted();
|
|
3775
|
+
if (++seen > 1e3) throw new Error("Model catalog exceeded the discovery limit");
|
|
3776
|
+
const id = model.name?.replace(/^(?:publishers\/google\/)?models\//, "");
|
|
3777
|
+
if (!id?.startsWith("gemini-") || /image|tts|audio|live|embedding/.test(id)) continue;
|
|
3778
|
+
if (model.supportedActions && !model.supportedActions.includes("generateContent")) continue;
|
|
3779
|
+
models.push({ id, displayName: model.displayName || id, tier: "standard" });
|
|
3780
|
+
}
|
|
3781
|
+
return models;
|
|
3782
|
+
}, {
|
|
3783
|
+
maxRetries: 1,
|
|
3784
|
+
baseDelayMs: 200,
|
|
3785
|
+
isRetryable: (error) => !signal.aborted && isRetryableHttpError(error),
|
|
3786
|
+
computeDelayMs: (_attempt, error, delay) => {
|
|
3787
|
+
const retryDelay = retryAfterDelayMs(error) ?? delay;
|
|
3788
|
+
if (retryDelay >= 2500) throw error;
|
|
3789
|
+
return retryDelay;
|
|
3790
|
+
}
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3763
3794
|
// ../provider-gemini/src/adapter.ts
|
|
3764
3795
|
function toGeminiConfig(config) {
|
|
3765
3796
|
return {
|
|
@@ -3791,6 +3822,7 @@ var geminiAdapter = {
|
|
|
3791
3822
|
{ id: "gemini-2.0-flash", displayName: "Gemini 2.0 Flash", tier: "standard" }
|
|
3792
3823
|
]
|
|
3793
3824
|
},
|
|
3825
|
+
listModels: (config, signal) => listModels(toGeminiConfig(config), signal),
|
|
3794
3826
|
validateConfig(config) {
|
|
3795
3827
|
const result = validateConfig(toGeminiConfig(config));
|
|
3796
3828
|
return {
|
|
@@ -4113,6 +4145,35 @@ function responseToRecord2(response) {
|
|
|
4113
4145
|
}
|
|
4114
4146
|
}
|
|
4115
4147
|
|
|
4148
|
+
// ../provider-openai/src/list-models.ts
|
|
4149
|
+
import OpenAI2 from "openai";
|
|
4150
|
+
function listModels2(config, signal) {
|
|
4151
|
+
return withRetry(async () => {
|
|
4152
|
+
signal.throwIfAborted();
|
|
4153
|
+
const client = new OpenAI2({ apiKey: config.apiKey, baseURL: config.baseUrl, maxRetries: 0, timeout: 2500 });
|
|
4154
|
+
const models = [];
|
|
4155
|
+
const page = client.models.list({ signal });
|
|
4156
|
+
let seen = 0;
|
|
4157
|
+
for await (const model of page) {
|
|
4158
|
+
signal.throwIfAborted();
|
|
4159
|
+
if (++seen > 1e3) throw new Error("Model catalog exceeded the discovery limit");
|
|
4160
|
+
if (!/^(?:gpt-|o\d|chat-)/.test(model.id) || /audio|realtime|transcrib|tts|image|codex|search|instruct/.test(model.id)) continue;
|
|
4161
|
+
if (/^gpt-(?:3\.5|4)(?:-|$)/.test(model.id)) continue;
|
|
4162
|
+
models.push({ id: model.id, displayName: model.id, tier: "standard" });
|
|
4163
|
+
}
|
|
4164
|
+
return models;
|
|
4165
|
+
}, {
|
|
4166
|
+
maxRetries: 1,
|
|
4167
|
+
baseDelayMs: 200,
|
|
4168
|
+
isRetryable: (error) => !signal.aborted && isRetryableHttpError(error),
|
|
4169
|
+
computeDelayMs: (_attempt, error, delay) => {
|
|
4170
|
+
const retryDelay = retryAfterDelayMs(error) ?? delay;
|
|
4171
|
+
if (retryDelay >= 2500) throw error;
|
|
4172
|
+
return retryDelay;
|
|
4173
|
+
}
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4116
4177
|
// ../provider-openai/src/adapter.ts
|
|
4117
4178
|
function toOpenAIConfig(config) {
|
|
4118
4179
|
return {
|
|
@@ -4143,6 +4204,7 @@ var openaiAdapter = {
|
|
|
4143
4204
|
{ id: "gpt-4.1", displayName: "GPT-4.1", tier: "standard" }
|
|
4144
4205
|
]
|
|
4145
4206
|
},
|
|
4207
|
+
listModels: listModels2,
|
|
4146
4208
|
validateConfig(config) {
|
|
4147
4209
|
const result = validateConfig2(toOpenAIConfig(config));
|
|
4148
4210
|
return {
|
|
@@ -4507,6 +4569,33 @@ function responseToRecord3(response) {
|
|
|
4507
4569
|
}
|
|
4508
4570
|
}
|
|
4509
4571
|
|
|
4572
|
+
// ../provider-claude/src/list-models.ts
|
|
4573
|
+
import Anthropic2 from "@anthropic-ai/sdk";
|
|
4574
|
+
function listModels3(config, signal) {
|
|
4575
|
+
return withRetry(async () => {
|
|
4576
|
+
signal.throwIfAborted();
|
|
4577
|
+
const client = new Anthropic2({ apiKey: config.apiKey, baseURL: config.baseUrl, maxRetries: 0, timeout: 2500 });
|
|
4578
|
+
const models = [];
|
|
4579
|
+
const page = client.models.list({ limit: 100 }, { signal });
|
|
4580
|
+
let seen = 0;
|
|
4581
|
+
for await (const model of page) {
|
|
4582
|
+
signal.throwIfAborted();
|
|
4583
|
+
if (++seen > 1e3) throw new Error("Model catalog exceeded the discovery limit");
|
|
4584
|
+
models.push({ id: model.id, displayName: model.display_name, tier: "standard" });
|
|
4585
|
+
}
|
|
4586
|
+
return models;
|
|
4587
|
+
}, {
|
|
4588
|
+
maxRetries: 1,
|
|
4589
|
+
baseDelayMs: 200,
|
|
4590
|
+
isRetryable: (error) => !signal.aborted && isRetryableHttpError(error),
|
|
4591
|
+
computeDelayMs: (_attempt, error, delay) => {
|
|
4592
|
+
const retryDelay = retryAfterDelayMs(error) ?? delay;
|
|
4593
|
+
if (retryDelay >= 2500) throw error;
|
|
4594
|
+
return retryDelay;
|
|
4595
|
+
}
|
|
4596
|
+
});
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4510
4599
|
// ../provider-claude/src/adapter.ts
|
|
4511
4600
|
function toClaudeConfig(config) {
|
|
4512
4601
|
return {
|
|
@@ -4533,6 +4622,7 @@ var claudeAdapter = {
|
|
|
4533
4622
|
{ id: "claude-haiku-4-5", displayName: "Claude Haiku 4.5", tier: "fast" }
|
|
4534
4623
|
]
|
|
4535
4624
|
},
|
|
4625
|
+
listModels: listModels3,
|
|
4536
4626
|
validateConfig(config) {
|
|
4537
4627
|
const result = validateConfig3(toClaudeConfig(config));
|
|
4538
4628
|
return {
|
|
@@ -4599,7 +4689,7 @@ var claudeAdapter = {
|
|
|
4599
4689
|
};
|
|
4600
4690
|
|
|
4601
4691
|
// ../provider-local/src/normalize.ts
|
|
4602
|
-
import
|
|
4692
|
+
import OpenAI3 from "openai";
|
|
4603
4693
|
|
|
4604
4694
|
// ../provider-local/src/utils.ts
|
|
4605
4695
|
async function withRetry5(fn, options = {}) {
|
|
@@ -4637,7 +4727,7 @@ async function healthcheck4(config) {
|
|
|
4637
4727
|
const validation = validateConfig4(config);
|
|
4638
4728
|
if (!validation.ok) return validation;
|
|
4639
4729
|
try {
|
|
4640
|
-
const client = new
|
|
4730
|
+
const client = new OpenAI3({
|
|
4641
4731
|
baseURL: config.baseUrl,
|
|
4642
4732
|
apiKey: config.apiKey || "not-needed"
|
|
4643
4733
|
});
|
|
@@ -4667,7 +4757,7 @@ async function healthcheck4(config) {
|
|
|
4667
4757
|
}
|
|
4668
4758
|
async function executeTrackedQuery4(input) {
|
|
4669
4759
|
const model = input.config.model ?? DEFAULT_MODEL4;
|
|
4670
|
-
const client = new
|
|
4760
|
+
const client = new OpenAI3({
|
|
4671
4761
|
baseURL: input.config.baseUrl,
|
|
4672
4762
|
apiKey: input.config.apiKey || "not-needed"
|
|
4673
4763
|
});
|
|
@@ -4730,7 +4820,7 @@ function extractAnswerText2(rawResponse) {
|
|
|
4730
4820
|
}
|
|
4731
4821
|
async function generateText4(prompt, config) {
|
|
4732
4822
|
const model = config.model ?? DEFAULT_MODEL4;
|
|
4733
|
-
const client = new
|
|
4823
|
+
const client = new OpenAI3({
|
|
4734
4824
|
baseURL: config.baseUrl,
|
|
4735
4825
|
apiKey: config.apiKey || "not-needed"
|
|
4736
4826
|
});
|
|
@@ -4753,6 +4843,31 @@ function responseToRecord4(response) {
|
|
|
4753
4843
|
}
|
|
4754
4844
|
}
|
|
4755
4845
|
|
|
4846
|
+
// ../provider-local/src/list-models.ts
|
|
4847
|
+
function listModels4(config, signal) {
|
|
4848
|
+
return withRetry(async () => {
|
|
4849
|
+
signal.throwIfAborted();
|
|
4850
|
+
if (!config.baseUrl) throw new Error("Local model discovery requires a configured endpoint");
|
|
4851
|
+
const response = await fetch(`${config.baseUrl.replace(/\/$/, "")}/models`, {
|
|
4852
|
+
method: "GET",
|
|
4853
|
+
signal,
|
|
4854
|
+
headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
|
|
4855
|
+
});
|
|
4856
|
+
if (!response.ok) throw Object.assign(new Error("Local model discovery failed"), { status: response.status, headers: response.headers });
|
|
4857
|
+
const body = await response.json();
|
|
4858
|
+
return body.data.filter((model) => typeof model.id === "string" && model.id.trim()).slice(0, 1e3).map((model) => ({ id: model.id, displayName: model.id, tier: "standard" }));
|
|
4859
|
+
}, {
|
|
4860
|
+
maxRetries: 1,
|
|
4861
|
+
baseDelayMs: 200,
|
|
4862
|
+
isRetryable: (error) => !signal.aborted && isRetryableHttpError(error),
|
|
4863
|
+
computeDelayMs: (_attempt, error, delay) => {
|
|
4864
|
+
const retryDelay = retryAfterDelayMs(error) ?? delay;
|
|
4865
|
+
if (retryDelay >= 2500) throw error;
|
|
4866
|
+
return retryDelay;
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4756
4871
|
// ../provider-local/src/adapter.ts
|
|
4757
4872
|
function toLocalConfig(config) {
|
|
4758
4873
|
return {
|
|
@@ -4776,6 +4891,7 @@ var localAdapter = {
|
|
|
4776
4891
|
{ id: "llama3", displayName: "Llama 3", tier: "standard" }
|
|
4777
4892
|
]
|
|
4778
4893
|
},
|
|
4894
|
+
listModels: listModels4,
|
|
4779
4895
|
validateConfig(config) {
|
|
4780
4896
|
const result = validateConfig4(toLocalConfig(config));
|
|
4781
4897
|
return {
|
|
@@ -5415,7 +5531,7 @@ var cdpChatgptAdapter = {
|
|
|
5415
5531
|
};
|
|
5416
5532
|
|
|
5417
5533
|
// ../provider-perplexity/src/normalize.ts
|
|
5418
|
-
import
|
|
5534
|
+
import OpenAI4 from "openai";
|
|
5419
5535
|
|
|
5420
5536
|
// ../provider-perplexity/src/utils.ts
|
|
5421
5537
|
async function withRetry6(fn, options = {}) {
|
|
@@ -5454,7 +5570,7 @@ async function healthcheck5(config) {
|
|
|
5454
5570
|
const validation = validateConfig5(config);
|
|
5455
5571
|
if (!validation.ok) return validation;
|
|
5456
5572
|
try {
|
|
5457
|
-
const client = new
|
|
5573
|
+
const client = new OpenAI4({ apiKey: config.apiKey, baseURL: BASE_URL });
|
|
5458
5574
|
const response = await withRetry6(
|
|
5459
5575
|
() => client.chat.completions.create({
|
|
5460
5576
|
model: config.model ?? DEFAULT_MODEL5,
|
|
@@ -5479,7 +5595,7 @@ async function healthcheck5(config) {
|
|
|
5479
5595
|
}
|
|
5480
5596
|
async function executeTrackedQuery5(input) {
|
|
5481
5597
|
const model = input.config.model ?? DEFAULT_MODEL5;
|
|
5482
|
-
const client = new
|
|
5598
|
+
const client = new OpenAI4({ apiKey: input.config.apiKey, baseURL: BASE_URL });
|
|
5483
5599
|
const prompt = buildPrompt4(input.query, input.location);
|
|
5484
5600
|
try {
|
|
5485
5601
|
const response = await withRetry6(
|
|
@@ -5639,7 +5755,7 @@ function extractDomainFromUri4(uri) {
|
|
|
5639
5755
|
}
|
|
5640
5756
|
async function generateText5(prompt, config) {
|
|
5641
5757
|
const model = config.model ?? DEFAULT_MODEL5;
|
|
5642
|
-
const client = new
|
|
5758
|
+
const client = new OpenAI4({ apiKey: config.apiKey, baseURL: BASE_URL });
|
|
5643
5759
|
const response = await withRetry6(
|
|
5644
5760
|
() => client.chat.completions.create({
|
|
5645
5761
|
model,
|
|
@@ -11925,7 +12041,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
11925
12041
|
return result;
|
|
11926
12042
|
}
|
|
11927
12043
|
async function backfillInsightsCommand(project, opts) {
|
|
11928
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
12044
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-M5TYY4TH.js");
|
|
11929
12045
|
const config = loadConfig();
|
|
11930
12046
|
const db = createClient(config.database);
|
|
11931
12047
|
migrate(db);
|
|
@@ -12500,6 +12616,58 @@ var ProviderRegistry = class {
|
|
|
12500
12616
|
}
|
|
12501
12617
|
};
|
|
12502
12618
|
|
|
12619
|
+
// src/provider-model-catalog.ts
|
|
12620
|
+
import { createHash } from "crypto";
|
|
12621
|
+
var TTL_MS = 60 * 60 * 1e3;
|
|
12622
|
+
var RETRY_MS = 60 * 1e3;
|
|
12623
|
+
var TIMEOUT_MS2 = 3e3;
|
|
12624
|
+
function createProviderModelCatalog(registry) {
|
|
12625
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12626
|
+
return async (name) => {
|
|
12627
|
+
const provider = registry.get(name);
|
|
12628
|
+
if (!provider) {
|
|
12629
|
+
cache.delete(name);
|
|
12630
|
+
return [];
|
|
12631
|
+
}
|
|
12632
|
+
const fallback = provider.adapter.modelRegistry.knownModels;
|
|
12633
|
+
if (!provider.adapter.listModels) return fallback;
|
|
12634
|
+
const identity = createHash("sha256").update(JSON.stringify(provider.config)).digest("hex");
|
|
12635
|
+
let entry = cache.get(name);
|
|
12636
|
+
if (entry?.identity !== identity) {
|
|
12637
|
+
entry = { identity, expiresAt: 0 };
|
|
12638
|
+
cache.set(name, entry);
|
|
12639
|
+
}
|
|
12640
|
+
if (entry.pending) return entry.pending;
|
|
12641
|
+
if (entry.expiresAt > Date.now()) return entry.models ?? fallback;
|
|
12642
|
+
const current = entry;
|
|
12643
|
+
const controller = new AbortController();
|
|
12644
|
+
let timer;
|
|
12645
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
12646
|
+
timer = setTimeout(() => {
|
|
12647
|
+
controller.abort();
|
|
12648
|
+
reject(new Error("Model discovery timed out"));
|
|
12649
|
+
}, TIMEOUT_MS2);
|
|
12650
|
+
});
|
|
12651
|
+
current.pending = Promise.race([
|
|
12652
|
+
Promise.resolve().then(() => provider.adapter.listModels(provider.config, controller.signal)),
|
|
12653
|
+
deadline
|
|
12654
|
+
]).then((models) => {
|
|
12655
|
+
const usableModels = [...new Map(models.filter((model) => model.id.trim()).map((model) => [model.id, model])).values()].sort((a, b) => b.id.localeCompare(a.id, "en", { numeric: true }));
|
|
12656
|
+
if (!usableModels.length) throw new Error("Empty model catalog");
|
|
12657
|
+
current.models = usableModels;
|
|
12658
|
+
current.expiresAt = Date.now() + TTL_MS;
|
|
12659
|
+
return current.models;
|
|
12660
|
+
}).catch((error) => {
|
|
12661
|
+
current.expiresAt = Date.now() + Math.max(RETRY_MS, retryAfterDelayMs(error) ?? 0);
|
|
12662
|
+
return current.models ?? fallback;
|
|
12663
|
+
}).finally(() => {
|
|
12664
|
+
clearTimeout(timer);
|
|
12665
|
+
current.pending = void 0;
|
|
12666
|
+
});
|
|
12667
|
+
return current.pending;
|
|
12668
|
+
};
|
|
12669
|
+
}
|
|
12670
|
+
|
|
12503
12671
|
// src/scheduler.ts
|
|
12504
12672
|
import crypto20 from "crypto";
|
|
12505
12673
|
import cron from "node-cron";
|
|
@@ -12524,6 +12692,10 @@ function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new D
|
|
|
12524
12692
|
function taskKey(projectId, kind) {
|
|
12525
12693
|
return `${projectId}::${kind}`;
|
|
12526
12694
|
}
|
|
12695
|
+
function scheduleRecurrence(schedule) {
|
|
12696
|
+
if (!schedule.recurrence) return null;
|
|
12697
|
+
return calendarRecurrenceSchema.safeParse(schedule.recurrence).data ?? null;
|
|
12698
|
+
}
|
|
12527
12699
|
var Scheduler = class {
|
|
12528
12700
|
db;
|
|
12529
12701
|
callbacks;
|
|
@@ -12598,10 +12770,14 @@ var Scheduler = class {
|
|
|
12598
12770
|
const allSchedules = this.db.select().from(schedules).where(eq17(schedules.enabled, true)).all();
|
|
12599
12771
|
for (const schedule of allSchedules) {
|
|
12600
12772
|
const missedRunAt = schedule.nextRunAt;
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12773
|
+
const recurrence = scheduleRecurrence(schedule);
|
|
12774
|
+
const registered = this.registerCronTask(schedule, recurrence ? { preserveNextRunAt: true } : {});
|
|
12775
|
+
if (registered && missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
12776
|
+
const answerRecurrence = recurrence && schedule.kind === SchedulableRunKinds["answer-visibility"];
|
|
12777
|
+
if (!recurrence || answerRecurrence || this.claimCalendarOccurrence(schedule, missedRunAt, /* @__PURE__ */ new Date())) {
|
|
12778
|
+
log14.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
|
|
12779
|
+
this.triggerRun(schedule.id, schedule.projectId, schedule.kind, answerRecurrence ? missedRunAt : void 0);
|
|
12780
|
+
}
|
|
12605
12781
|
}
|
|
12606
12782
|
}
|
|
12607
12783
|
log14.info("started", { scheduleCount: allSchedules.length });
|
|
@@ -12661,12 +12837,21 @@ var Scheduler = class {
|
|
|
12661
12837
|
}).where(eq17(schedules.id, scheduleId)).run();
|
|
12662
12838
|
});
|
|
12663
12839
|
}
|
|
12664
|
-
registerCronTask(schedule) {
|
|
12840
|
+
registerCronTask(schedule, options = {}) {
|
|
12841
|
+
const recurrence = scheduleRecurrence(schedule);
|
|
12842
|
+
if (recurrence) {
|
|
12843
|
+
this.registerCalendarTask(schedule, recurrence, options);
|
|
12844
|
+
return true;
|
|
12845
|
+
}
|
|
12846
|
+
if (schedule.recurrence) {
|
|
12847
|
+
log14.error("calendar.invalid", { projectId: schedule.projectId, kind: schedule.kind });
|
|
12848
|
+
return false;
|
|
12849
|
+
}
|
|
12665
12850
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
12666
12851
|
const kind = schedule.kind;
|
|
12667
12852
|
if (!cron.validate(cronExpr)) {
|
|
12668
12853
|
log14.error("cron.invalid", { projectId, kind, cronExpr });
|
|
12669
|
-
return;
|
|
12854
|
+
return false;
|
|
12670
12855
|
}
|
|
12671
12856
|
const task = cron.schedule(cronExpr, () => {
|
|
12672
12857
|
this.triggerRun(scheduleId, projectId, kind);
|
|
@@ -12674,13 +12859,94 @@ var Scheduler = class {
|
|
|
12674
12859
|
timezone
|
|
12675
12860
|
});
|
|
12676
12861
|
this.tasks.set(taskKey(projectId, kind), task);
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12862
|
+
if (!options.preserveNextRunAt || !schedule.nextRunAt) {
|
|
12863
|
+
this.updateScheduleTiming(scheduleId, {
|
|
12864
|
+
nextRunAt: nextRunFromCron(cronExpr, timezone)
|
|
12865
|
+
});
|
|
12866
|
+
}
|
|
12680
12867
|
const label = schedule.preset ?? cronExpr;
|
|
12681
12868
|
log14.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
12869
|
+
return true;
|
|
12870
|
+
}
|
|
12871
|
+
/** Calendar schedules are checked at most once a minute so long intervals
|
|
12872
|
+
* never exceed Node's timeout limit and a delayed event loop cannot fire an
|
|
12873
|
+
* old occurrence twice. The row's nextRunAt is a persistent claim token. */
|
|
12874
|
+
registerCalendarTask(schedule, recurrence, options) {
|
|
12875
|
+
const { id: scheduleId, projectId, timezone } = schedule;
|
|
12876
|
+
const kind = schedule.kind;
|
|
12877
|
+
const key = taskKey(projectId, kind);
|
|
12878
|
+
let timer;
|
|
12879
|
+
let stopped = false;
|
|
12880
|
+
const task = {
|
|
12881
|
+
stop: () => {
|
|
12882
|
+
stopped = true;
|
|
12883
|
+
if (timer) clearTimeout(timer);
|
|
12884
|
+
},
|
|
12885
|
+
destroy: () => {
|
|
12886
|
+
stopped = true;
|
|
12887
|
+
if (timer) clearTimeout(timer);
|
|
12888
|
+
}
|
|
12889
|
+
};
|
|
12890
|
+
const arm = (nextRunAt) => {
|
|
12891
|
+
if (stopped) return;
|
|
12892
|
+
const dueMs = nextRunAt ? Date.parse(nextRunAt) : Number.NaN;
|
|
12893
|
+
const delay = Number.isFinite(dueMs) && dueMs > Date.now() ? Math.min(6e4, Math.max(1, dueMs - Date.now())) : 6e4;
|
|
12894
|
+
timer = setTimeout(tick, delay);
|
|
12895
|
+
};
|
|
12896
|
+
const tick = () => {
|
|
12897
|
+
if (stopped) return;
|
|
12898
|
+
const current = this.db.select().from(schedules).where(eq17(schedules.id, scheduleId)).get();
|
|
12899
|
+
if (!current || !current.enabled || !scheduleRecurrence(current)) {
|
|
12900
|
+
this.remove(projectId, kind);
|
|
12901
|
+
return;
|
|
12902
|
+
}
|
|
12903
|
+
const now = /* @__PURE__ */ new Date();
|
|
12904
|
+
let nextRunAt = current.nextRunAt;
|
|
12905
|
+
if (!nextRunAt || Number.isNaN(Date.parse(nextRunAt))) {
|
|
12906
|
+
nextRunAt = nextRunFromSchedule({ cronExpr: current.cronExpr, timezone: current.timezone, recurrence: scheduleRecurrence(current) }, now);
|
|
12907
|
+
if (nextRunAt) this.updateScheduleTiming(current.id, { nextRunAt });
|
|
12908
|
+
}
|
|
12909
|
+
if (nextRunAt && Date.parse(nextRunAt) <= now.getTime()) {
|
|
12910
|
+
if (kind === SchedulableRunKinds["answer-visibility"]) {
|
|
12911
|
+
this.triggerRun(scheduleId, projectId, kind, nextRunAt);
|
|
12912
|
+
} else if (this.claimCalendarOccurrence(current, nextRunAt, now)) {
|
|
12913
|
+
this.triggerRun(scheduleId, projectId, kind);
|
|
12914
|
+
}
|
|
12915
|
+
}
|
|
12916
|
+
arm(this.db.select({ nextRunAt: schedules.nextRunAt }).from(schedules).where(eq17(schedules.id, scheduleId)).get()?.nextRunAt);
|
|
12917
|
+
};
|
|
12918
|
+
this.tasks.set(key, task);
|
|
12919
|
+
let registeredNextRunAt = schedule.nextRunAt;
|
|
12920
|
+
if (!options.preserveNextRunAt || !registeredNextRunAt) {
|
|
12921
|
+
registeredNextRunAt = nextRunFromSchedule({ cronExpr: schedule.cronExpr, timezone, recurrence }, /* @__PURE__ */ new Date());
|
|
12922
|
+
if (registeredNextRunAt) this.updateScheduleTiming(scheduleId, { nextRunAt: registeredNextRunAt });
|
|
12923
|
+
}
|
|
12924
|
+
if (options.preserveNextRunAt) arm(registeredNextRunAt);
|
|
12925
|
+
else timer = setTimeout(tick, 0);
|
|
12926
|
+
log14.info("calendar.registered", { projectId, kind, recurrence, timezone });
|
|
12927
|
+
}
|
|
12928
|
+
/** Atomically advance a due callback-only calendar occurrence before dispatch. */
|
|
12929
|
+
claimCalendarOccurrence(schedule, dueAt, now) {
|
|
12930
|
+
return this.db.transaction((tx) => {
|
|
12931
|
+
const current = tx.select().from(schedules).where(eq17(schedules.id, schedule.id)).get();
|
|
12932
|
+
if (!current || !current.enabled || current.updatedAt !== schedule.updatedAt || current.nextRunAt !== dueAt) return false;
|
|
12933
|
+
const recurrence = scheduleRecurrence(current);
|
|
12934
|
+
if (!recurrence) return false;
|
|
12935
|
+
const nextRunAt = nextRunFromSchedule({ cronExpr: current.cronExpr, timezone: current.timezone, recurrence }, now);
|
|
12936
|
+
if (!nextRunAt) return false;
|
|
12937
|
+
const result = tx.update(schedules).set({
|
|
12938
|
+
nextRunAt,
|
|
12939
|
+
updatedAt: nextScheduleUpdatedAt(current.updatedAt)
|
|
12940
|
+
}).where(and15(
|
|
12941
|
+
eq17(schedules.id, current.id),
|
|
12942
|
+
eq17(schedules.enabled, true),
|
|
12943
|
+
eq17(schedules.nextRunAt, dueAt),
|
|
12944
|
+
eq17(schedules.updatedAt, schedule.updatedAt)
|
|
12945
|
+
)).run();
|
|
12946
|
+
return result.changes === 1;
|
|
12947
|
+
});
|
|
12682
12948
|
}
|
|
12683
|
-
triggerRun(scheduleId, projectId, kind) {
|
|
12949
|
+
triggerRun(scheduleId, projectId, kind, claimedOccurrence) {
|
|
12684
12950
|
try {
|
|
12685
12951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12686
12952
|
const currentSchedule = this.db.select().from(schedules).where(eq17(schedules.id, scheduleId)).get();
|
|
@@ -12689,7 +12955,16 @@ var Scheduler = class {
|
|
|
12689
12955
|
this.remove(projectId, kind);
|
|
12690
12956
|
return;
|
|
12691
12957
|
}
|
|
12692
|
-
const
|
|
12958
|
+
const recurrence = scheduleRecurrence(currentSchedule);
|
|
12959
|
+
if (claimedOccurrence && (!recurrence || currentSchedule.nextRunAt !== claimedOccurrence)) {
|
|
12960
|
+
log14.info("calendar.stale-claim", { projectId, scheduleId, kind });
|
|
12961
|
+
return;
|
|
12962
|
+
}
|
|
12963
|
+
const nextRunAt = nextRunFromSchedule({
|
|
12964
|
+
cronExpr: currentSchedule.cronExpr,
|
|
12965
|
+
timezone: currentSchedule.timezone,
|
|
12966
|
+
recurrence
|
|
12967
|
+
});
|
|
12693
12968
|
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
12694
12969
|
if (!project) {
|
|
12695
12970
|
log14.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
@@ -12862,6 +13137,10 @@ var Scheduler = class {
|
|
|
12862
13137
|
const locationLabel = resolvedLocation?.label ?? null;
|
|
12863
13138
|
const scheduleProviders = currentSchedule.providers;
|
|
12864
13139
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
13140
|
+
if (claimedOccurrence && recurrence && !nextRunAt) {
|
|
13141
|
+
log14.error("calendar.invalid", { projectId, kind, scheduleId });
|
|
13142
|
+
return;
|
|
13143
|
+
}
|
|
12865
13144
|
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
12866
13145
|
createdAt: now,
|
|
12867
13146
|
kind: "answer-visibility",
|
|
@@ -12870,17 +13149,29 @@ var Scheduler = class {
|
|
|
12870
13149
|
location: locationLabel,
|
|
12871
13150
|
providers,
|
|
12872
13151
|
runnableProviders: this.callbacks.getRunnableProviderNames?.(),
|
|
12873
|
-
providerModels: this.callbacks.getEffectiveProviderModels?.()
|
|
13152
|
+
providerModels: this.callbacks.getEffectiveProviderModels?.(),
|
|
13153
|
+
...claimedOccurrence && recurrence && currentSchedule.nextRunAt === claimedOccurrence ? {
|
|
13154
|
+
scheduleClaim: {
|
|
13155
|
+
scheduleId: currentSchedule.id,
|
|
13156
|
+
dueAt: claimedOccurrence,
|
|
13157
|
+
expectedUpdatedAt: currentSchedule.updatedAt,
|
|
13158
|
+
nextRunAt
|
|
13159
|
+
}
|
|
13160
|
+
} : {}
|
|
12874
13161
|
});
|
|
12875
13162
|
if (queueResult.conflict) {
|
|
13163
|
+
if (queueResult.scheduleClaimed === false) {
|
|
13164
|
+
log14.info("calendar.skipped-claimed", { projectName: project.name, scheduleId: currentSchedule.id });
|
|
13165
|
+
return;
|
|
13166
|
+
}
|
|
12876
13167
|
log14.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
12877
|
-
this.updateScheduleTiming(currentSchedule.id, {
|
|
12878
|
-
nextRunAt
|
|
12879
|
-
});
|
|
13168
|
+
if (!claimedOccurrence) this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
12880
13169
|
return;
|
|
12881
13170
|
}
|
|
12882
13171
|
const runId = queueResult.runId;
|
|
12883
|
-
this.updateScheduleTiming(currentSchedule.id, {
|
|
13172
|
+
this.updateScheduleTiming(currentSchedule.id, claimedOccurrence ? {
|
|
13173
|
+
lastRunAt: now
|
|
13174
|
+
} : {
|
|
12884
13175
|
lastRunAt: now,
|
|
12885
13176
|
nextRunAt
|
|
12886
13177
|
});
|
|
@@ -15440,6 +15731,12 @@ function mcpTransportPaths() {
|
|
|
15440
15731
|
}
|
|
15441
15732
|
return paths;
|
|
15442
15733
|
}
|
|
15734
|
+
function mcpHttpHealth(app, apiPrefix) {
|
|
15735
|
+
const available = mcpTransportPaths().every(
|
|
15736
|
+
(path9) => ["POST", "GET", "DELETE"].every((method) => app.hasRoute({ method, url: `${apiPrefix}${path9}` }))
|
|
15737
|
+
);
|
|
15738
|
+
return { status: available ? "available" : "unavailable" };
|
|
15739
|
+
}
|
|
15443
15740
|
function registerMcpHttpRoutes(scope, opts) {
|
|
15444
15741
|
const sessions = /* @__PURE__ */ new Map();
|
|
15445
15742
|
const now = opts.now ?? (() => Date.now());
|
|
@@ -18677,6 +18974,7 @@ async function createServer(opts) {
|
|
|
18677
18974
|
includeCanonryLocal: true
|
|
18678
18975
|
},
|
|
18679
18976
|
providerSummary,
|
|
18977
|
+
getProviderModels: createProviderModelCatalog(registry),
|
|
18680
18978
|
providerAdapters: [...API_ADAPTERS, ...BROWSER_ADAPTERS].map((a) => ({
|
|
18681
18979
|
name: a.name,
|
|
18682
18980
|
displayName: a.displayName,
|
|
@@ -19099,6 +19397,7 @@ async function createServer(opts) {
|
|
|
19099
19397
|
status: "ok",
|
|
19100
19398
|
service: "canonry",
|
|
19101
19399
|
version: PKG_VERSION2,
|
|
19400
|
+
mcp: mcpHttpHealth(app, apiPrefix),
|
|
19102
19401
|
...basePath ? { basePath: basePath.replace(/\/$/, "") } : {},
|
|
19103
19402
|
...update ? { updateAvailable: update } : {}
|
|
19104
19403
|
};
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
installSkills,
|
|
6
6
|
loadConfigRaw,
|
|
7
7
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YDYB3H4O.js";
|
|
9
9
|
import {
|
|
10
10
|
SKILL_MANIFEST_FILENAME,
|
|
11
11
|
SkillsClients
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-CDI3YR57.js";
|
|
13
13
|
|
|
14
14
|
// src/skills-autosync.ts
|
|
15
15
|
import fs from "fs";
|