@ainyc/canonry 4.97.0 → 4.99.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.
Files changed (24) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +7 -3
  2. package/assets/assets/{BacklinksPage-BI9BtHjp.js → BacklinksPage-CuWs7I1D.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-BcjGunGr.js → ChartPrimitives-bm37tprp.js} +1 -1
  4. package/assets/assets/{ProjectPage-BdoberYt.js → ProjectPage-BksYe8FP.js} +1 -1
  5. package/assets/assets/{RunRow-BT2kpdQR.js → RunRow-DzhQl87g.js} +1 -1
  6. package/assets/assets/{RunsPage-BAgZuX7j.js → RunsPage-wOaewhbF.js} +1 -1
  7. package/assets/assets/{SettingsPage-BvKcpivM.js → SettingsPage-CJdhhkkE.js} +1 -1
  8. package/assets/assets/{TrafficPage-BdGOZv1e.js → TrafficPage-BJBeN5Ph.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-C_N2D9xy.js → TrafficSourceDetailPage-D3PmH0B1.js} +1 -1
  10. package/assets/assets/{arrow-left-C8Fbup8X.js → arrow-left-BmITMI6O.js} +1 -1
  11. package/assets/assets/{extract-error-message-C29K35Gm.js → extract-error-message-BGwnyAfP.js} +1 -1
  12. package/assets/assets/{index-BGfyt3LX.js → index-BSX6r6ju.js} +9 -9
  13. package/assets/assets/{trash-2-DWLIELE_.js → trash-2-Bd2mkP_w.js} +1 -1
  14. package/assets/index.html +1 -1
  15. package/dist/{chunk-NUGGUQH3.js → chunk-CEHD33NQ.js} +326 -29
  16. package/dist/{chunk-E2FDXNMW.js → chunk-CWFTMFEU.js} +5 -3
  17. package/dist/{chunk-TXNKKTFD.js → chunk-DJVUNURL.js} +51 -2
  18. package/dist/{chunk-SRJ5IGNK.js → chunk-VQN74WWA.js} +28 -2
  19. package/dist/cli.js +4 -4
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-DC2TD7RR.js → intelligence-service-QHBXW4KI.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +7 -7
@@ -1 +1 @@
1
- import{c}from"./index-BGfyt3LX.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-BSX6r6ju.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
package/assets/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-BGfyt3LX.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-BSX6r6ju.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-C9EZkgbP.js">
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-SRJ5IGNK.js";
12
+ } from "./chunk-VQN74WWA.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -111,7 +111,7 @@ import {
111
111
  siteAuditPages,
112
112
  siteAuditSnapshots,
113
113
  usageCounters
114
- } from "./chunk-TXNKKTFD.js";
114
+ } from "./chunk-DJVUNURL.js";
115
115
  import {
116
116
  AGENT_MEMORY_VALUE_MAX_BYTES,
117
117
  AGENT_PROVIDER_IDS,
@@ -171,7 +171,7 @@ import {
171
171
  validationError,
172
172
  winnabilityClassLabel,
173
173
  withRetry
174
- } from "./chunk-E2FDXNMW.js";
174
+ } from "./chunk-CWFTMFEU.js";
175
175
 
176
176
  // src/telemetry.ts
177
177
  import crypto from "crypto";
@@ -6402,7 +6402,7 @@ function readStoredGroundingSources(rawResponse) {
6402
6402
  return result;
6403
6403
  }
6404
6404
  async function backfillInsightsCommand(project, opts) {
6405
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-DC2TD7RR.js");
6405
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-QHBXW4KI.js");
6406
6406
  const config = loadConfig();
6407
6407
  const db = createClient(config.database);
6408
6408
  migrate(db);
@@ -7988,7 +7988,10 @@ import { Agent } from "@mariozechner/pi-agent-core";
7988
7988
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
7989
7989
 
7990
7990
  // src/agent/providers.ts
7991
- import { getEnvApiKey, getModel } from "@mariozechner/pi-ai";
7991
+ import {
7992
+ getEnvApiKey,
7993
+ getModel
7994
+ } from "@mariozechner/pi-ai";
7992
7995
  var PROVIDER_MODELS = {
7993
7996
  [AgentProviderIds.claude]: {
7994
7997
  [LlmCapabilities.agent]: "claude-opus-4-7",
@@ -8014,6 +8017,14 @@ var PROVIDER_MODELS = {
8014
8017
  [LlmCapabilities.agent]: "glm-5.1",
8015
8018
  [LlmCapabilities.analyze]: "glm-5-turbo",
8016
8019
  [LlmCapabilities.classify]: "glm-5-turbo"
8020
+ },
8021
+ [AgentProviderIds.deepinfra]: {
8022
+ // DeepInfra `org/Model` slugs. GLM-5.2 drives the agent loop; the much
8023
+ // cheaper DeepSeek-V4-Flash fills analyze + classify. Resolved into a
8024
+ // custom openai-completions model, not pi-ai's catalog.
8025
+ [LlmCapabilities.agent]: "zai-org/GLM-5.2",
8026
+ [LlmCapabilities.analyze]: "deepseek-ai/DeepSeek-V4-Flash",
8027
+ [LlmCapabilities.classify]: "deepseek-ai/DeepSeek-V4-Flash"
8017
8028
  }
8018
8029
  };
8019
8030
  var AGENT_PROVIDERS = {
@@ -8040,6 +8051,62 @@ var AGENT_PROVIDERS = {
8040
8051
  label: "Z.ai (GLM)",
8041
8052
  defaultModel: PROVIDER_MODELS[AgentProviderIds.zai][LlmCapabilities.agent],
8042
8053
  autoDetectPriority: 3
8054
+ },
8055
+ [AgentProviderIds.deepinfra]: {
8056
+ // `piAiProvider` is the canonical id here (not a pi-ai vendor) — it's the
8057
+ // `model.provider` string the agent loop hands to `getApiKey`, which
8058
+ // `resolveAgentId` maps straight back to the deepinfra config/env key.
8059
+ piAiProvider: AgentProviderIds.deepinfra,
8060
+ label: "DeepInfra (GLM / DeepSeek)",
8061
+ defaultModel: PROVIDER_MODELS[AgentProviderIds.deepinfra][LlmCapabilities.agent],
8062
+ autoDetectPriority: 4,
8063
+ openaiCompatible: {
8064
+ baseUrl: "https://api.deepinfra.com/v1/openai",
8065
+ baseUrlEnvVar: "DEEPINFRA_BASE_URL",
8066
+ apiKeyEnvVar: "DEEPINFRA_TOKEN",
8067
+ // DeepInfra serves open-weight models (GLM, DeepSeek) behind an
8068
+ // OpenAI-compatible vLLM endpoint. pi-ai's `detectCompat` has no rule
8069
+ // for api.deepinfra.com, so it would apply the strict OpenAI profile;
8070
+ // we set the same open-model profile pi-ai uses for deepseek.com /
8071
+ // cerebras / z.ai. `developer` role and `reasoning_effort` are
8072
+ // OpenAI-platform-isms these models reject, `store` is response
8073
+ // persistence DeepInfra doesn't implement, and DeepInfra documents
8074
+ // `max_tokens` (not `max_completion_tokens`) on its OpenAI route.
8075
+ compat: {
8076
+ supportsStore: false,
8077
+ supportsDeveloperRole: false,
8078
+ supportsReasoningEffort: false,
8079
+ maxTokensField: "max_tokens"
8080
+ },
8081
+ // Best-effort metadata. Costs are USD/1M tokens (DeepInfra published
8082
+ // rates: GLM-5.2 ~$0.95 in / $0.18 cached / $3.00 out; DeepSeek-V4-Flash
8083
+ // ~$0.10 in / $0.20 out). contextWindow is DeepInfra's 1M (fp4) serving
8084
+ // window for both models; it's descriptive (see OpenAiCompatibleModelMeta),
8085
+ // so it documents the real window rather than gating compaction.
8086
+ knownModels: {
8087
+ "zai-org/GLM-5.2": {
8088
+ contextWindow: 1048576,
8089
+ maxTokens: 98304,
8090
+ reasoning: true,
8091
+ cost: { input: 0.95, output: 3, cacheRead: 0.18, cacheWrite: 0 }
8092
+ },
8093
+ "deepseek-ai/DeepSeek-V4-Flash": {
8094
+ contextWindow: 1048576,
8095
+ maxTokens: 32768,
8096
+ reasoning: false,
8097
+ cost: { input: 0.1, output: 0.2, cacheRead: 0, cacheWrite: 0 }
8098
+ }
8099
+ },
8100
+ // Fallback for arbitrary `--model` slugs we don't ship as tiers. cost is
8101
+ // 0, so an unknown slug isn't cost-tracked by pi-ai's `calculateCost`;
8102
+ // contextWindow stays conservative since we can't know the slug's window.
8103
+ defaultModelMeta: {
8104
+ contextWindow: 131072,
8105
+ maxTokens: 32768,
8106
+ reasoning: false,
8107
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
8108
+ }
8109
+ }
8043
8110
  }
8044
8111
  };
8045
8112
  function agentProvidersByPriority() {
@@ -8061,6 +8128,9 @@ function resolveModelForProvider(provider, modelId) {
8061
8128
  function resolveModelForCapability(provider, capability, modelIdOverride) {
8062
8129
  const entry = AGENT_PROVIDERS[provider];
8063
8130
  const id = modelIdOverride ?? PROVIDER_MODELS[provider][capability];
8131
+ if (entry.openaiCompatible) {
8132
+ return buildOpenAiCompatibleModel(entry, id);
8133
+ }
8064
8134
  const model = getModel(entry.piAiProvider, id);
8065
8135
  if (!model) {
8066
8136
  throw new Error(
@@ -8069,6 +8139,27 @@ function resolveModelForCapability(provider, capability, modelIdOverride) {
8069
8139
  }
8070
8140
  return model;
8071
8141
  }
8142
+ function buildOpenAiCompatibleModel(entry, id) {
8143
+ const host = entry.openaiCompatible;
8144
+ if (!host) {
8145
+ throw new Error(`buildOpenAiCompatibleModel called for non-custom provider '${entry.piAiProvider}'`);
8146
+ }
8147
+ const meta = host.knownModels[id] ?? host.defaultModelMeta;
8148
+ const baseUrl = (host.baseUrlEnvVar ? process.env[host.baseUrlEnvVar] : void 0) || host.baseUrl;
8149
+ return {
8150
+ id,
8151
+ name: id,
8152
+ api: "openai-completions",
8153
+ provider: entry.piAiProvider,
8154
+ baseUrl,
8155
+ reasoning: meta.reasoning,
8156
+ input: ["text"],
8157
+ cost: meta.cost,
8158
+ contextWindow: meta.contextWindow,
8159
+ maxTokens: meta.maxTokens,
8160
+ ...host.compat ? { compat: host.compat } : {}
8161
+ };
8162
+ }
8072
8163
  function validateAgentProviderRegistry() {
8073
8164
  for (const provider of listAgentProviders()) {
8074
8165
  for (const capability of LLM_CAPABILITIES) {
@@ -8092,10 +8183,14 @@ function resolveApiKeySource(providerOrPiAi, config) {
8092
8183
  const entry = AGENT_PROVIDERS[id];
8093
8184
  const fromConfig = config.providers?.[id]?.apiKey;
8094
8185
  if (fromConfig) return { key: fromConfig, source: "config" };
8095
- const fromEnv = getEnvApiKey(entry.piAiProvider);
8186
+ const fromEnv = entry.openaiCompatible ? process.env[entry.openaiCompatible.apiKeyEnvVar] : getEnvApiKey(entry.piAiProvider);
8096
8187
  if (fromEnv) return { key: fromEnv, source: "env" };
8097
8188
  return void 0;
8098
8189
  }
8190
+ function agentProviderApiKeyEnvVar(id) {
8191
+ const entry = AGENT_PROVIDERS[id];
8192
+ return entry.openaiCompatible ? entry.openaiCompatible.apiKeyEnvVar : `${entry.piAiProvider.toUpperCase()}_API_KEY`;
8193
+ }
8099
8194
  function resolveAgentId(providerOrPiAi) {
8100
8195
  if (isAgentProviderId(providerOrPiAi)) return providerOrPiAi;
8101
8196
  for (const id of AGENT_PROVIDER_IDS) {
@@ -8236,13 +8331,63 @@ function buildSkillDocTools() {
8236
8331
  // src/agent/mcp-to-agent-tool.ts
8237
8332
  import { Type as Type2 } from "@sinclair/typebox";
8238
8333
  var MAX_TOOL_RESULT_CHARS = 2e4;
8239
- function truncate(json) {
8240
- if (json.length <= MAX_TOOL_RESULT_CHARS) return json;
8241
- return json.slice(0, MAX_TOOL_RESULT_CHARS) + "\n... (truncated \u2014 result too large)";
8334
+ var TRUNCATION_NOTE = "... (truncated \u2014 result too large)";
8335
+ function serializeResult(value) {
8336
+ return JSON.stringify(value, null, 2);
8337
+ }
8338
+ function largestArrayKey(obj) {
8339
+ let best;
8340
+ let bestLen = -1;
8341
+ for (const [k, v] of Object.entries(obj)) {
8342
+ if (!Array.isArray(v)) continue;
8343
+ const len = serializeResult(v).length;
8344
+ if (len > bestLen) {
8345
+ bestLen = len;
8346
+ best = k;
8347
+ }
8348
+ }
8349
+ return best;
8350
+ }
8351
+ function trimRowsToFit(rows, render) {
8352
+ let kept = rows.slice();
8353
+ while (kept.length > 0 && render(kept).length > MAX_TOOL_RESULT_CHARS) {
8354
+ kept = kept.slice(0, -1);
8355
+ }
8356
+ return kept;
8357
+ }
8358
+ function truncateToolResult(details) {
8359
+ const full = serializeResult(details);
8360
+ if (full.length <= MAX_TOOL_RESULT_CHARS) return full;
8361
+ if (Array.isArray(details)) {
8362
+ const kept = trimRowsToFit(
8363
+ details,
8364
+ (rows) => serializeResult({ items: rows, __truncated: true, __omittedRows: details.length - rows.length })
8365
+ );
8366
+ return serializeResult({ items: kept, __truncated: true, __omittedRows: details.length - kept.length });
8367
+ }
8368
+ if (details && typeof details === "object") {
8369
+ const obj = details;
8370
+ const arrayKey = largestArrayKey(obj);
8371
+ if (arrayKey) {
8372
+ const rows = obj[arrayKey];
8373
+ const kept = trimRowsToFit(
8374
+ rows,
8375
+ (r) => serializeResult({ ...obj, [arrayKey]: r, __truncated: true, __omittedRows: rows.length - r.length })
8376
+ );
8377
+ const out = serializeResult({
8378
+ ...obj,
8379
+ [arrayKey]: kept,
8380
+ __truncated: true,
8381
+ __omittedRows: rows.length - kept.length
8382
+ });
8383
+ if (out.length <= MAX_TOOL_RESULT_CHARS) return out;
8384
+ }
8385
+ }
8386
+ return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
8242
8387
  }
8243
8388
  function textResult2(details) {
8244
8389
  return {
8245
- content: [{ type: "text", text: truncate(JSON.stringify(details, null, 2)) }],
8390
+ content: [{ type: "text", text: truncateToolResult(details) }],
8246
8391
  details
8247
8392
  };
8248
8393
  }
@@ -8309,17 +8454,35 @@ function loadAeroSystemPrompt(pkgDir) {
8309
8454
  const skillDir = resolveAeroSkillDir(pkgDir);
8310
8455
  const skillBody = fs7.readFileSync(path8.join(skillDir, "SKILL.md"), "utf-8");
8311
8456
  const soulPath = path8.join(skillDir, "soul.md");
8312
- if (!fs7.existsSync(soulPath)) return skillBody;
8313
- const soulBody = fs7.readFileSync(soulPath, "utf-8");
8314
- return `${soulBody.trimEnd()}
8457
+ const base = fs7.existsSync(soulPath) ? `${fs7.readFileSync(soulPath, "utf-8").trimEnd()}
8458
+
8459
+ ---
8460
+
8461
+ ${skillBody}` : skillBody;
8462
+ return appendSystemPromptExtras(base);
8463
+ }
8464
+ function appendSystemPromptExtras(base, env = process.env) {
8465
+ const inline = env.AERO_SYSTEM_PROMPT_APPEND?.trim();
8466
+ let fileBody = "";
8467
+ const filePath = env.AERO_SYSTEM_PROMPT_FILE?.trim();
8468
+ if (filePath) {
8469
+ try {
8470
+ fileBody = fs7.readFileSync(filePath, "utf-8").trim();
8471
+ } catch {
8472
+ fileBody = "";
8473
+ }
8474
+ }
8475
+ const extras = [inline, fileBody].filter((s) => !!s && s.length > 0);
8476
+ if (extras.length === 0) return base;
8477
+ return `${base.trimEnd()}
8315
8478
 
8316
8479
  ---
8317
8480
 
8318
- ${skillBody}`;
8481
+ ${extras.join("\n\n")}`;
8319
8482
  }
8320
8483
  function missingProviderMessage() {
8321
8484
  const configHints = agentProvidersByPriority().join(", ");
8322
- const envHints = agentProvidersByPriority().map((p) => `${AGENT_PROVIDERS[p].piAiProvider.toUpperCase()}_API_KEY`).join(" / ");
8485
+ const envHints = agentProvidersByPriority().map(agentProviderApiKeyEnvVar).join(" / ");
8323
8486
  return `No agent LLM provider configured. Add an API key for one of: ${configHints} in ~/.canonry/config.yaml, or export ${envHints}.`;
8324
8487
  }
8325
8488
  function detectAgentProvider(config) {
@@ -8366,6 +8529,93 @@ function resolveSessionProviderAndModel(config, opts) {
8366
8529
  return { provider, modelId };
8367
8530
  }
8368
8531
 
8532
+ // src/agent/remote-mcp.ts
8533
+ import { Type as Type3 } from "@sinclair/typebox";
8534
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8535
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8536
+ var log18 = createLogger("RemoteMcp");
8537
+ var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
8538
+ var MAX_TOOL_RESULT_CHARS2 = 2e4;
8539
+ function truncate(text) {
8540
+ if (text.length <= MAX_TOOL_RESULT_CHARS2) return text;
8541
+ return text.slice(0, MAX_TOOL_RESULT_CHARS2) + "\n... (truncated, result too large)";
8542
+ }
8543
+ async function connectStreamableHttp(server) {
8544
+ const transport = new StreamableHTTPClientTransport(new URL(server.url), {
8545
+ requestInit: {
8546
+ headers: { Authorization: `Bearer ${server.token}` }
8547
+ }
8548
+ });
8549
+ const client = new Client(
8550
+ { name: "canonry-aero", version: "1.0.0" },
8551
+ { capabilities: {} }
8552
+ );
8553
+ await client.connect(transport);
8554
+ return client;
8555
+ }
8556
+ function isReadOnly(tool) {
8557
+ return tool.annotations?.readOnlyHint === true;
8558
+ }
8559
+ function adaptRemoteTool(client, tool) {
8560
+ const parameters = Type3.Unsafe(
8561
+ tool.inputSchema ?? { type: "object", properties: {} }
8562
+ );
8563
+ const execute = async (_toolCallId, params) => {
8564
+ const result = await client.callTool({ name: tool.name, arguments: params });
8565
+ return {
8566
+ content: [{ type: "text", text: truncate(JSON.stringify(result, null, 2)) }],
8567
+ details: result
8568
+ };
8569
+ };
8570
+ return {
8571
+ name: tool.name,
8572
+ label: tool.annotations?.title ?? tool.name,
8573
+ description: tool.description ?? "",
8574
+ parameters,
8575
+ execute
8576
+ };
8577
+ }
8578
+ async function loadExternalMcpTools(servers, opts = {}) {
8579
+ if (!servers || servers.length === 0) return [];
8580
+ const connect = opts.connect ?? connectStreamableHttp;
8581
+ const excluded = opts.excluded ?? AERO_EXCLUDED_MCP_TOOLS2;
8582
+ const tools = [];
8583
+ for (const server of servers) {
8584
+ const label = server.label ?? server.url;
8585
+ let client;
8586
+ try {
8587
+ client = await connect(server);
8588
+ } catch (err) {
8589
+ log18.error("external-mcp.connect-failed", {
8590
+ label,
8591
+ error: err instanceof Error ? err.message : String(err)
8592
+ });
8593
+ continue;
8594
+ }
8595
+ let listed;
8596
+ try {
8597
+ listed = await client.listTools();
8598
+ } catch (err) {
8599
+ log18.error("external-mcp.list-failed", {
8600
+ label,
8601
+ error: err instanceof Error ? err.message : String(err)
8602
+ });
8603
+ continue;
8604
+ }
8605
+ for (const tool of listed.tools) {
8606
+ if (excluded.has(tool.name)) continue;
8607
+ if (!isReadOnly(tool)) continue;
8608
+ tools.push(adaptRemoteTool(client, tool));
8609
+ }
8610
+ log18.info("external-mcp.loaded", {
8611
+ label,
8612
+ discovered: listed.tools.length,
8613
+ adopted: tools.length
8614
+ });
8615
+ }
8616
+ return tools;
8617
+ }
8618
+
8369
8619
  // src/agent/memory-store.ts
8370
8620
  import crypto18 from "crypto";
8371
8621
  import { and as and13, desc as desc6, eq as eq17, like, sql as sql5 } from "drizzle-orm";
@@ -8589,7 +8839,7 @@ async function compactMessages(args) {
8589
8839
  }
8590
8840
 
8591
8841
  // src/agent/session-registry.ts
8592
- var log18 = createLogger("SessionRegistry");
8842
+ var log19 = createLogger("SessionRegistry");
8593
8843
  var MAX_HYDRATE_NOTES = 20;
8594
8844
  var MAX_HYDRATE_BYTES = 32 * 1024;
8595
8845
  function escapeMemoryFragment(value) {
@@ -8608,10 +8858,53 @@ var SessionRegistry = class {
8608
8858
  * awaits the same promise instead of kicking off a duplicate LLM call.
8609
8859
  */
8610
8860
  compactions = /* @__PURE__ */ new Map();
8861
+ /**
8862
+ * Read-only tools loaded once from the injected remote MCP servers (OSS-A).
8863
+ * The server set is static config, so we connect + adapt a single time and
8864
+ * cache the promise; every session merges the same AgentTool list. Resolves
8865
+ * to `[]` when no servers are configured or all fail to connect (the load is
8866
+ * fail-soft and never throws).
8867
+ */
8868
+ externalToolsPromise;
8611
8869
  opts;
8612
8870
  constructor(opts) {
8613
8871
  this.opts = opts;
8614
8872
  }
8873
+ /**
8874
+ * Lazily load + cache the injected remote MCP tools. The seam OSS-A exposes:
8875
+ * `loadExternalMcpTools` connects to each configured server over the frozen
8876
+ * bearer-gated Streamable HTTP transport, lists tools, and adapts the
8877
+ * read-only, non-excluded ones into AgentTools. Cached so we connect once per
8878
+ * registry lifetime regardless of how many projects or turns run.
8879
+ */
8880
+ loadExternalTools() {
8881
+ if (!this.externalToolsPromise) {
8882
+ this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
8883
+ (err) => {
8884
+ log19.error("external-mcp.load-failed", {
8885
+ error: err instanceof Error ? err.message : String(err)
8886
+ });
8887
+ return [];
8888
+ }
8889
+ );
8890
+ }
8891
+ return this.externalToolsPromise;
8892
+ }
8893
+ /**
8894
+ * Append the cached external MCP tools to the live agent's tool list, idempotently.
8895
+ * Called after scope/model alignment in `acquireForTurn` (which rebuilds
8896
+ * `state.tools` from the local registry), so the remote read-only tools ride
8897
+ * alongside the local ones for the upcoming turn. No-op when none are configured.
8898
+ */
8899
+ async mergeExternalTools(agent) {
8900
+ const external = await this.loadExternalTools();
8901
+ if (external.length === 0) return;
8902
+ const current = agent.state.tools;
8903
+ const present = new Set(current.map((t) => t.name));
8904
+ const additions = external.filter((t) => !present.has(t.name));
8905
+ if (additions.length === 0) return;
8906
+ agent.state.tools = [...current, ...additions];
8907
+ }
8615
8908
  /** Read-only access to the config snapshot the registry was built with. */
8616
8909
  getConfig() {
8617
8910
  return this.opts.config;
@@ -8770,6 +9063,7 @@ ${lines.join("\n")}
8770
9063
  if (preferences?.provider || preferences?.modelId) {
8771
9064
  this.alignModel(projectName, agent, preferences);
8772
9065
  }
9066
+ await this.mergeExternalTools(agent);
8773
9067
  await this.maybeCompact(projectName, agent);
8774
9068
  return agent;
8775
9069
  }
@@ -8816,13 +9110,13 @@ ${lines.join("\n")}
8816
9110
  agent.state.messages = result.messages;
8817
9111
  agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
8818
9112
  this.save(projectName);
8819
- log18.info("compaction.completed", {
9113
+ log19.info("compaction.completed", {
8820
9114
  projectName,
8821
9115
  removedCount: result.removedCount,
8822
9116
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
8823
9117
  });
8824
9118
  } catch (err) {
8825
- log18.error("compaction.failed", {
9119
+ log19.error("compaction.failed", {
8826
9120
  projectName,
8827
9121
  error: err instanceof Error ? err.message : String(err)
8828
9122
  });
@@ -8919,7 +9213,7 @@ ${lines.join("\n")}
8919
9213
  await agent.prompt(msgs);
8920
9214
  this.save(projectName);
8921
9215
  } catch (err) {
8922
- log18.error("drain.failed", {
9216
+ log19.error("drain.failed", {
8923
9217
  projectName,
8924
9218
  error: err instanceof Error ? err.message : String(err)
8925
9219
  });
@@ -9273,7 +9567,7 @@ function pickExplainProvider(config, providerOverride) {
9273
9567
  for (const provider of agentProvidersByPriority()) {
9274
9568
  if (resolveApiKeyFor(provider, config)) return provider;
9275
9569
  }
9276
- const hints = agentProvidersByPriority().map((p) => `${AGENT_PROVIDERS[p].piAiProvider.toUpperCase()}_API_KEY`).join(" / ");
9570
+ const hints = agentProvidersByPriority().map(agentProviderApiKeyEnvVar).join(" / ");
9277
9571
  throw providerError(
9278
9572
  `No LLM provider configured. Add an API key in ~/.canonry/config.yaml or set one of: ${hints}.`
9279
9573
  );
@@ -9517,7 +9811,7 @@ function formatAuditFactorScore(factor) {
9517
9811
  }
9518
9812
 
9519
9813
  // src/snapshot-service.ts
9520
- var log19 = createLogger("Snapshot");
9814
+ var log20 = createLogger("Snapshot");
9521
9815
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
9522
9816
  var SNAPSHOT_QUERY_COUNT = 6;
9523
9817
  var ProviderExecutionGate2 = class {
@@ -9663,7 +9957,7 @@ var SnapshotService = class {
9663
9957
  return mapAuditReport(report);
9664
9958
  } catch (err) {
9665
9959
  const message = err instanceof Error ? err.message : String(err);
9666
- log19.warn("audit.failed", { homepageUrl, error: message });
9960
+ log20.warn("audit.failed", { homepageUrl, error: message });
9667
9961
  return {
9668
9962
  url: homepageUrl,
9669
9963
  finalUrl: homepageUrl,
@@ -9692,7 +9986,7 @@ var SnapshotService = class {
9692
9986
  queries: parsedQueries
9693
9987
  };
9694
9988
  } catch (err) {
9695
- log19.warn("profile.generation-failed", {
9989
+ log20.warn("profile.generation-failed", {
9696
9990
  domain: ctx.domain,
9697
9991
  provider: ctx.analysisProvider.adapter.name,
9698
9992
  error: err instanceof Error ? err.message : String(err)
@@ -9834,7 +10128,7 @@ var SnapshotService = class {
9834
10128
  recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
9835
10129
  };
9836
10130
  } catch (err) {
9837
- log19.warn("response.analysis-failed", {
10131
+ log20.warn("response.analysis-failed", {
9838
10132
  provider: ctx.analysisProvider.adapter.name,
9839
10133
  error: err instanceof Error ? err.message : String(err)
9840
10134
  });
@@ -10116,7 +10410,7 @@ function clipText(value, length) {
10116
10410
  // src/server.ts
10117
10411
  var _require3 = createRequire3(import.meta.url);
10118
10412
  var { version: PKG_VERSION2 } = _require3("../package.json");
10119
- var log20 = createLogger("Server");
10413
+ var log21 = createLogger("Server");
10120
10414
  var DEFAULT_QUOTA = {
10121
10415
  maxConcurrency: 2,
10122
10416
  maxRequestsPerMinute: 10,
@@ -10249,7 +10543,7 @@ function applyLegacyCredentials(rows, config) {
10249
10543
  }
10250
10544
  if (migratedGoogle > 0) {
10251
10545
  saveConfigPatch({ google: config.google });
10252
- log20.info("credentials.migrated", { type: "google", count: migratedGoogle });
10546
+ log21.info("credentials.migrated", { type: "google", count: migratedGoogle });
10253
10547
  }
10254
10548
  let migratedGa4 = 0;
10255
10549
  for (const row of rows.ga4) {
@@ -10267,7 +10561,7 @@ function applyLegacyCredentials(rows, config) {
10267
10561
  }
10268
10562
  if (migratedGa4 > 0) {
10269
10563
  saveConfigPatch({ ga4: config.ga4 });
10270
- log20.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
10564
+ log21.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
10271
10565
  }
10272
10566
  }
10273
10567
  function isLoopbackBindHost(host) {
@@ -10306,11 +10600,11 @@ async function createServer(opts) {
10306
10600
  applyLegacyCredentials(legacyRows, opts.config);
10307
10601
  dropLegacyCredentialColumns(opts.db);
10308
10602
  } catch (err) {
10309
- log20.warn("credentials.migration.failed", {
10603
+ log21.warn("credentials.migration.failed", {
10310
10604
  error: err instanceof Error ? err.message : String(err)
10311
10605
  });
10312
10606
  }
10313
- log20.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
10607
+ log21.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
10314
10608
  const p = providers[k];
10315
10609
  return p?.apiKey || p?.baseUrl || p?.vertexProject;
10316
10610
  }) });
@@ -10892,6 +11186,9 @@ async function createServer(opts) {
10892
11186
  },
10893
11187
  getGoogleAuthConfig: () => getGoogleAuthConfig(opts.config),
10894
11188
  getPlacesConfig: () => getPlacesConfig(opts.config),
11189
+ // Resolved fresh each call so a key added at runtime (settings API) shows
11190
+ // up immediately in the `config.agent-providers` doctor check.
11191
+ getAgentProviderSummary: () => buildAgentProvidersResponse(opts.config).providers,
10895
11192
  googleConnectionStore,
10896
11193
  googleStateSecret,
10897
11194
  publicUrl: opts.config.publicUrl,
@@ -1434,7 +1434,8 @@ var ProviderIds = {
1434
1434
  perplexity: "perplexity",
1435
1435
  local: "local",
1436
1436
  cdpChatgpt: "cdp:chatgpt",
1437
- zai: "zai"
1437
+ zai: "zai",
1438
+ deepinfra: "deepinfra"
1438
1439
  };
1439
1440
  var PROVIDER_IDS = Object.values(ProviderIds);
1440
1441
  var SweepProviderIds = {
@@ -1450,7 +1451,8 @@ var AgentProviderIds = {
1450
1451
  claude: ProviderIds.claude,
1451
1452
  openai: ProviderIds.openai,
1452
1453
  gemini: ProviderIds.gemini,
1453
- zai: ProviderIds.zai
1454
+ zai: ProviderIds.zai,
1455
+ deepinfra: ProviderIds.deepinfra
1454
1456
  };
1455
1457
  var AGENT_PROVIDER_IDS = Object.values(AgentProviderIds);
1456
1458
  function isAgentProviderId(value) {
@@ -3027,7 +3029,7 @@ function escapeRegExp(value) {
3027
3029
 
3028
3030
  // ../contracts/src/agent.ts
3029
3031
  import { z as z22 } from "zod";
3030
- var agentProviderIdSchema = z22.enum(["claude", "openai", "gemini", "zai"]);
3032
+ var agentProviderIdSchema = z22.enum(["claude", "openai", "gemini", "zai", "deepinfra"]);
3031
3033
  var agentProviderOptionDtoSchema = z22.object({
3032
3034
  /** Stable identifier — what clients pass back as `provider` on the prompt endpoint. */
3033
3035
  id: agentProviderIdSchema,