@ainyc/canonry 4.188.4 → 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-Cn1xsaFZ.js → AuditHistoryPanel-CZ7ygQfy.js} +1 -1
- package/assets/assets/{BacklinksPage-CfTxBHZn.js → BacklinksPage-CYEYxjE8.js} +1 -1
- package/assets/assets/{HistoryPage-BvgKqrbW.js → HistoryPage-CvAllKzD.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-CGbNTIdz.js → MeasurementPropertyPage-BED9ak5a.js} +1 -1
- package/assets/assets/ProjectPage-Cgl5v16o.js +9 -0
- package/assets/assets/{RunRow-DzneLSnv.js → RunRow-DUlFaUBp.js} +1 -1
- package/assets/assets/{RunsPage-BgByi_t3.js → RunsPage-BcbBe_aQ.js} +1 -1
- package/assets/assets/{SettingsPage-DBv-GyPu.js → SettingsPage-gCyOuoD_.js} +1 -1
- package/assets/assets/{SiteHealthSection-DaNslx5J.js → SiteHealthSection-DJfFmT3n.js} +3 -3
- package/assets/assets/{TrafficPage-B6mfVlPe.js → TrafficPage-2ATExz7V.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BXwAVIDo.js → TrafficSourceDetailPage-0PBMk9UH.js} +1 -1
- package/assets/assets/{extract-error-message-DMJa2ku8.js → extract-error-message-D8N7K1pb.js} +1 -1
- package/assets/assets/index-Bhwxe6W_.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-DiTvmmQd.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-7NMMQ7QO.js → chunk-HG6NLN7H.js} +330 -30
- package/dist/{chunk-VJRQTKUB.js → chunk-J533DKYE.js} +2 -2
- package/dist/{chunk-AC3ZIRHR.js → chunk-QQLMRABM.js} +645 -502
- package/dist/{chunk-OXYM6VCJ.js → chunk-YDYB3H4O.js} +19 -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 +11 -11
- package/assets/assets/ProjectPage-BQ4lzu49.js +0 -9
- package/assets/assets/index-BRnD7hdh.js +0 -86
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApiClient,
|
|
3
|
+
CANONRY_MCP_TIERS,
|
|
3
4
|
CANONRY_MCP_TOOLKIT_NAMES,
|
|
4
5
|
CanonryMcpToolNames,
|
|
5
6
|
CliError,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
loadConfig,
|
|
14
15
|
loadConfigRaw,
|
|
15
16
|
saveConfigPatch
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-YDYB3H4O.js";
|
|
17
18
|
import {
|
|
18
19
|
CC_CACHE_DIR,
|
|
19
20
|
DUCKDB_SPEC,
|
|
@@ -119,6 +120,7 @@ import {
|
|
|
119
120
|
measurementRunCompleteness,
|
|
120
121
|
migrate,
|
|
121
122
|
nextRunFromCron,
|
|
123
|
+
nextRunFromSchedule,
|
|
122
124
|
notifications,
|
|
123
125
|
parseCookieHeader,
|
|
124
126
|
parseJsonColumn,
|
|
@@ -160,7 +162,7 @@ import {
|
|
|
160
162
|
siteCrawlSnapshots,
|
|
161
163
|
toAlertView,
|
|
162
164
|
usageCounters
|
|
163
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-QQLMRABM.js";
|
|
164
166
|
import {
|
|
165
167
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
166
168
|
AGENT_PROVIDER_IDS,
|
|
@@ -222,6 +224,7 @@ import {
|
|
|
222
224
|
bucketOnboardingCount,
|
|
223
225
|
buildRunErrorFromMessages,
|
|
224
226
|
buildSimpleMeasurementDefinition,
|
|
227
|
+
calendarRecurrenceSchema,
|
|
225
228
|
canonicalizeGtmAccountId,
|
|
226
229
|
canonicalizeGtmContainerId,
|
|
227
230
|
canonicalizeGtmResourceSelection,
|
|
@@ -302,7 +305,7 @@ import {
|
|
|
302
305
|
validationError,
|
|
303
306
|
winnabilityClassLabel,
|
|
304
307
|
withRetry
|
|
305
|
-
} from "./chunk-
|
|
308
|
+
} from "./chunk-CDI3YR57.js";
|
|
306
309
|
|
|
307
310
|
// src/telemetry.ts
|
|
308
311
|
import crypto from "crypto";
|
|
@@ -3759,6 +3762,35 @@ function createGeminiEmbedClient(apiKey, baseUrl) {
|
|
|
3759
3762
|
};
|
|
3760
3763
|
}
|
|
3761
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
|
+
|
|
3762
3794
|
// ../provider-gemini/src/adapter.ts
|
|
3763
3795
|
function toGeminiConfig(config) {
|
|
3764
3796
|
return {
|
|
@@ -3790,6 +3822,7 @@ var geminiAdapter = {
|
|
|
3790
3822
|
{ id: "gemini-2.0-flash", displayName: "Gemini 2.0 Flash", tier: "standard" }
|
|
3791
3823
|
]
|
|
3792
3824
|
},
|
|
3825
|
+
listModels: (config, signal) => listModels(toGeminiConfig(config), signal),
|
|
3793
3826
|
validateConfig(config) {
|
|
3794
3827
|
const result = validateConfig(toGeminiConfig(config));
|
|
3795
3828
|
return {
|
|
@@ -4112,6 +4145,35 @@ function responseToRecord2(response) {
|
|
|
4112
4145
|
}
|
|
4113
4146
|
}
|
|
4114
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
|
+
|
|
4115
4177
|
// ../provider-openai/src/adapter.ts
|
|
4116
4178
|
function toOpenAIConfig(config) {
|
|
4117
4179
|
return {
|
|
@@ -4142,6 +4204,7 @@ var openaiAdapter = {
|
|
|
4142
4204
|
{ id: "gpt-4.1", displayName: "GPT-4.1", tier: "standard" }
|
|
4143
4205
|
]
|
|
4144
4206
|
},
|
|
4207
|
+
listModels: listModels2,
|
|
4145
4208
|
validateConfig(config) {
|
|
4146
4209
|
const result = validateConfig2(toOpenAIConfig(config));
|
|
4147
4210
|
return {
|
|
@@ -4506,6 +4569,33 @@ function responseToRecord3(response) {
|
|
|
4506
4569
|
}
|
|
4507
4570
|
}
|
|
4508
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
|
+
|
|
4509
4599
|
// ../provider-claude/src/adapter.ts
|
|
4510
4600
|
function toClaudeConfig(config) {
|
|
4511
4601
|
return {
|
|
@@ -4532,6 +4622,7 @@ var claudeAdapter = {
|
|
|
4532
4622
|
{ id: "claude-haiku-4-5", displayName: "Claude Haiku 4.5", tier: "fast" }
|
|
4533
4623
|
]
|
|
4534
4624
|
},
|
|
4625
|
+
listModels: listModels3,
|
|
4535
4626
|
validateConfig(config) {
|
|
4536
4627
|
const result = validateConfig3(toClaudeConfig(config));
|
|
4537
4628
|
return {
|
|
@@ -4598,7 +4689,7 @@ var claudeAdapter = {
|
|
|
4598
4689
|
};
|
|
4599
4690
|
|
|
4600
4691
|
// ../provider-local/src/normalize.ts
|
|
4601
|
-
import
|
|
4692
|
+
import OpenAI3 from "openai";
|
|
4602
4693
|
|
|
4603
4694
|
// ../provider-local/src/utils.ts
|
|
4604
4695
|
async function withRetry5(fn, options = {}) {
|
|
@@ -4636,7 +4727,7 @@ async function healthcheck4(config) {
|
|
|
4636
4727
|
const validation = validateConfig4(config);
|
|
4637
4728
|
if (!validation.ok) return validation;
|
|
4638
4729
|
try {
|
|
4639
|
-
const client = new
|
|
4730
|
+
const client = new OpenAI3({
|
|
4640
4731
|
baseURL: config.baseUrl,
|
|
4641
4732
|
apiKey: config.apiKey || "not-needed"
|
|
4642
4733
|
});
|
|
@@ -4666,7 +4757,7 @@ async function healthcheck4(config) {
|
|
|
4666
4757
|
}
|
|
4667
4758
|
async function executeTrackedQuery4(input) {
|
|
4668
4759
|
const model = input.config.model ?? DEFAULT_MODEL4;
|
|
4669
|
-
const client = new
|
|
4760
|
+
const client = new OpenAI3({
|
|
4670
4761
|
baseURL: input.config.baseUrl,
|
|
4671
4762
|
apiKey: input.config.apiKey || "not-needed"
|
|
4672
4763
|
});
|
|
@@ -4729,7 +4820,7 @@ function extractAnswerText2(rawResponse) {
|
|
|
4729
4820
|
}
|
|
4730
4821
|
async function generateText4(prompt, config) {
|
|
4731
4822
|
const model = config.model ?? DEFAULT_MODEL4;
|
|
4732
|
-
const client = new
|
|
4823
|
+
const client = new OpenAI3({
|
|
4733
4824
|
baseURL: config.baseUrl,
|
|
4734
4825
|
apiKey: config.apiKey || "not-needed"
|
|
4735
4826
|
});
|
|
@@ -4752,6 +4843,31 @@ function responseToRecord4(response) {
|
|
|
4752
4843
|
}
|
|
4753
4844
|
}
|
|
4754
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
|
+
|
|
4755
4871
|
// ../provider-local/src/adapter.ts
|
|
4756
4872
|
function toLocalConfig(config) {
|
|
4757
4873
|
return {
|
|
@@ -4775,6 +4891,7 @@ var localAdapter = {
|
|
|
4775
4891
|
{ id: "llama3", displayName: "Llama 3", tier: "standard" }
|
|
4776
4892
|
]
|
|
4777
4893
|
},
|
|
4894
|
+
listModels: listModels4,
|
|
4778
4895
|
validateConfig(config) {
|
|
4779
4896
|
const result = validateConfig4(toLocalConfig(config));
|
|
4780
4897
|
return {
|
|
@@ -5414,7 +5531,7 @@ var cdpChatgptAdapter = {
|
|
|
5414
5531
|
};
|
|
5415
5532
|
|
|
5416
5533
|
// ../provider-perplexity/src/normalize.ts
|
|
5417
|
-
import
|
|
5534
|
+
import OpenAI4 from "openai";
|
|
5418
5535
|
|
|
5419
5536
|
// ../provider-perplexity/src/utils.ts
|
|
5420
5537
|
async function withRetry6(fn, options = {}) {
|
|
@@ -5453,7 +5570,7 @@ async function healthcheck5(config) {
|
|
|
5453
5570
|
const validation = validateConfig5(config);
|
|
5454
5571
|
if (!validation.ok) return validation;
|
|
5455
5572
|
try {
|
|
5456
|
-
const client = new
|
|
5573
|
+
const client = new OpenAI4({ apiKey: config.apiKey, baseURL: BASE_URL });
|
|
5457
5574
|
const response = await withRetry6(
|
|
5458
5575
|
() => client.chat.completions.create({
|
|
5459
5576
|
model: config.model ?? DEFAULT_MODEL5,
|
|
@@ -5478,7 +5595,7 @@ async function healthcheck5(config) {
|
|
|
5478
5595
|
}
|
|
5479
5596
|
async function executeTrackedQuery5(input) {
|
|
5480
5597
|
const model = input.config.model ?? DEFAULT_MODEL5;
|
|
5481
|
-
const client = new
|
|
5598
|
+
const client = new OpenAI4({ apiKey: input.config.apiKey, baseURL: BASE_URL });
|
|
5482
5599
|
const prompt = buildPrompt4(input.query, input.location);
|
|
5483
5600
|
try {
|
|
5484
5601
|
const response = await withRetry6(
|
|
@@ -5638,7 +5755,7 @@ function extractDomainFromUri4(uri) {
|
|
|
5638
5755
|
}
|
|
5639
5756
|
async function generateText5(prompt, config) {
|
|
5640
5757
|
const model = config.model ?? DEFAULT_MODEL5;
|
|
5641
|
-
const client = new
|
|
5758
|
+
const client = new OpenAI4({ apiKey: config.apiKey, baseURL: BASE_URL });
|
|
5642
5759
|
const response = await withRetry6(
|
|
5643
5760
|
() => client.chat.completions.create({
|
|
5644
5761
|
model,
|
|
@@ -11924,7 +12041,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
11924
12041
|
return result;
|
|
11925
12042
|
}
|
|
11926
12043
|
async function backfillInsightsCommand(project, opts) {
|
|
11927
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
12044
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-M5TYY4TH.js");
|
|
11928
12045
|
const config = loadConfig();
|
|
11929
12046
|
const db = createClient(config.database);
|
|
11930
12047
|
migrate(db);
|
|
@@ -12499,6 +12616,58 @@ var ProviderRegistry = class {
|
|
|
12499
12616
|
}
|
|
12500
12617
|
};
|
|
12501
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
|
+
|
|
12502
12671
|
// src/scheduler.ts
|
|
12503
12672
|
import crypto20 from "crypto";
|
|
12504
12673
|
import cron from "node-cron";
|
|
@@ -12523,6 +12692,10 @@ function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new D
|
|
|
12523
12692
|
function taskKey(projectId, kind) {
|
|
12524
12693
|
return `${projectId}::${kind}`;
|
|
12525
12694
|
}
|
|
12695
|
+
function scheduleRecurrence(schedule) {
|
|
12696
|
+
if (!schedule.recurrence) return null;
|
|
12697
|
+
return calendarRecurrenceSchema.safeParse(schedule.recurrence).data ?? null;
|
|
12698
|
+
}
|
|
12526
12699
|
var Scheduler = class {
|
|
12527
12700
|
db;
|
|
12528
12701
|
callbacks;
|
|
@@ -12597,10 +12770,14 @@ var Scheduler = class {
|
|
|
12597
12770
|
const allSchedules = this.db.select().from(schedules).where(eq17(schedules.enabled, true)).all();
|
|
12598
12771
|
for (const schedule of allSchedules) {
|
|
12599
12772
|
const missedRunAt = schedule.nextRunAt;
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
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
|
+
}
|
|
12604
12781
|
}
|
|
12605
12782
|
}
|
|
12606
12783
|
log14.info("started", { scheduleCount: allSchedules.length });
|
|
@@ -12660,12 +12837,21 @@ var Scheduler = class {
|
|
|
12660
12837
|
}).where(eq17(schedules.id, scheduleId)).run();
|
|
12661
12838
|
});
|
|
12662
12839
|
}
|
|
12663
|
-
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
|
+
}
|
|
12664
12850
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
12665
12851
|
const kind = schedule.kind;
|
|
12666
12852
|
if (!cron.validate(cronExpr)) {
|
|
12667
12853
|
log14.error("cron.invalid", { projectId, kind, cronExpr });
|
|
12668
|
-
return;
|
|
12854
|
+
return false;
|
|
12669
12855
|
}
|
|
12670
12856
|
const task = cron.schedule(cronExpr, () => {
|
|
12671
12857
|
this.triggerRun(scheduleId, projectId, kind);
|
|
@@ -12673,13 +12859,94 @@ var Scheduler = class {
|
|
|
12673
12859
|
timezone
|
|
12674
12860
|
});
|
|
12675
12861
|
this.tasks.set(taskKey(projectId, kind), task);
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12862
|
+
if (!options.preserveNextRunAt || !schedule.nextRunAt) {
|
|
12863
|
+
this.updateScheduleTiming(scheduleId, {
|
|
12864
|
+
nextRunAt: nextRunFromCron(cronExpr, timezone)
|
|
12865
|
+
});
|
|
12866
|
+
}
|
|
12679
12867
|
const label = schedule.preset ?? cronExpr;
|
|
12680
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
|
+
});
|
|
12681
12948
|
}
|
|
12682
|
-
triggerRun(scheduleId, projectId, kind) {
|
|
12949
|
+
triggerRun(scheduleId, projectId, kind, claimedOccurrence) {
|
|
12683
12950
|
try {
|
|
12684
12951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12685
12952
|
const currentSchedule = this.db.select().from(schedules).where(eq17(schedules.id, scheduleId)).get();
|
|
@@ -12688,7 +12955,16 @@ var Scheduler = class {
|
|
|
12688
12955
|
this.remove(projectId, kind);
|
|
12689
12956
|
return;
|
|
12690
12957
|
}
|
|
12691
|
-
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
|
+
});
|
|
12692
12968
|
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
12693
12969
|
if (!project) {
|
|
12694
12970
|
log14.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
@@ -12861,6 +13137,10 @@ var Scheduler = class {
|
|
|
12861
13137
|
const locationLabel = resolvedLocation?.label ?? null;
|
|
12862
13138
|
const scheduleProviders = currentSchedule.providers;
|
|
12863
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
|
+
}
|
|
12864
13144
|
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
12865
13145
|
createdAt: now,
|
|
12866
13146
|
kind: "answer-visibility",
|
|
@@ -12869,17 +13149,29 @@ var Scheduler = class {
|
|
|
12869
13149
|
location: locationLabel,
|
|
12870
13150
|
providers,
|
|
12871
13151
|
runnableProviders: this.callbacks.getRunnableProviderNames?.(),
|
|
12872
|
-
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
|
+
} : {}
|
|
12873
13161
|
});
|
|
12874
13162
|
if (queueResult.conflict) {
|
|
13163
|
+
if (queueResult.scheduleClaimed === false) {
|
|
13164
|
+
log14.info("calendar.skipped-claimed", { projectName: project.name, scheduleId: currentSchedule.id });
|
|
13165
|
+
return;
|
|
13166
|
+
}
|
|
12875
13167
|
log14.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
12876
|
-
this.updateScheduleTiming(currentSchedule.id, {
|
|
12877
|
-
nextRunAt
|
|
12878
|
-
});
|
|
13168
|
+
if (!claimedOccurrence) this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
12879
13169
|
return;
|
|
12880
13170
|
}
|
|
12881
13171
|
const runId = queueResult.runId;
|
|
12882
|
-
this.updateScheduleTiming(currentSchedule.id, {
|
|
13172
|
+
this.updateScheduleTiming(currentSchedule.id, claimedOccurrence ? {
|
|
13173
|
+
lastRunAt: now
|
|
13174
|
+
} : {
|
|
12883
13175
|
lastRunAt: now,
|
|
12884
13176
|
nextRunAt
|
|
12885
13177
|
});
|
|
@@ -15439,6 +15731,12 @@ function mcpTransportPaths() {
|
|
|
15439
15731
|
}
|
|
15440
15732
|
return paths;
|
|
15441
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
|
+
}
|
|
15442
15740
|
function registerMcpHttpRoutes(scope, opts) {
|
|
15443
15741
|
const sessions = /* @__PURE__ */ new Map();
|
|
15444
15742
|
const now = opts.now ?? (() => Date.now());
|
|
@@ -15547,8 +15845,8 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
15547
15845
|
scope.get(pathname, { config }, run);
|
|
15548
15846
|
scope.delete(pathname, { config }, run);
|
|
15549
15847
|
}
|
|
15550
|
-
mount("/mcp", { id: "
|
|
15551
|
-
mount("/mcp/readonly", { id: "
|
|
15848
|
+
mount("/mcp", { id: "default", tiers: CANONRY_MCP_TIERS, readOnly: false });
|
|
15849
|
+
mount("/mcp/readonly", { id: "default:ro", tiers: CANONRY_MCP_TIERS, readOnly: true });
|
|
15552
15850
|
for (const toolkit of CANONRY_MCP_TOOLKIT_NAMES) {
|
|
15553
15851
|
const tiers = ["core", toolkit];
|
|
15554
15852
|
mount(`/mcp/x/${toolkit}`, { id: toolkit, tiers, readOnly: false });
|
|
@@ -18676,6 +18974,7 @@ async function createServer(opts) {
|
|
|
18676
18974
|
includeCanonryLocal: true
|
|
18677
18975
|
},
|
|
18678
18976
|
providerSummary,
|
|
18977
|
+
getProviderModels: createProviderModelCatalog(registry),
|
|
18679
18978
|
providerAdapters: [...API_ADAPTERS, ...BROWSER_ADAPTERS].map((a) => ({
|
|
18680
18979
|
name: a.name,
|
|
18681
18980
|
displayName: a.displayName,
|
|
@@ -19098,6 +19397,7 @@ async function createServer(opts) {
|
|
|
19098
19397
|
status: "ok",
|
|
19099
19398
|
service: "canonry",
|
|
19100
19399
|
version: PKG_VERSION2,
|
|
19400
|
+
mcp: mcpHttpHealth(app, apiPrefix),
|
|
19101
19401
|
...basePath ? { basePath: basePath.replace(/\/$/, "") } : {},
|
|
19102
19402
|
...update ? { updateAvailable: update } : {}
|
|
19103
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";
|