@ainyc/canonry 4.98.0 → 4.99.1
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/{BacklinksPage-CuWs7I1D.js → BacklinksPage-v2RC8wlg.js} +1 -1
- package/assets/assets/{ChartPrimitives-bm37tprp.js → ChartPrimitives-B74e5pqn.js} +1 -1
- package/assets/assets/ProjectPage-BCzS7TQn.js +6 -0
- package/assets/assets/{RunRow-DzhQl87g.js → RunRow-BmVPjO-m.js} +1 -1
- package/assets/assets/{RunsPage-wOaewhbF.js → RunsPage-CK5atXvL.js} +1 -1
- package/assets/assets/{SettingsPage-CJdhhkkE.js → SettingsPage-DarPFUPl.js} +1 -1
- package/assets/assets/{TrafficPage-BJBeN5Ph.js → TrafficPage-Ddnn_WqU.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D3PmH0B1.js → TrafficSourceDetailPage-g08SKnwZ.js} +1 -1
- package/assets/assets/{arrow-left-BmITMI6O.js → arrow-left-D39Vxasb.js} +1 -1
- package/assets/assets/{extract-error-message-BGwnyAfP.js → extract-error-message-BiNWqBeh.js} +1 -1
- package/assets/assets/{index-BsZNCwg8.css → index-BCuWbxUj.css} +1 -1
- package/assets/assets/index-Dd4J1VhB.js +210 -0
- package/assets/assets/{trash-2-Bd2mkP_w.js → trash-2-xh6gsnap.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-7COFPNMH.js → chunk-4Q3LBD7A.js} +470 -131
- package/dist/{chunk-3TH54726.js → chunk-IDI6LMYL.js} +27 -1
- package/dist/{chunk-CWFTMFEU.js → chunk-JF7EXHGA.js} +12 -3
- package/dist/{chunk-DJVUNURL.js → chunk-YBIT5O62.js} +36 -29
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +16 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-QHBXW4KI.js → intelligence-service-TWCEKVKZ.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +7 -7
- package/assets/assets/ProjectPage-BksYe8FP.js +0 -6
- package/assets/assets/index-BSX6r6ju.js +0 -210
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDI6LMYL.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-
|
|
114
|
+
} from "./chunk-YBIT5O62.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-
|
|
174
|
+
} from "./chunk-JF7EXHGA.js";
|
|
175
175
|
|
|
176
176
|
// src/telemetry.ts
|
|
177
177
|
import crypto from "crypto";
|
|
@@ -1473,7 +1473,8 @@ function extractWebSearchToolErrors(rawResponse) {
|
|
|
1473
1473
|
if (!content) return [];
|
|
1474
1474
|
for (const block of content) {
|
|
1475
1475
|
if (block.type !== "web_search_tool_result") continue;
|
|
1476
|
-
if (block.content === null || typeof block.content !== "object" || Array.isArray(block.content))
|
|
1476
|
+
if (block.content === null || typeof block.content !== "object" || Array.isArray(block.content))
|
|
1477
|
+
continue;
|
|
1477
1478
|
const errorCode = block.content.error_code;
|
|
1478
1479
|
if (typeof errorCode === "string" && errorCode.length > 0) {
|
|
1479
1480
|
errors.add(errorCode);
|
|
@@ -6402,7 +6403,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
6402
6403
|
return result;
|
|
6403
6404
|
}
|
|
6404
6405
|
async function backfillInsightsCommand(project, opts) {
|
|
6405
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
6406
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-TWCEKVKZ.js");
|
|
6406
6407
|
const config = loadConfig();
|
|
6407
6408
|
const db = createClient(config.database);
|
|
6408
6409
|
migrate(db);
|
|
@@ -8331,13 +8332,63 @@ function buildSkillDocTools() {
|
|
|
8331
8332
|
// src/agent/mcp-to-agent-tool.ts
|
|
8332
8333
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
8333
8334
|
var MAX_TOOL_RESULT_CHARS = 2e4;
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
return
|
|
8335
|
+
var TRUNCATION_NOTE = "... (truncated \u2014 result too large)";
|
|
8336
|
+
function serializeResult(value) {
|
|
8337
|
+
return JSON.stringify(value, null, 2);
|
|
8338
|
+
}
|
|
8339
|
+
function largestArrayKey(obj) {
|
|
8340
|
+
let best;
|
|
8341
|
+
let bestLen = -1;
|
|
8342
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
8343
|
+
if (!Array.isArray(v)) continue;
|
|
8344
|
+
const len = serializeResult(v).length;
|
|
8345
|
+
if (len > bestLen) {
|
|
8346
|
+
bestLen = len;
|
|
8347
|
+
best = k;
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8350
|
+
return best;
|
|
8351
|
+
}
|
|
8352
|
+
function trimRowsToFit(rows, render) {
|
|
8353
|
+
let kept = rows.slice();
|
|
8354
|
+
while (kept.length > 0 && render(kept).length > MAX_TOOL_RESULT_CHARS) {
|
|
8355
|
+
kept = kept.slice(0, -1);
|
|
8356
|
+
}
|
|
8357
|
+
return kept;
|
|
8358
|
+
}
|
|
8359
|
+
function truncateToolResult(details) {
|
|
8360
|
+
const full = serializeResult(details);
|
|
8361
|
+
if (full.length <= MAX_TOOL_RESULT_CHARS) return full;
|
|
8362
|
+
if (Array.isArray(details)) {
|
|
8363
|
+
const kept = trimRowsToFit(
|
|
8364
|
+
details,
|
|
8365
|
+
(rows) => serializeResult({ items: rows, __truncated: true, __omittedRows: details.length - rows.length })
|
|
8366
|
+
);
|
|
8367
|
+
return serializeResult({ items: kept, __truncated: true, __omittedRows: details.length - kept.length });
|
|
8368
|
+
}
|
|
8369
|
+
if (details && typeof details === "object") {
|
|
8370
|
+
const obj = details;
|
|
8371
|
+
const arrayKey = largestArrayKey(obj);
|
|
8372
|
+
if (arrayKey) {
|
|
8373
|
+
const rows = obj[arrayKey];
|
|
8374
|
+
const kept = trimRowsToFit(
|
|
8375
|
+
rows,
|
|
8376
|
+
(r) => serializeResult({ ...obj, [arrayKey]: r, __truncated: true, __omittedRows: rows.length - r.length })
|
|
8377
|
+
);
|
|
8378
|
+
const out = serializeResult({
|
|
8379
|
+
...obj,
|
|
8380
|
+
[arrayKey]: kept,
|
|
8381
|
+
__truncated: true,
|
|
8382
|
+
__omittedRows: rows.length - kept.length
|
|
8383
|
+
});
|
|
8384
|
+
if (out.length <= MAX_TOOL_RESULT_CHARS) return out;
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
|
|
8337
8388
|
}
|
|
8338
8389
|
function textResult2(details) {
|
|
8339
8390
|
return {
|
|
8340
|
-
content: [{ type: "text", text:
|
|
8391
|
+
content: [{ type: "text", text: truncateToolResult(details) }],
|
|
8341
8392
|
details
|
|
8342
8393
|
};
|
|
8343
8394
|
}
|
|
@@ -8404,13 +8455,31 @@ function loadAeroSystemPrompt(pkgDir) {
|
|
|
8404
8455
|
const skillDir = resolveAeroSkillDir(pkgDir);
|
|
8405
8456
|
const skillBody = fs7.readFileSync(path8.join(skillDir, "SKILL.md"), "utf-8");
|
|
8406
8457
|
const soulPath = path8.join(skillDir, "soul.md");
|
|
8407
|
-
|
|
8408
|
-
const soulBody = fs7.readFileSync(soulPath, "utf-8");
|
|
8409
|
-
return `${soulBody.trimEnd()}
|
|
8458
|
+
const base = fs7.existsSync(soulPath) ? `${fs7.readFileSync(soulPath, "utf-8").trimEnd()}
|
|
8410
8459
|
|
|
8411
8460
|
---
|
|
8412
8461
|
|
|
8413
|
-
${skillBody}
|
|
8462
|
+
${skillBody}` : skillBody;
|
|
8463
|
+
return appendSystemPromptExtras(base);
|
|
8464
|
+
}
|
|
8465
|
+
function appendSystemPromptExtras(base, env = process.env) {
|
|
8466
|
+
const inline = env.AERO_SYSTEM_PROMPT_APPEND?.trim();
|
|
8467
|
+
let fileBody = "";
|
|
8468
|
+
const filePath = env.AERO_SYSTEM_PROMPT_FILE?.trim();
|
|
8469
|
+
if (filePath) {
|
|
8470
|
+
try {
|
|
8471
|
+
fileBody = fs7.readFileSync(filePath, "utf-8").trim();
|
|
8472
|
+
} catch {
|
|
8473
|
+
fileBody = "";
|
|
8474
|
+
}
|
|
8475
|
+
}
|
|
8476
|
+
const extras = [inline, fileBody].filter((s) => !!s && s.length > 0);
|
|
8477
|
+
if (extras.length === 0) return base;
|
|
8478
|
+
return `${base.trimEnd()}
|
|
8479
|
+
|
|
8480
|
+
---
|
|
8481
|
+
|
|
8482
|
+
${extras.join("\n\n")}`;
|
|
8414
8483
|
}
|
|
8415
8484
|
function missingProviderMessage() {
|
|
8416
8485
|
const configHints = agentProvidersByPriority().join(", ");
|
|
@@ -8461,6 +8530,93 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
8461
8530
|
return { provider, modelId };
|
|
8462
8531
|
}
|
|
8463
8532
|
|
|
8533
|
+
// src/agent/remote-mcp.ts
|
|
8534
|
+
import { Type as Type3 } from "@sinclair/typebox";
|
|
8535
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8536
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8537
|
+
var log18 = createLogger("RemoteMcp");
|
|
8538
|
+
var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
|
|
8539
|
+
var MAX_TOOL_RESULT_CHARS2 = 2e4;
|
|
8540
|
+
function truncate(text) {
|
|
8541
|
+
if (text.length <= MAX_TOOL_RESULT_CHARS2) return text;
|
|
8542
|
+
return text.slice(0, MAX_TOOL_RESULT_CHARS2) + "\n... (truncated, result too large)";
|
|
8543
|
+
}
|
|
8544
|
+
async function connectStreamableHttp(server) {
|
|
8545
|
+
const transport = new StreamableHTTPClientTransport(new URL(server.url), {
|
|
8546
|
+
requestInit: {
|
|
8547
|
+
headers: { Authorization: `Bearer ${server.token}` }
|
|
8548
|
+
}
|
|
8549
|
+
});
|
|
8550
|
+
const client = new Client(
|
|
8551
|
+
{ name: "canonry-aero", version: "1.0.0" },
|
|
8552
|
+
{ capabilities: {} }
|
|
8553
|
+
);
|
|
8554
|
+
await client.connect(transport);
|
|
8555
|
+
return client;
|
|
8556
|
+
}
|
|
8557
|
+
function isReadOnly(tool) {
|
|
8558
|
+
return tool.annotations?.readOnlyHint === true;
|
|
8559
|
+
}
|
|
8560
|
+
function adaptRemoteTool(client, tool) {
|
|
8561
|
+
const parameters = Type3.Unsafe(
|
|
8562
|
+
tool.inputSchema ?? { type: "object", properties: {} }
|
|
8563
|
+
);
|
|
8564
|
+
const execute = async (_toolCallId, params) => {
|
|
8565
|
+
const result = await client.callTool({ name: tool.name, arguments: params });
|
|
8566
|
+
return {
|
|
8567
|
+
content: [{ type: "text", text: truncate(JSON.stringify(result, null, 2)) }],
|
|
8568
|
+
details: result
|
|
8569
|
+
};
|
|
8570
|
+
};
|
|
8571
|
+
return {
|
|
8572
|
+
name: tool.name,
|
|
8573
|
+
label: tool.annotations?.title ?? tool.name,
|
|
8574
|
+
description: tool.description ?? "",
|
|
8575
|
+
parameters,
|
|
8576
|
+
execute
|
|
8577
|
+
};
|
|
8578
|
+
}
|
|
8579
|
+
async function loadExternalMcpTools(servers, opts = {}) {
|
|
8580
|
+
if (!servers || servers.length === 0) return [];
|
|
8581
|
+
const connect = opts.connect ?? connectStreamableHttp;
|
|
8582
|
+
const excluded = opts.excluded ?? AERO_EXCLUDED_MCP_TOOLS2;
|
|
8583
|
+
const tools = [];
|
|
8584
|
+
for (const server of servers) {
|
|
8585
|
+
const label = server.label ?? server.url;
|
|
8586
|
+
let client;
|
|
8587
|
+
try {
|
|
8588
|
+
client = await connect(server);
|
|
8589
|
+
} catch (err) {
|
|
8590
|
+
log18.error("external-mcp.connect-failed", {
|
|
8591
|
+
label,
|
|
8592
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8593
|
+
});
|
|
8594
|
+
continue;
|
|
8595
|
+
}
|
|
8596
|
+
let listed;
|
|
8597
|
+
try {
|
|
8598
|
+
listed = await client.listTools();
|
|
8599
|
+
} catch (err) {
|
|
8600
|
+
log18.error("external-mcp.list-failed", {
|
|
8601
|
+
label,
|
|
8602
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8603
|
+
});
|
|
8604
|
+
continue;
|
|
8605
|
+
}
|
|
8606
|
+
for (const tool of listed.tools) {
|
|
8607
|
+
if (excluded.has(tool.name)) continue;
|
|
8608
|
+
if (!isReadOnly(tool)) continue;
|
|
8609
|
+
tools.push(adaptRemoteTool(client, tool));
|
|
8610
|
+
}
|
|
8611
|
+
log18.info("external-mcp.loaded", {
|
|
8612
|
+
label,
|
|
8613
|
+
discovered: listed.tools.length,
|
|
8614
|
+
adopted: tools.length
|
|
8615
|
+
});
|
|
8616
|
+
}
|
|
8617
|
+
return tools;
|
|
8618
|
+
}
|
|
8619
|
+
|
|
8464
8620
|
// src/agent/memory-store.ts
|
|
8465
8621
|
import crypto18 from "crypto";
|
|
8466
8622
|
import { and as and13, desc as desc6, eq as eq17, like, sql as sql5 } from "drizzle-orm";
|
|
@@ -8684,7 +8840,7 @@ async function compactMessages(args) {
|
|
|
8684
8840
|
}
|
|
8685
8841
|
|
|
8686
8842
|
// src/agent/session-registry.ts
|
|
8687
|
-
var
|
|
8843
|
+
var log19 = createLogger("SessionRegistry");
|
|
8688
8844
|
var MAX_HYDRATE_NOTES = 20;
|
|
8689
8845
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
8690
8846
|
function escapeMemoryFragment(value) {
|
|
@@ -8703,10 +8859,53 @@ var SessionRegistry = class {
|
|
|
8703
8859
|
* awaits the same promise instead of kicking off a duplicate LLM call.
|
|
8704
8860
|
*/
|
|
8705
8861
|
compactions = /* @__PURE__ */ new Map();
|
|
8862
|
+
/**
|
|
8863
|
+
* Read-only tools loaded once from the injected remote MCP servers (OSS-A).
|
|
8864
|
+
* The server set is static config, so we connect + adapt a single time and
|
|
8865
|
+
* cache the promise; every session merges the same AgentTool list. Resolves
|
|
8866
|
+
* to `[]` when no servers are configured or all fail to connect (the load is
|
|
8867
|
+
* fail-soft and never throws).
|
|
8868
|
+
*/
|
|
8869
|
+
externalToolsPromise;
|
|
8706
8870
|
opts;
|
|
8707
8871
|
constructor(opts) {
|
|
8708
8872
|
this.opts = opts;
|
|
8709
8873
|
}
|
|
8874
|
+
/**
|
|
8875
|
+
* Lazily load + cache the injected remote MCP tools. The seam OSS-A exposes:
|
|
8876
|
+
* `loadExternalMcpTools` connects to each configured server over the frozen
|
|
8877
|
+
* bearer-gated Streamable HTTP transport, lists tools, and adapts the
|
|
8878
|
+
* read-only, non-excluded ones into AgentTools. Cached so we connect once per
|
|
8879
|
+
* registry lifetime regardless of how many projects or turns run.
|
|
8880
|
+
*/
|
|
8881
|
+
loadExternalTools() {
|
|
8882
|
+
if (!this.externalToolsPromise) {
|
|
8883
|
+
this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
|
|
8884
|
+
(err) => {
|
|
8885
|
+
log19.error("external-mcp.load-failed", {
|
|
8886
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8887
|
+
});
|
|
8888
|
+
return [];
|
|
8889
|
+
}
|
|
8890
|
+
);
|
|
8891
|
+
}
|
|
8892
|
+
return this.externalToolsPromise;
|
|
8893
|
+
}
|
|
8894
|
+
/**
|
|
8895
|
+
* Append the cached external MCP tools to the live agent's tool list, idempotently.
|
|
8896
|
+
* Called after scope/model alignment in `acquireForTurn` (which rebuilds
|
|
8897
|
+
* `state.tools` from the local registry), so the remote read-only tools ride
|
|
8898
|
+
* alongside the local ones for the upcoming turn. No-op when none are configured.
|
|
8899
|
+
*/
|
|
8900
|
+
async mergeExternalTools(agent) {
|
|
8901
|
+
const external = await this.loadExternalTools();
|
|
8902
|
+
if (external.length === 0) return;
|
|
8903
|
+
const current = agent.state.tools;
|
|
8904
|
+
const present = new Set(current.map((t) => t.name));
|
|
8905
|
+
const additions = external.filter((t) => !present.has(t.name));
|
|
8906
|
+
if (additions.length === 0) return;
|
|
8907
|
+
agent.state.tools = [...current, ...additions];
|
|
8908
|
+
}
|
|
8710
8909
|
/** Read-only access to the config snapshot the registry was built with. */
|
|
8711
8910
|
getConfig() {
|
|
8712
8911
|
return this.opts.config;
|
|
@@ -8865,6 +9064,7 @@ ${lines.join("\n")}
|
|
|
8865
9064
|
if (preferences?.provider || preferences?.modelId) {
|
|
8866
9065
|
this.alignModel(projectName, agent, preferences);
|
|
8867
9066
|
}
|
|
9067
|
+
await this.mergeExternalTools(agent);
|
|
8868
9068
|
await this.maybeCompact(projectName, agent);
|
|
8869
9069
|
return agent;
|
|
8870
9070
|
}
|
|
@@ -8911,13 +9111,13 @@ ${lines.join("\n")}
|
|
|
8911
9111
|
agent.state.messages = result.messages;
|
|
8912
9112
|
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
8913
9113
|
this.save(projectName);
|
|
8914
|
-
|
|
9114
|
+
log19.info("compaction.completed", {
|
|
8915
9115
|
projectName,
|
|
8916
9116
|
removedCount: result.removedCount,
|
|
8917
9117
|
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
8918
9118
|
});
|
|
8919
9119
|
} catch (err) {
|
|
8920
|
-
|
|
9120
|
+
log19.error("compaction.failed", {
|
|
8921
9121
|
projectName,
|
|
8922
9122
|
error: err instanceof Error ? err.message : String(err)
|
|
8923
9123
|
});
|
|
@@ -9014,7 +9214,7 @@ ${lines.join("\n")}
|
|
|
9014
9214
|
await agent.prompt(msgs);
|
|
9015
9215
|
this.save(projectName);
|
|
9016
9216
|
} catch (err) {
|
|
9017
|
-
|
|
9217
|
+
log19.error("drain.failed", {
|
|
9018
9218
|
projectName,
|
|
9019
9219
|
error: err instanceof Error ? err.message : String(err)
|
|
9020
9220
|
});
|
|
@@ -9612,7 +9812,7 @@ function formatAuditFactorScore(factor) {
|
|
|
9612
9812
|
}
|
|
9613
9813
|
|
|
9614
9814
|
// src/snapshot-service.ts
|
|
9615
|
-
var
|
|
9815
|
+
var log20 = createLogger("Snapshot");
|
|
9616
9816
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
9617
9817
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
9618
9818
|
var ProviderExecutionGate2 = class {
|
|
@@ -9758,7 +9958,7 @@ var SnapshotService = class {
|
|
|
9758
9958
|
return mapAuditReport(report);
|
|
9759
9959
|
} catch (err) {
|
|
9760
9960
|
const message = err instanceof Error ? err.message : String(err);
|
|
9761
|
-
|
|
9961
|
+
log20.warn("audit.failed", { homepageUrl, error: message });
|
|
9762
9962
|
return {
|
|
9763
9963
|
url: homepageUrl,
|
|
9764
9964
|
finalUrl: homepageUrl,
|
|
@@ -9787,7 +9987,7 @@ var SnapshotService = class {
|
|
|
9787
9987
|
queries: parsedQueries
|
|
9788
9988
|
};
|
|
9789
9989
|
} catch (err) {
|
|
9790
|
-
|
|
9990
|
+
log20.warn("profile.generation-failed", {
|
|
9791
9991
|
domain: ctx.domain,
|
|
9792
9992
|
provider: ctx.analysisProvider.adapter.name,
|
|
9793
9993
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -9929,7 +10129,7 @@ var SnapshotService = class {
|
|
|
9929
10129
|
recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
|
|
9930
10130
|
};
|
|
9931
10131
|
} catch (err) {
|
|
9932
|
-
|
|
10132
|
+
log20.warn("response.analysis-failed", {
|
|
9933
10133
|
provider: ctx.analysisProvider.adapter.name,
|
|
9934
10134
|
error: err instanceof Error ? err.message : String(err)
|
|
9935
10135
|
});
|
|
@@ -10211,7 +10411,7 @@ function clipText(value, length) {
|
|
|
10211
10411
|
// src/server.ts
|
|
10212
10412
|
var _require3 = createRequire3(import.meta.url);
|
|
10213
10413
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
10214
|
-
var
|
|
10414
|
+
var log21 = createLogger("Server");
|
|
10215
10415
|
var DEFAULT_QUOTA = {
|
|
10216
10416
|
maxConcurrency: 2,
|
|
10217
10417
|
maxRequestsPerMinute: 10,
|
|
@@ -10226,9 +10426,7 @@ var API_ADAPTERS = [
|
|
|
10226
10426
|
localAdapter,
|
|
10227
10427
|
perplexityAdapter
|
|
10228
10428
|
];
|
|
10229
|
-
var BROWSER_ADAPTERS = [
|
|
10230
|
-
cdpChatgptAdapter
|
|
10231
|
-
];
|
|
10429
|
+
var BROWSER_ADAPTERS = [cdpChatgptAdapter];
|
|
10232
10430
|
var adapterMap = Object.fromEntries(
|
|
10233
10431
|
API_ADAPTERS.map((a) => [a.name, a])
|
|
10234
10432
|
);
|
|
@@ -10278,13 +10476,18 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
10278
10476
|
N: DASHBOARD_SCRYPT_COST,
|
|
10279
10477
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
10280
10478
|
});
|
|
10281
|
-
if (derived.length !== expected.length)
|
|
10282
|
-
|
|
10479
|
+
if (derived.length !== expected.length)
|
|
10480
|
+
return { ok: false, needsRehash: false };
|
|
10481
|
+
return {
|
|
10482
|
+
ok: crypto20.timingSafeEqual(derived, expected),
|
|
10483
|
+
needsRehash: false
|
|
10484
|
+
};
|
|
10283
10485
|
}
|
|
10284
10486
|
if (/^[a-f0-9]{64}$/i.test(storedHash)) {
|
|
10285
10487
|
const candidate = Buffer.from(hashApiKey(password), "hex");
|
|
10286
10488
|
const expected = Buffer.from(storedHash, "hex");
|
|
10287
|
-
if (candidate.length !== expected.length)
|
|
10489
|
+
if (candidate.length !== expected.length)
|
|
10490
|
+
return { ok: false, needsRehash: false };
|
|
10288
10491
|
const ok = crypto20.timingSafeEqual(candidate, expected);
|
|
10289
10492
|
return { ok, needsRehash: ok };
|
|
10290
10493
|
}
|
|
@@ -10326,7 +10529,11 @@ function serializeSessionCookie(opts) {
|
|
|
10326
10529
|
function applyLegacyCredentials(rows, config) {
|
|
10327
10530
|
let migratedGoogle = 0;
|
|
10328
10531
|
for (const row of rows.google) {
|
|
10329
|
-
const existing = getGoogleConnection(
|
|
10532
|
+
const existing = getGoogleConnection(
|
|
10533
|
+
config,
|
|
10534
|
+
row.domain,
|
|
10535
|
+
row.connectionType
|
|
10536
|
+
);
|
|
10330
10537
|
if (existing?.refreshToken) continue;
|
|
10331
10538
|
upsertGoogleConnection(config, {
|
|
10332
10539
|
domain: row.domain,
|
|
@@ -10344,7 +10551,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
10344
10551
|
}
|
|
10345
10552
|
if (migratedGoogle > 0) {
|
|
10346
10553
|
saveConfigPatch({ google: config.google });
|
|
10347
|
-
|
|
10554
|
+
log21.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
10348
10555
|
}
|
|
10349
10556
|
let migratedGa4 = 0;
|
|
10350
10557
|
for (const row of rows.ga4) {
|
|
@@ -10362,7 +10569,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
10362
10569
|
}
|
|
10363
10570
|
if (migratedGa4 > 0) {
|
|
10364
10571
|
saveConfigPatch({ ga4: config.ga4 });
|
|
10365
|
-
|
|
10572
|
+
log21.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
10366
10573
|
}
|
|
10367
10574
|
}
|
|
10368
10575
|
function isLoopbackBindHost(host) {
|
|
@@ -10401,14 +10608,16 @@ async function createServer(opts) {
|
|
|
10401
10608
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
10402
10609
|
dropLegacyCredentialColumns(opts.db);
|
|
10403
10610
|
} catch (err) {
|
|
10404
|
-
|
|
10611
|
+
log21.warn("credentials.migration.failed", {
|
|
10405
10612
|
error: err instanceof Error ? err.message : String(err)
|
|
10406
10613
|
});
|
|
10407
10614
|
}
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10615
|
+
log21.info("providers.configured", {
|
|
10616
|
+
providers: Object.keys(providers).filter((k) => {
|
|
10617
|
+
const p = providers[k];
|
|
10618
|
+
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
10619
|
+
})
|
|
10620
|
+
});
|
|
10412
10621
|
for (const adapter of API_ADAPTERS) {
|
|
10413
10622
|
const entry = providers[adapter.name];
|
|
10414
10623
|
if (!entry) continue;
|
|
@@ -10428,7 +10637,11 @@ async function createServer(opts) {
|
|
|
10428
10637
|
}
|
|
10429
10638
|
const cdpConfig = opts.config.cdp;
|
|
10430
10639
|
if (cdpConfig?.host || cdpConfig?.port) {
|
|
10431
|
-
const CDP_DEFAULT_QUOTA = {
|
|
10640
|
+
const CDP_DEFAULT_QUOTA = {
|
|
10641
|
+
maxConcurrency: 1,
|
|
10642
|
+
maxRequestsPerMinute: 4,
|
|
10643
|
+
maxRequestsPerDay: 200
|
|
10644
|
+
};
|
|
10432
10645
|
const cdpEndpoint = `ws://${cdpConfig.host ?? "localhost"}:${cdpConfig.port ?? 9222}`;
|
|
10433
10646
|
registry.register(cdpChatgptAdapter, {
|
|
10434
10647
|
provider: "cdp:chatgpt",
|
|
@@ -10442,7 +10655,9 @@ async function createServer(opts) {
|
|
|
10442
10655
|
jobRunner.recoverStaleRuns();
|
|
10443
10656
|
const notifier = new Notifier(opts.db, serverUrl);
|
|
10444
10657
|
const intelligenceService = new IntelligenceService(opts.db);
|
|
10445
|
-
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
10658
|
+
const aeroClient = new ApiClient(opts.config.apiUrl, opts.config.apiKey, {
|
|
10659
|
+
skipProbe: true
|
|
10660
|
+
});
|
|
10446
10661
|
const agentEnabled = resolveAgentEnabled(process.env, opts.config);
|
|
10447
10662
|
const sessionRegistry = agentEnabled ? new SessionRegistry({
|
|
10448
10663
|
db: opts.db,
|
|
@@ -10489,7 +10704,9 @@ async function createServer(opts) {
|
|
|
10489
10704
|
const runGbpSync = (runId, projectId, syncOpts) => {
|
|
10490
10705
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
10491
10706
|
if (!googleClientId || !googleClientSecret) {
|
|
10492
|
-
app.log.error(
|
|
10707
|
+
app.log.error(
|
|
10708
|
+
"GBP sync requested but Google OAuth credentials are not configured in the local config"
|
|
10709
|
+
);
|
|
10493
10710
|
return;
|
|
10494
10711
|
}
|
|
10495
10712
|
executeGbpSync(opts.db, runId, projectId, {
|
|
@@ -10542,7 +10759,14 @@ async function createServer(opts) {
|
|
|
10542
10759
|
},
|
|
10543
10760
|
onTrafficSyncRequested: (projectName, sourceId) => {
|
|
10544
10761
|
aeroClient.trafficSync(projectName, sourceId).catch((err) => {
|
|
10545
|
-
app.log.error(
|
|
10762
|
+
app.log.error(
|
|
10763
|
+
{
|
|
10764
|
+
projectName,
|
|
10765
|
+
sourceId,
|
|
10766
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10767
|
+
},
|
|
10768
|
+
"Scheduled traffic sync failed"
|
|
10769
|
+
);
|
|
10546
10770
|
});
|
|
10547
10771
|
},
|
|
10548
10772
|
onGbpSyncRequested: (runId, projectId) => {
|
|
@@ -10557,21 +10781,33 @@ async function createServer(opts) {
|
|
|
10557
10781
|
onBacklinksSyncRequested: (projectName) => {
|
|
10558
10782
|
void (async () => {
|
|
10559
10783
|
const probed = await probeLatestRelease().catch((err) => {
|
|
10560
|
-
app.log.warn(
|
|
10784
|
+
app.log.warn(
|
|
10785
|
+
{ projectName, err },
|
|
10786
|
+
"Scheduled backlinks sync: latest-release probe failed"
|
|
10787
|
+
);
|
|
10561
10788
|
return null;
|
|
10562
10789
|
});
|
|
10563
10790
|
if (!probed) return;
|
|
10564
|
-
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10791
|
+
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10792
|
+
and14(
|
|
10793
|
+
eq20(ccReleaseSyncs.release, probed.release),
|
|
10794
|
+
eq20(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
10795
|
+
)
|
|
10796
|
+
).limit(1).get();
|
|
10568
10797
|
if (alreadySynced) {
|
|
10569
|
-
app.log.info(
|
|
10798
|
+
app.log.info(
|
|
10799
|
+
{ projectName, release: probed.release },
|
|
10800
|
+
"Scheduled backlinks sync: already up to date, skipping"
|
|
10801
|
+
);
|
|
10570
10802
|
return;
|
|
10571
10803
|
}
|
|
10572
10804
|
aeroClient.backlinksTriggerSync(probed.release).catch((err) => {
|
|
10573
10805
|
app.log.error(
|
|
10574
|
-
{
|
|
10806
|
+
{
|
|
10807
|
+
projectName,
|
|
10808
|
+
release: probed.release,
|
|
10809
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10810
|
+
},
|
|
10575
10811
|
"Scheduled backlinks sync failed"
|
|
10576
10812
|
);
|
|
10577
10813
|
});
|
|
@@ -10580,7 +10816,10 @@ async function createServer(opts) {
|
|
|
10580
10816
|
if (project && bingConnectionStore.getConnection(project.canonicalDomain)) {
|
|
10581
10817
|
aeroClient.backlinksBingSync(projectName).catch((err) => {
|
|
10582
10818
|
app.log.error(
|
|
10583
|
-
{
|
|
10819
|
+
{
|
|
10820
|
+
projectName,
|
|
10821
|
+
err: err instanceof Error ? err.message : String(err)
|
|
10822
|
+
},
|
|
10584
10823
|
"Scheduled Bing backlinks sync failed"
|
|
10585
10824
|
);
|
|
10586
10825
|
});
|
|
@@ -10602,7 +10841,9 @@ async function createServer(opts) {
|
|
|
10602
10841
|
vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
|
|
10603
10842
|
}));
|
|
10604
10843
|
const googleSettingsSummary = {
|
|
10605
|
-
configured: Boolean(
|
|
10844
|
+
configured: Boolean(
|
|
10845
|
+
opts.config.google?.clientId && opts.config.google?.clientSecret
|
|
10846
|
+
)
|
|
10606
10847
|
};
|
|
10607
10848
|
const bingSettingsSummary = {
|
|
10608
10849
|
// Treat Bing as configured if there is at least one connection with an API key,
|
|
@@ -10619,8 +10860,13 @@ async function createServer(opts) {
|
|
|
10619
10860
|
upsertConnection: (connection) => {
|
|
10620
10861
|
if (!opts.config.bing) opts.config.bing = {};
|
|
10621
10862
|
if (!opts.config.bing.connections) opts.config.bing.connections = [];
|
|
10622
|
-
const idx = opts.config.bing.connections.findIndex(
|
|
10623
|
-
|
|
10863
|
+
const idx = opts.config.bing.connections.findIndex(
|
|
10864
|
+
(c) => c.domain === connection.domain
|
|
10865
|
+
);
|
|
10866
|
+
const normalized = {
|
|
10867
|
+
...connection,
|
|
10868
|
+
createdByProjectId: connection.createdByProjectId ?? null
|
|
10869
|
+
};
|
|
10624
10870
|
if (idx >= 0) {
|
|
10625
10871
|
opts.config.bing.connections[idx] = normalized;
|
|
10626
10872
|
} else {
|
|
@@ -10630,7 +10876,9 @@ async function createServer(opts) {
|
|
|
10630
10876
|
return normalized;
|
|
10631
10877
|
},
|
|
10632
10878
|
updateConnection: (domain, patch) => {
|
|
10633
|
-
const conn = opts.config.bing?.connections?.find(
|
|
10879
|
+
const conn = opts.config.bing?.connections?.find(
|
|
10880
|
+
(c) => c.domain === domain
|
|
10881
|
+
);
|
|
10634
10882
|
if (!conn) return void 0;
|
|
10635
10883
|
Object.assign(conn, patch);
|
|
10636
10884
|
saveConfigPatch(opts.config);
|
|
@@ -10638,7 +10886,9 @@ async function createServer(opts) {
|
|
|
10638
10886
|
},
|
|
10639
10887
|
deleteConnection: (domain) => {
|
|
10640
10888
|
if (!opts.config.bing?.connections) return false;
|
|
10641
|
-
const idx = opts.config.bing.connections.findIndex(
|
|
10889
|
+
const idx = opts.config.bing.connections.findIndex(
|
|
10890
|
+
(c) => c.domain === domain
|
|
10891
|
+
);
|
|
10642
10892
|
if (idx < 0) return false;
|
|
10643
10893
|
opts.config.bing.connections.splice(idx, 1);
|
|
10644
10894
|
saveConfigPatch(opts.config);
|
|
@@ -10685,7 +10935,10 @@ async function createServer(opts) {
|
|
|
10685
10935
|
return updated;
|
|
10686
10936
|
},
|
|
10687
10937
|
deleteConnection: (projectName) => {
|
|
10688
|
-
const removed = removeWordpressTrafficConnection(
|
|
10938
|
+
const removed = removeWordpressTrafficConnection(
|
|
10939
|
+
opts.config,
|
|
10940
|
+
projectName
|
|
10941
|
+
);
|
|
10689
10942
|
if (removed) saveConfigPatch(opts.config);
|
|
10690
10943
|
return removed;
|
|
10691
10944
|
}
|
|
@@ -10715,12 +10968,21 @@ async function createServer(opts) {
|
|
|
10715
10968
|
return updated;
|
|
10716
10969
|
},
|
|
10717
10970
|
updateConnection: (domain, connectionType, patch) => {
|
|
10718
|
-
const updated = patchGoogleConnection(
|
|
10971
|
+
const updated = patchGoogleConnection(
|
|
10972
|
+
opts.config,
|
|
10973
|
+
domain,
|
|
10974
|
+
connectionType,
|
|
10975
|
+
patch
|
|
10976
|
+
);
|
|
10719
10977
|
if (updated) saveConfigPatch(opts.config);
|
|
10720
10978
|
return updated;
|
|
10721
10979
|
},
|
|
10722
10980
|
deleteConnection: (domain, connectionType) => {
|
|
10723
|
-
const removed = removeGoogleConnection(
|
|
10981
|
+
const removed = removeGoogleConnection(
|
|
10982
|
+
opts.config,
|
|
10983
|
+
domain,
|
|
10984
|
+
connectionType
|
|
10985
|
+
);
|
|
10724
10986
|
if (removed) saveConfigPatch(opts.config);
|
|
10725
10987
|
return removed;
|
|
10726
10988
|
}
|
|
@@ -10811,13 +11073,16 @@ async function createServer(opts) {
|
|
|
10811
11073
|
const key = getDefaultApiKey();
|
|
10812
11074
|
if (!key || key.revokedAt) return false;
|
|
10813
11075
|
const sessionId = createSession(key.id);
|
|
10814
|
-
reply.header(
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
11076
|
+
reply.header(
|
|
11077
|
+
"set-cookie",
|
|
11078
|
+
serializeSessionCookie({
|
|
11079
|
+
name: SESSION_COOKIE_NAME,
|
|
11080
|
+
value: sessionId,
|
|
11081
|
+
path: sessionCookiePath,
|
|
11082
|
+
secure: sessionCookieSecure,
|
|
11083
|
+
ttlMs: SESSION_TTL_MS
|
|
11084
|
+
})
|
|
11085
|
+
);
|
|
10821
11086
|
return true;
|
|
10822
11087
|
};
|
|
10823
11088
|
const boundToLoopback = isLoopbackBindHost(opts.host);
|
|
@@ -10838,7 +11103,9 @@ async function createServer(opts) {
|
|
|
10838
11103
|
});
|
|
10839
11104
|
app.post(apiPrefix + "/session/setup", async (request, reply) => {
|
|
10840
11105
|
if (!boundToLoopback && !requestHasValidApiKey(request)) {
|
|
10841
|
-
const err = authRequired(
|
|
11106
|
+
const err = authRequired(
|
|
11107
|
+
"This server is network-reachable; setting the dashboard password requires a valid API key."
|
|
11108
|
+
);
|
|
10842
11109
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
10843
11110
|
}
|
|
10844
11111
|
if (opts.config.dashboardPasswordHash) {
|
|
@@ -10863,19 +11130,31 @@ async function createServer(opts) {
|
|
|
10863
11130
|
const apiKey = request.body?.apiKey?.trim();
|
|
10864
11131
|
if (password) {
|
|
10865
11132
|
if (!opts.config.dashboardPasswordHash) {
|
|
10866
|
-
const err2 = validationError(
|
|
11133
|
+
const err2 = validationError(
|
|
11134
|
+
"No dashboard password configured \u2014 use /session/setup first"
|
|
11135
|
+
);
|
|
10867
11136
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
10868
11137
|
}
|
|
10869
|
-
const verification = verifyDashboardPassword(
|
|
11138
|
+
const verification = verifyDashboardPassword(
|
|
11139
|
+
password,
|
|
11140
|
+
opts.config.dashboardPasswordHash
|
|
11141
|
+
);
|
|
10870
11142
|
if (!verification.ok) {
|
|
10871
|
-
return reply.status(401).send({
|
|
11143
|
+
return reply.status(401).send({
|
|
11144
|
+
error: { code: "AUTH_INVALID", message: "Incorrect password" }
|
|
11145
|
+
});
|
|
10872
11146
|
}
|
|
10873
11147
|
if (verification.needsRehash) {
|
|
10874
11148
|
opts.config.dashboardPasswordHash = hashDashboardPassword(password);
|
|
10875
11149
|
saveConfigPatch(opts.config);
|
|
10876
11150
|
}
|
|
10877
11151
|
if (!createPasswordSession(reply)) {
|
|
10878
|
-
return reply.status(401).send({
|
|
11152
|
+
return reply.status(401).send({
|
|
11153
|
+
error: {
|
|
11154
|
+
code: "AUTH_INVALID",
|
|
11155
|
+
message: "Server API key not found \u2014 re-run canonry init"
|
|
11156
|
+
}
|
|
11157
|
+
});
|
|
10879
11158
|
}
|
|
10880
11159
|
return reply.send({ authenticated: true });
|
|
10881
11160
|
}
|
|
@@ -10887,13 +11166,16 @@ async function createServer(opts) {
|
|
|
10887
11166
|
}
|
|
10888
11167
|
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(apiKeys.id, key.id)).run();
|
|
10889
11168
|
const sessionId = createSession(key.id);
|
|
10890
|
-
reply.header(
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
11169
|
+
reply.header(
|
|
11170
|
+
"set-cookie",
|
|
11171
|
+
serializeSessionCookie({
|
|
11172
|
+
name: SESSION_COOKIE_NAME,
|
|
11173
|
+
value: sessionId,
|
|
11174
|
+
path: sessionCookiePath,
|
|
11175
|
+
secure: sessionCookieSecure,
|
|
11176
|
+
ttlMs: SESSION_TTL_MS
|
|
11177
|
+
})
|
|
11178
|
+
);
|
|
10897
11179
|
return reply.send({ authenticated: true });
|
|
10898
11180
|
}
|
|
10899
11181
|
const err = validationError("Either password or apiKey is required");
|
|
@@ -10902,13 +11184,16 @@ async function createServer(opts) {
|
|
|
10902
11184
|
app.delete(apiPrefix + "/session", async (request, reply) => {
|
|
10903
11185
|
const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
|
|
10904
11186
|
clearSession(sessionId);
|
|
10905
|
-
reply.header(
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
11187
|
+
reply.header(
|
|
11188
|
+
"set-cookie",
|
|
11189
|
+
serializeSessionCookie({
|
|
11190
|
+
name: SESSION_COOKIE_NAME,
|
|
11191
|
+
value: null,
|
|
11192
|
+
path: sessionCookiePath,
|
|
11193
|
+
secure: sessionCookieSecure,
|
|
11194
|
+
ttlMs: SESSION_TTL_MS
|
|
11195
|
+
})
|
|
11196
|
+
);
|
|
10912
11197
|
return reply.status(204).send();
|
|
10913
11198
|
});
|
|
10914
11199
|
const LATEST_RELEASE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -10933,8 +11218,12 @@ async function createServer(opts) {
|
|
|
10933
11218
|
latestReleaseCache = { value, expiresAt: now + LATEST_RELEASE_TTL_MS };
|
|
10934
11219
|
return value;
|
|
10935
11220
|
};
|
|
10936
|
-
const explainContentRecommendation = createRecommendationExplainer({
|
|
10937
|
-
|
|
11221
|
+
const explainContentRecommendation = createRecommendationExplainer({
|
|
11222
|
+
config: opts.config
|
|
11223
|
+
});
|
|
11224
|
+
const briefContentRecommendation = createRecommendationBriefSynthesizer({
|
|
11225
|
+
config: opts.config
|
|
11226
|
+
});
|
|
10938
11227
|
await app.register(apiRoutes, {
|
|
10939
11228
|
db: opts.db,
|
|
10940
11229
|
routePrefix: apiPrefix,
|
|
@@ -10996,7 +11285,9 @@ async function createServer(opts) {
|
|
|
10996
11285
|
onGscSyncRequested: (runId, projectId, syncOpts) => {
|
|
10997
11286
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
10998
11287
|
if (!googleClientId || !googleClientSecret) {
|
|
10999
|
-
app.log.error(
|
|
11288
|
+
app.log.error(
|
|
11289
|
+
"GSC sync requested but Google OAuth credentials are not configured in the local config"
|
|
11290
|
+
);
|
|
11000
11291
|
return;
|
|
11001
11292
|
}
|
|
11002
11293
|
executeGscSync(opts.db, runId, projectId, {
|
|
@@ -11009,7 +11300,9 @@ async function createServer(opts) {
|
|
|
11009
11300
|
onInspectSitemapRequested: (runId, projectId, inspectOpts) => {
|
|
11010
11301
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
11011
11302
|
if (!googleClientId || !googleClientSecret) {
|
|
11012
|
-
app.log.error(
|
|
11303
|
+
app.log.error(
|
|
11304
|
+
"Inspect sitemap requested but Google OAuth credentials are not configured"
|
|
11305
|
+
);
|
|
11013
11306
|
return;
|
|
11014
11307
|
}
|
|
11015
11308
|
executeInspectSitemap(opts.db, runId, projectId, {
|
|
@@ -11034,7 +11327,9 @@ async function createServer(opts) {
|
|
|
11034
11327
|
pluginDir: PLUGIN_DIR
|
|
11035
11328
|
}),
|
|
11036
11329
|
onInstallBacklinks: async () => {
|
|
11037
|
-
const result = await installDuckdb({
|
|
11330
|
+
const result = await installDuckdb({
|
|
11331
|
+
onLog: (line) => app.log.info({ line }, "duckdb install")
|
|
11332
|
+
});
|
|
11038
11333
|
return {
|
|
11039
11334
|
installed: true,
|
|
11040
11335
|
version: result.version,
|
|
@@ -11057,8 +11352,13 @@ async function createServer(opts) {
|
|
|
11057
11352
|
trigger: RunTriggers.scheduled,
|
|
11058
11353
|
createdAt: now
|
|
11059
11354
|
}).run();
|
|
11060
|
-
executeBacklinkExtract(opts.db, runId, projectId, {
|
|
11061
|
-
|
|
11355
|
+
executeBacklinkExtract(opts.db, runId, projectId, {
|
|
11356
|
+
release: r
|
|
11357
|
+
}).catch((err) => {
|
|
11358
|
+
app.log.error(
|
|
11359
|
+
{ runId, projectId, err },
|
|
11360
|
+
"Auto backlink extract failed"
|
|
11361
|
+
);
|
|
11062
11362
|
});
|
|
11063
11363
|
}
|
|
11064
11364
|
}
|
|
@@ -11067,9 +11367,11 @@ async function createServer(opts) {
|
|
|
11067
11367
|
});
|
|
11068
11368
|
},
|
|
11069
11369
|
onBacklinkExtractRequested: (runId, projectId, release) => {
|
|
11070
|
-
executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
|
|
11071
|
-
|
|
11072
|
-
|
|
11370
|
+
executeBacklinkExtract(opts.db, runId, projectId, { release }).catch(
|
|
11371
|
+
(err) => {
|
|
11372
|
+
app.log.error({ runId, err }, "Backlink extract failed");
|
|
11373
|
+
}
|
|
11374
|
+
);
|
|
11073
11375
|
},
|
|
11074
11376
|
onBingBacklinkSyncRequested: (runId, projectId) => {
|
|
11075
11377
|
executeBingBacklinkSync(opts.db, runId, projectId, {
|
|
@@ -11104,9 +11406,16 @@ async function createServer(opts) {
|
|
|
11104
11406
|
embedQueries: (queriesToEmbed) => {
|
|
11105
11407
|
const cfg = registry.get("gemini")?.config;
|
|
11106
11408
|
if (!cfg?.apiKey) {
|
|
11107
|
-
return Promise.reject(
|
|
11409
|
+
return Promise.reject(
|
|
11410
|
+
new Error(
|
|
11411
|
+
"Gemini API key not configured; harvest semantic novelty unavailable"
|
|
11412
|
+
)
|
|
11413
|
+
);
|
|
11108
11414
|
}
|
|
11109
|
-
return embedQueries(queriesToEmbed, {
|
|
11415
|
+
return embedQueries(queriesToEmbed, {
|
|
11416
|
+
apiKey: cfg.apiKey,
|
|
11417
|
+
baseUrl: cfg.baseUrl
|
|
11418
|
+
});
|
|
11110
11419
|
},
|
|
11111
11420
|
onSiteAuditRequested: (runId, projectId, auditOpts) => {
|
|
11112
11421
|
runSiteAudit(runId, projectId, auditOpts);
|
|
@@ -11122,7 +11431,10 @@ async function createServer(opts) {
|
|
|
11122
11431
|
const cached = listCachedReleases();
|
|
11123
11432
|
const syncByRelease = /* @__PURE__ */ new Map();
|
|
11124
11433
|
for (const row of opts.db.select().from(ccReleaseSyncs).all()) {
|
|
11125
|
-
syncByRelease.set(row.release, {
|
|
11434
|
+
syncByRelease.set(row.release, {
|
|
11435
|
+
status: row.status,
|
|
11436
|
+
updatedAt: row.updatedAt
|
|
11437
|
+
});
|
|
11126
11438
|
}
|
|
11127
11439
|
return cached.map((entry) => {
|
|
11128
11440
|
const sync = syncByRelease.get(entry.release);
|
|
@@ -11171,16 +11483,20 @@ async function createServer(opts) {
|
|
|
11171
11483
|
wordpressTrafficCredentialStore,
|
|
11172
11484
|
vercelTrafficCredentialStore,
|
|
11173
11485
|
onTrafficSynced: (event) => {
|
|
11174
|
-
trackEvent(
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11486
|
+
trackEvent(
|
|
11487
|
+
"traffic.synced",
|
|
11488
|
+
{
|
|
11489
|
+
status: event.status,
|
|
11490
|
+
sourceType: event.sourceType,
|
|
11491
|
+
sourceId: event.sourceId,
|
|
11492
|
+
pulledEvents: event.pulledEvents,
|
|
11493
|
+
selfTrafficExcluded: event.selfTrafficExcluded,
|
|
11494
|
+
crawlerHits: event.crawlerHits,
|
|
11495
|
+
aiReferralHits: event.aiReferralHits,
|
|
11496
|
+
durationMs: event.durationMs
|
|
11497
|
+
},
|
|
11498
|
+
event.errorCode ? { errorCode: event.errorCode } : void 0
|
|
11499
|
+
);
|
|
11184
11500
|
},
|
|
11185
11501
|
onRunCreated: (runId, projectId, providers2, location) => {
|
|
11186
11502
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
@@ -11243,16 +11559,18 @@ async function createServer(opts) {
|
|
|
11243
11559
|
}).map((project) => project.id);
|
|
11244
11560
|
const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
|
|
11245
11561
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11246
|
-
opts.db.insert(auditLog).values(
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11562
|
+
opts.db.insert(auditLog).values(
|
|
11563
|
+
targetProjectIds.map((projectId) => ({
|
|
11564
|
+
id: crypto20.randomUUID(),
|
|
11565
|
+
projectId,
|
|
11566
|
+
actor: "api",
|
|
11567
|
+
action: existing ? "provider.updated" : "provider.created",
|
|
11568
|
+
entityType: "provider",
|
|
11569
|
+
entityId: name,
|
|
11570
|
+
diff,
|
|
11571
|
+
createdAt
|
|
11572
|
+
}))
|
|
11573
|
+
).run();
|
|
11256
11574
|
}
|
|
11257
11575
|
return {
|
|
11258
11576
|
name,
|
|
@@ -11300,7 +11618,10 @@ async function createServer(opts) {
|
|
|
11300
11618
|
"aliases changed \u2014 recomputed mention fields on historical snapshots"
|
|
11301
11619
|
);
|
|
11302
11620
|
} catch (err) {
|
|
11303
|
-
app.log.error(
|
|
11621
|
+
app.log.error(
|
|
11622
|
+
{ err, projectId, projectName },
|
|
11623
|
+
"alias-triggered backfill failed"
|
|
11624
|
+
);
|
|
11304
11625
|
}
|
|
11305
11626
|
});
|
|
11306
11627
|
},
|
|
@@ -11329,7 +11650,11 @@ async function createServer(opts) {
|
|
|
11329
11650
|
app.log.error({ err }, "Failed to save CDP config");
|
|
11330
11651
|
throw err;
|
|
11331
11652
|
}
|
|
11332
|
-
const CDP_DEFAULT_QUOTA = {
|
|
11653
|
+
const CDP_DEFAULT_QUOTA = {
|
|
11654
|
+
maxConcurrency: 1,
|
|
11655
|
+
maxRequestsPerMinute: 4,
|
|
11656
|
+
maxRequestsPerDay: 200
|
|
11657
|
+
};
|
|
11333
11658
|
registry.register(cdpChatgptAdapter, {
|
|
11334
11659
|
provider: "cdp:chatgpt",
|
|
11335
11660
|
cdpEndpoint: `ws://${host}:${port2}`,
|
|
@@ -11361,16 +11686,19 @@ async function createServer(opts) {
|
|
|
11361
11686
|
conn.config
|
|
11362
11687
|
);
|
|
11363
11688
|
const raw = result.rawResponse;
|
|
11364
|
-
return [
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11689
|
+
return [
|
|
11690
|
+
{
|
|
11691
|
+
target: targets?.[0] ?? "chatgpt",
|
|
11692
|
+
screenshotPath: result.screenshotPath ?? "",
|
|
11693
|
+
answerText: raw.answerText ?? "",
|
|
11694
|
+
citations: raw.groundingSources ?? []
|
|
11695
|
+
}
|
|
11696
|
+
];
|
|
11370
11697
|
},
|
|
11371
11698
|
onGenerateQueries: async (providerName, count, project) => {
|
|
11372
11699
|
const provider = registry.get(providerName);
|
|
11373
|
-
if (!provider)
|
|
11700
|
+
if (!provider)
|
|
11701
|
+
throw new Error(`Provider "${providerName}" is not configured`);
|
|
11374
11702
|
const siteText = await fetchSiteText(project.domain);
|
|
11375
11703
|
const prompt = buildQueryGenerationPrompt({
|
|
11376
11704
|
domain: project.domain,
|
|
@@ -11423,11 +11751,11 @@ async function createServer(opts) {
|
|
|
11423
11751
|
// Without this, the browser hits the server for every JS chunk on
|
|
11424
11752
|
// every page load, defeating most of the dashboard's first-paint
|
|
11425
11753
|
// budget.
|
|
11426
|
-
setHeaders: (
|
|
11427
|
-
if (
|
|
11428
|
-
|
|
11754
|
+
setHeaders: (res, path10) => {
|
|
11755
|
+
if (path10.endsWith(".html")) {
|
|
11756
|
+
res.setHeader("Cache-Control", "no-cache, must-revalidate");
|
|
11429
11757
|
} else {
|
|
11430
|
-
|
|
11758
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
11431
11759
|
}
|
|
11432
11760
|
}
|
|
11433
11761
|
});
|
|
@@ -11491,10 +11819,18 @@ function buildQueryGenerationPrompt(ctx) {
|
|
|
11491
11819
|
lines.push(`Country: ${ctx.country}`);
|
|
11492
11820
|
lines.push(`Language: ${ctx.language}`);
|
|
11493
11821
|
if (ctx.siteText) {
|
|
11494
|
-
lines.push(
|
|
11822
|
+
lines.push(
|
|
11823
|
+
"",
|
|
11824
|
+
"--- Site Content ---",
|
|
11825
|
+
ctx.siteText,
|
|
11826
|
+
"--- End Site Content ---"
|
|
11827
|
+
);
|
|
11495
11828
|
}
|
|
11496
11829
|
if (ctx.existingQueries.length > 0) {
|
|
11497
|
-
lines.push(
|
|
11830
|
+
lines.push(
|
|
11831
|
+
"",
|
|
11832
|
+
`Already tracking (do NOT duplicate): ${ctx.existingQueries.join(", ")}`
|
|
11833
|
+
);
|
|
11498
11834
|
}
|
|
11499
11835
|
lines.push(
|
|
11500
11836
|
"",
|
|
@@ -11516,7 +11852,10 @@ function parseQueryResponse(raw, count) {
|
|
|
11516
11852
|
let cleaned = line.replace(/^\s*(?:\d+[.)]\s*|[-*•]\s*)/, "").trim();
|
|
11517
11853
|
cleaned = cleaned.replace(/^["']|["']$/g, "").trim();
|
|
11518
11854
|
if (!cleaned) continue;
|
|
11519
|
-
if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
|
|
11855
|
+
if (/^(?:here are|sure|certainly|of course|i['’]ve|these are|below are)/i.test(
|
|
11856
|
+
cleaned
|
|
11857
|
+
))
|
|
11858
|
+
continue;
|
|
11520
11859
|
if (cleaned.split(/\s+/).length > 8) continue;
|
|
11521
11860
|
const key = cleaned.toLowerCase();
|
|
11522
11861
|
if (seen.has(key)) continue;
|