@ainyc/canonry 4.32.0 → 4.33.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/agent-workspace/skills/canonry/references/canonry-cli.md +2 -1
- package/assets/assets/{index-CUMjedc6.js → index-47V0U52s.js} +34 -34
- package/assets/index.html +1 -1
- package/dist/{chunk-LVX5TOYA.js → chunk-5EBN7736.js} +1 -1
- package/dist/{chunk-LUAJVZVZ.js → chunk-BJXHETQW.js} +1 -1
- package/dist/{chunk-7I65IXVU.js → chunk-DZENHID5.js} +104 -40
- package/dist/{chunk-5M4PP6P4.js → chunk-XW3F5EEW.js} +112 -81
- package/dist/cli.js +31 -13
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-RSRWDBHS.js → intelligence-service-XKOUBRCE.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
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-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-47V0U52s.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="./assets/index-CNKAwZMB.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
loadConfigRaw,
|
|
7
7
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5EBN7736.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_RUN_HISTORY_LIMIT,
|
|
11
11
|
IntelligenceService,
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
schedules,
|
|
71
71
|
trafficSources,
|
|
72
72
|
usageCounters
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-BJXHETQW.js";
|
|
74
74
|
import {
|
|
75
75
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
76
76
|
AGENT_PROVIDER_IDS,
|
|
@@ -162,6 +162,7 @@ import {
|
|
|
162
162
|
reportHorizonLabel,
|
|
163
163
|
reportSeverityLabel,
|
|
164
164
|
resolveConfigSpecQueries,
|
|
165
|
+
resolveLocations,
|
|
165
166
|
resolveSnapshotRequestQueries,
|
|
166
167
|
runInProgress,
|
|
167
168
|
runNotCancellable,
|
|
@@ -178,7 +179,7 @@ import {
|
|
|
178
179
|
visibilityStateFromAnswerMentioned,
|
|
179
180
|
windowCutoff,
|
|
180
181
|
wordpressEnvSchema
|
|
181
|
-
} from "./chunk-
|
|
182
|
+
} from "./chunk-XW3F5EEW.js";
|
|
182
183
|
|
|
183
184
|
// src/telemetry.ts
|
|
184
185
|
import crypto from "crypto";
|
|
@@ -10387,7 +10388,7 @@ var routeCatalog = [
|
|
|
10387
10388
|
method: "post",
|
|
10388
10389
|
path: "/api/v1/projects/{name}/discover/run",
|
|
10389
10390
|
summary: "Start a tracked-basket discovery session",
|
|
10390
|
-
description: 'Kicks off a discovery session for the project. The pipeline: ICP description \u2192 Gemini grounded seed prompt \u2192 embed + cluster (cosine \u2265 0.85 by default) \u2192 pick canonical representatives \u2192 probe each canonical via Gemini grounding \u2192 classify into cited / aspirational / wasted-surface \u2192 aggregate competitor map. Returns immediately with `{ runId, sessionId, status: "running" }`; the actual work runs in the background. Poll `GET /projects/{name}/discover/sessions/{id}` until `status` is `completed` or `failed`.',
|
|
10391
|
+
description: 'Kicks off a discovery session for the project. The pipeline: ICP description \u2192 Gemini grounded seed prompt \u2192 embed + cluster (cosine \u2265 0.85 by default) \u2192 pick canonical representatives \u2192 probe each canonical via Gemini grounding \u2192 classify into cited / aspirational / wasted-surface \u2192 aggregate competitor map. Returns immediately with `{ runId, sessionId, status: "running", consolidated }`; the actual work runs in the background. Poll `GET /projects/{name}/discover/sessions/{id}` until `status` is `completed` or `failed`. Concurrent/duplicate requests for the same (project, ICP) are consolidated onto a single in-flight session: the response carries `consolidated: true` and `200 OK` instead of `201`, and the request\'s `dedupThreshold` / `maxProbes` are ignored (the in-flight session keeps its original config).',
|
|
10391
10392
|
tags: ["discovery"],
|
|
10392
10393
|
parameters: [nameParameter],
|
|
10393
10394
|
requestBody: {
|
|
@@ -10399,14 +10400,20 @@ var routeCatalog = [
|
|
|
10399
10400
|
properties: {
|
|
10400
10401
|
icpDescription: { type: "string", description: "Free-text ICP. Required if the project does not have spec.icpDescription stored." },
|
|
10401
10402
|
dedupThreshold: { type: "number", description: "Cosine similarity threshold for clustering. Defaults to 0.85." },
|
|
10402
|
-
maxProbes: { type: "integer", description: "Max canonical queries to probe in this session. Default 100, hard cap 500." }
|
|
10403
|
+
maxProbes: { type: "integer", description: "Max canonical queries to probe in this session. Default 100, hard cap 500." },
|
|
10404
|
+
locations: {
|
|
10405
|
+
type: "array",
|
|
10406
|
+
items: { type: "string" },
|
|
10407
|
+
description: "Optional override of the project location labels used to geo-constrain seed generation. Each label must match a configured project location; an unknown label is a 400. Omit to use every project location."
|
|
10408
|
+
}
|
|
10403
10409
|
}
|
|
10404
10410
|
}
|
|
10405
10411
|
}
|
|
10406
10412
|
}
|
|
10407
10413
|
},
|
|
10408
10414
|
responses: {
|
|
10409
|
-
|
|
10415
|
+
200: { description: "An in-flight session with the same project + ICP was reused; returns { runId, sessionId, status, consolidated: true }. The request's dedupThreshold / maxProbes are ignored." },
|
|
10416
|
+
201: { description: "New discovery session enqueued; returns { runId, sessionId, status, consolidated: false }." },
|
|
10410
10417
|
400: { description: "Missing or invalid ICP / parameters." },
|
|
10411
10418
|
404: { description: "Project not found." }
|
|
10412
10419
|
}
|
|
@@ -20003,7 +20010,8 @@ async function doctorRoutes(app, opts) {
|
|
|
20003
20010
|
|
|
20004
20011
|
// ../api-routes/src/discovery/routes.ts
|
|
20005
20012
|
import crypto21 from "crypto";
|
|
20006
|
-
import {
|
|
20013
|
+
import { and as and16, desc as desc13, eq as eq25, gte as gte4, inArray as inArray8 } from "drizzle-orm";
|
|
20014
|
+
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
20007
20015
|
async function discoveryRoutes(app, opts) {
|
|
20008
20016
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
20009
20017
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -20022,15 +20030,33 @@ async function discoveryRoutes(app, opts) {
|
|
|
20022
20030
|
"icpDescription is required. Pass it in the request body or store it on the project (spec.icpDescription)."
|
|
20023
20031
|
);
|
|
20024
20032
|
}
|
|
20033
|
+
const locations = resolveLocations(
|
|
20034
|
+
parseJsonColumn(project.locations, []),
|
|
20035
|
+
parsed.data.locations
|
|
20036
|
+
);
|
|
20025
20037
|
if (!opts.onDiscoveryRunRequested) {
|
|
20026
20038
|
throw validationError("Discovery is not available on this deployment.", {
|
|
20027
20039
|
reason: "no-discovery-handler"
|
|
20028
20040
|
});
|
|
20029
20041
|
}
|
|
20030
20042
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20031
|
-
const
|
|
20032
|
-
const
|
|
20033
|
-
|
|
20043
|
+
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
20044
|
+
const decision = app.db.transaction((tx) => {
|
|
20045
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and16(
|
|
20046
|
+
eq25(discoverySessions.projectId, project.id),
|
|
20047
|
+
eq25(discoverySessions.icpDescription, icpDescription),
|
|
20048
|
+
inArray8(discoverySessions.status, [
|
|
20049
|
+
DiscoverySessionStatuses.queued,
|
|
20050
|
+
DiscoverySessionStatuses.seeding,
|
|
20051
|
+
DiscoverySessionStatuses.probing
|
|
20052
|
+
]),
|
|
20053
|
+
gte4(discoverySessions.createdAt, ageFloorIso)
|
|
20054
|
+
)).orderBy(desc13(discoverySessions.createdAt)).get();
|
|
20055
|
+
if (existing && existing.runId) {
|
|
20056
|
+
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
20057
|
+
}
|
|
20058
|
+
const sessionId = crypto21.randomUUID();
|
|
20059
|
+
const runId = crypto21.randomUUID();
|
|
20034
20060
|
tx.insert(discoverySessions).values({
|
|
20035
20061
|
id: sessionId,
|
|
20036
20062
|
projectId: project.id,
|
|
@@ -20056,16 +20082,31 @@ async function discoveryRoutes(app, opts) {
|
|
|
20056
20082
|
entityType: "discovery_session",
|
|
20057
20083
|
entityId: sessionId
|
|
20058
20084
|
});
|
|
20085
|
+
return { reused: false, sessionId, runId };
|
|
20059
20086
|
});
|
|
20087
|
+
if (decision.reused) {
|
|
20088
|
+
return reply.status(200).send({
|
|
20089
|
+
runId: decision.runId,
|
|
20090
|
+
sessionId: decision.sessionId,
|
|
20091
|
+
status: "running",
|
|
20092
|
+
consolidated: true
|
|
20093
|
+
});
|
|
20094
|
+
}
|
|
20060
20095
|
opts.onDiscoveryRunRequested({
|
|
20061
|
-
runId,
|
|
20062
|
-
sessionId,
|
|
20096
|
+
runId: decision.runId,
|
|
20097
|
+
sessionId: decision.sessionId,
|
|
20063
20098
|
projectId: project.id,
|
|
20064
20099
|
icpDescription,
|
|
20065
20100
|
dedupThreshold: parsed.data.dedupThreshold,
|
|
20066
|
-
maxProbes: parsed.data.maxProbes
|
|
20101
|
+
maxProbes: parsed.data.maxProbes,
|
|
20102
|
+
locations
|
|
20103
|
+
});
|
|
20104
|
+
return reply.status(201).send({
|
|
20105
|
+
runId: decision.runId,
|
|
20106
|
+
sessionId: decision.sessionId,
|
|
20107
|
+
status: "running",
|
|
20108
|
+
consolidated: false
|
|
20067
20109
|
});
|
|
20068
|
-
return reply.status(201).send({ runId, sessionId, status: "running" });
|
|
20069
20110
|
});
|
|
20070
20111
|
app.get(
|
|
20071
20112
|
"/projects/:name/discover/sessions",
|
|
@@ -20343,7 +20384,8 @@ async function executeDiscovery(opts) {
|
|
|
20343
20384
|
}).where(eq26(discoverySessions.id, opts.sessionId)).run();
|
|
20344
20385
|
const seedResult = await opts.deps.seed({
|
|
20345
20386
|
project: opts.project,
|
|
20346
|
-
icpDescription: opts.icpDescription
|
|
20387
|
+
icpDescription: opts.icpDescription,
|
|
20388
|
+
locations: opts.locations ?? []
|
|
20347
20389
|
});
|
|
20348
20390
|
const rawCandidates = dedupeStrings(seedResult.candidates);
|
|
20349
20391
|
const seedCountRaw = rawCandidates.length;
|
|
@@ -23316,7 +23358,7 @@ import crypto24 from "crypto";
|
|
|
23316
23358
|
import fs7 from "fs";
|
|
23317
23359
|
import path9 from "path";
|
|
23318
23360
|
import os5 from "os";
|
|
23319
|
-
import { and as
|
|
23361
|
+
import { and as and17, eq as eq27, inArray as inArray9, sql as sql10 } from "drizzle-orm";
|
|
23320
23362
|
|
|
23321
23363
|
// src/run-telemetry.ts
|
|
23322
23364
|
import crypto23 from "crypto";
|
|
@@ -23657,7 +23699,7 @@ var JobRunner = class {
|
|
|
23657
23699
|
this.registry = registry;
|
|
23658
23700
|
}
|
|
23659
23701
|
recoverStaleRuns() {
|
|
23660
|
-
const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
23702
|
+
const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray9(runs.status, ["running", "queued"])).all();
|
|
23661
23703
|
if (stale.length === 0) return;
|
|
23662
23704
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23663
23705
|
for (const run of stale) {
|
|
@@ -23695,7 +23737,7 @@ var JobRunner = class {
|
|
|
23695
23737
|
throw new Error(`Run ${runId} is not executable from status '${existingRun.status}'`);
|
|
23696
23738
|
}
|
|
23697
23739
|
if (existingRun.status === "queued") {
|
|
23698
|
-
this.db.update(runs).set({ status: "running", startedAt: now }).where(
|
|
23740
|
+
this.db.update(runs).set({ status: "running", startedAt: now }).where(and17(eq27(runs.id, runId), eq27(runs.status, "queued"))).run();
|
|
23699
23741
|
}
|
|
23700
23742
|
this.throwIfRunCancelled(runId);
|
|
23701
23743
|
const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
|
|
@@ -23720,7 +23762,7 @@ var JobRunner = class {
|
|
|
23720
23762
|
}
|
|
23721
23763
|
log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
|
|
23722
23764
|
const scopedQueryNames = parseJsonColumn(existingRun.queries, null);
|
|
23723
|
-
projectQueries = scopedQueryNames ? this.db.select().from(queries).where(
|
|
23765
|
+
projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and17(eq27(queries.projectId, projectId), inArray9(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq27(queries.projectId, projectId)).all();
|
|
23724
23766
|
const projectCompetitors = this.db.select().from(competitors).where(eq27(competitors.projectId, projectId)).all();
|
|
23725
23767
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
23726
23768
|
const allDomains = effectiveDomains({
|
|
@@ -24056,7 +24098,7 @@ function buildPhases(input) {
|
|
|
24056
24098
|
|
|
24057
24099
|
// src/gsc-sync.ts
|
|
24058
24100
|
import crypto25 from "crypto";
|
|
24059
|
-
import { eq as eq28, and as
|
|
24101
|
+
import { eq as eq28, and as and18, sql as sql11 } from "drizzle-orm";
|
|
24060
24102
|
var log2 = createLogger("GscSync");
|
|
24061
24103
|
function formatDate3(d) {
|
|
24062
24104
|
return d.toISOString().split("T")[0];
|
|
@@ -24108,7 +24150,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
24108
24150
|
});
|
|
24109
24151
|
log2.info("fetch.complete", { runId, projectId, rowCount: rows.length });
|
|
24110
24152
|
db.delete(gscSearchData).where(
|
|
24111
|
-
|
|
24153
|
+
and18(
|
|
24112
24154
|
eq28(gscSearchData.projectId, projectId),
|
|
24113
24155
|
sql11`${gscSearchData.date} >= ${startDate}`,
|
|
24114
24156
|
sql11`${gscSearchData.date} <= ${endDate}`
|
|
@@ -24197,7 +24239,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
24197
24239
|
}
|
|
24198
24240
|
}
|
|
24199
24241
|
const snapshotDate = formatDate3(/* @__PURE__ */ new Date());
|
|
24200
|
-
db.delete(gscCoverageSnapshots).where(
|
|
24242
|
+
db.delete(gscCoverageSnapshots).where(and18(eq28(gscCoverageSnapshots.projectId, projectId), eq28(gscCoverageSnapshots.date, snapshotDate))).run();
|
|
24201
24243
|
db.insert(gscCoverageSnapshots).values({
|
|
24202
24244
|
id: crypto25.randomUUID(),
|
|
24203
24245
|
projectId,
|
|
@@ -24220,7 +24262,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
24220
24262
|
|
|
24221
24263
|
// src/gsc-inspect-sitemap.ts
|
|
24222
24264
|
import crypto26 from "crypto";
|
|
24223
|
-
import { eq as eq29, and as
|
|
24265
|
+
import { eq as eq29, and as and19 } from "drizzle-orm";
|
|
24224
24266
|
|
|
24225
24267
|
// src/sitemap-parser.ts
|
|
24226
24268
|
var log3 = createLogger("SitemapParser");
|
|
@@ -24436,7 +24478,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
24436
24478
|
}
|
|
24437
24479
|
}
|
|
24438
24480
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
24439
|
-
db.delete(gscCoverageSnapshots).where(
|
|
24481
|
+
db.delete(gscCoverageSnapshots).where(and19(eq29(gscCoverageSnapshots.projectId, projectId), eq29(gscCoverageSnapshots.date, snapshotDate))).run();
|
|
24440
24482
|
db.insert(gscCoverageSnapshots).values({
|
|
24441
24483
|
id: crypto26.randomUUID(),
|
|
24442
24484
|
projectId,
|
|
@@ -24647,7 +24689,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
24647
24689
|
// src/commoncrawl-sync.ts
|
|
24648
24690
|
import crypto28 from "crypto";
|
|
24649
24691
|
import path10 from "path";
|
|
24650
|
-
import { and as
|
|
24692
|
+
import { and as and20, eq as eq31, sql as sql12 } from "drizzle-orm";
|
|
24651
24693
|
var log6 = createLogger("CommonCrawlSync");
|
|
24652
24694
|
var INSERT_CHUNK_SIZE = 1e4;
|
|
24653
24695
|
function defaultDeps() {
|
|
@@ -24838,7 +24880,7 @@ function computeSummary(rows) {
|
|
|
24838
24880
|
// src/backlink-extract.ts
|
|
24839
24881
|
import crypto29 from "crypto";
|
|
24840
24882
|
import fs8 from "fs";
|
|
24841
|
-
import { and as
|
|
24883
|
+
import { and as and21, desc as desc15, eq as eq32 } from "drizzle-orm";
|
|
24842
24884
|
var log7 = createLogger("BacklinkExtract");
|
|
24843
24885
|
function defaultDeps2() {
|
|
24844
24886
|
return {
|
|
@@ -24884,7 +24926,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
24884
24926
|
const targetDomain = project.canonicalDomain;
|
|
24885
24927
|
db.transaction((tx) => {
|
|
24886
24928
|
tx.delete(backlinkDomains).where(
|
|
24887
|
-
|
|
24929
|
+
and21(eq32(backlinkDomains.projectId, projectId), eq32(backlinkDomains.release, release))
|
|
24888
24930
|
).run();
|
|
24889
24931
|
if (rows.length > 0) {
|
|
24890
24932
|
const values = rows.map((r) => ({
|
|
@@ -24984,6 +25026,7 @@ async function executeDiscoveryRun(opts) {
|
|
|
24984
25026
|
icpDescription: opts.icpDescription,
|
|
24985
25027
|
dedupThreshold: opts.dedupThreshold,
|
|
24986
25028
|
maxProbes: opts.maxProbes,
|
|
25029
|
+
locations: opts.locations,
|
|
24987
25030
|
deps
|
|
24988
25031
|
});
|
|
24989
25032
|
writeDiscoveryInsight(opts.db, {
|
|
@@ -25141,12 +25184,32 @@ function extractClassificationCategory(line) {
|
|
|
25141
25184
|
}
|
|
25142
25185
|
return null;
|
|
25143
25186
|
}
|
|
25187
|
+
function formatLocationLine(location) {
|
|
25188
|
+
return [location.city, location.region, location.country].map((part) => part.trim()).filter(Boolean).join(", ");
|
|
25189
|
+
}
|
|
25190
|
+
function buildLocationConstraint(locations) {
|
|
25191
|
+
if (locations.length === 0) return [];
|
|
25192
|
+
const formatted = locations.map(formatLocationLine);
|
|
25193
|
+
if (locations.length === 1) {
|
|
25194
|
+
return [
|
|
25195
|
+
`The business serves ${formatted[0]}. Every query must be relevant to that service area \u2014 work the city or region into the query the way a real searcher would.`
|
|
25196
|
+
];
|
|
25197
|
+
}
|
|
25198
|
+
const perLocation = Math.max(1, Math.floor(DEFAULT_SEED_COUNT / locations.length));
|
|
25199
|
+
return [
|
|
25200
|
+
"The business serves these locations:",
|
|
25201
|
+
...formatted.map((line) => ` - ${line}`),
|
|
25202
|
+
`Generate at least ${perLocation} queries for EACH service area listed above so coverage stays balanced \u2014 do not let one area dominate. Every query must be relevant to at least one of these service areas, working the city or region into the query the way a real searcher would.`
|
|
25203
|
+
];
|
|
25204
|
+
}
|
|
25144
25205
|
function buildSeedPrompt(input) {
|
|
25206
|
+
const locationConstraint = buildLocationConstraint(input.locations ?? []);
|
|
25145
25207
|
return [
|
|
25146
25208
|
"You are an AEO (Answer Engine Optimization) analyst expanding a tracked-query basket for a customer.",
|
|
25147
25209
|
"",
|
|
25148
25210
|
`Customer: ${input.project.name} (domains: ${input.project.canonicalDomains.join(", ")})`,
|
|
25149
25211
|
`ICP: ${input.icpDescription}`,
|
|
25212
|
+
...locationConstraint.length > 0 ? ["", ...locationConstraint] : [],
|
|
25150
25213
|
"",
|
|
25151
25214
|
"Brainstorm a wide set of queries a member of this ICP would type into an AI answer engine (Gemini, ChatGPT, Perplexity) when they are about to make a decision in this space. Aim for 30+ candidates covering:",
|
|
25152
25215
|
' - Comparison queries ("best X for Y")',
|
|
@@ -25280,7 +25343,7 @@ var ProviderRegistry = class {
|
|
|
25280
25343
|
|
|
25281
25344
|
// src/scheduler.ts
|
|
25282
25345
|
import cron from "node-cron";
|
|
25283
|
-
import { and as
|
|
25346
|
+
import { and as and22, eq as eq34 } from "drizzle-orm";
|
|
25284
25347
|
var log9 = createLogger("Scheduler");
|
|
25285
25348
|
function taskKey(projectId, kind) {
|
|
25286
25349
|
return `${projectId}::${kind}`;
|
|
@@ -25325,7 +25388,7 @@ var Scheduler = class {
|
|
|
25325
25388
|
this.stopTask(key, existing, "Stopped");
|
|
25326
25389
|
this.tasks.delete(key);
|
|
25327
25390
|
}
|
|
25328
|
-
const schedule = this.db.select().from(schedules).where(
|
|
25391
|
+
const schedule = this.db.select().from(schedules).where(and22(eq34(schedules.projectId, projectId), eq34(schedules.kind, kind))).get();
|
|
25329
25392
|
if (schedule && schedule.enabled === 1) {
|
|
25330
25393
|
this.registerCronTask(schedule);
|
|
25331
25394
|
}
|
|
@@ -25449,7 +25512,7 @@ var Scheduler = class {
|
|
|
25449
25512
|
};
|
|
25450
25513
|
|
|
25451
25514
|
// src/notifier.ts
|
|
25452
|
-
import { eq as eq35, desc as desc16, and as
|
|
25515
|
+
import { eq as eq35, desc as desc16, and as and23, inArray as inArray10, or as or4 } from "drizzle-orm";
|
|
25453
25516
|
import crypto31 from "crypto";
|
|
25454
25517
|
var log10 = createLogger("Notifier");
|
|
25455
25518
|
var Notifier = class {
|
|
@@ -25556,7 +25619,7 @@ var Notifier = class {
|
|
|
25556
25619
|
computeTransitions(runId, projectId) {
|
|
25557
25620
|
const thisRun = this.db.select().from(runs).where(eq35(runs.id, runId)).get();
|
|
25558
25621
|
if (!thisRun) return [];
|
|
25559
|
-
const groupSiblings = this.db.select().from(runs).where(
|
|
25622
|
+
const groupSiblings = this.db.select().from(runs).where(and23(
|
|
25560
25623
|
eq35(runs.projectId, projectId),
|
|
25561
25624
|
eq35(runs.kind, thisRun.kind),
|
|
25562
25625
|
eq35(runs.createdAt, thisRun.createdAt)
|
|
@@ -25582,7 +25645,7 @@ var Notifier = class {
|
|
|
25582
25645
|
);
|
|
25583
25646
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
25584
25647
|
const recentRuns = this.db.select().from(runs).where(
|
|
25585
|
-
|
|
25648
|
+
and23(
|
|
25586
25649
|
eq35(runs.projectId, projectId),
|
|
25587
25650
|
eq35(runs.kind, thisRun.kind),
|
|
25588
25651
|
or4(eq35(runs.status, "completed"), eq35(runs.status, "partial"))
|
|
@@ -25602,13 +25665,13 @@ var Notifier = class {
|
|
|
25602
25665
|
provider: querySnapshots.provider,
|
|
25603
25666
|
location: querySnapshots.location,
|
|
25604
25667
|
citationState: querySnapshots.citationState
|
|
25605
|
-
}).from(querySnapshots).leftJoin(queries, eq35(querySnapshots.queryId, queries.id)).where(
|
|
25668
|
+
}).from(querySnapshots).leftJoin(queries, eq35(querySnapshots.queryId, queries.id)).where(inArray10(querySnapshots.runId, currentRunIds)).all();
|
|
25606
25669
|
const previousSnapshots = this.db.select({
|
|
25607
25670
|
queryId: querySnapshots.queryId,
|
|
25608
25671
|
provider: querySnapshots.provider,
|
|
25609
25672
|
location: querySnapshots.location,
|
|
25610
25673
|
citationState: querySnapshots.citationState
|
|
25611
|
-
}).from(querySnapshots).where(
|
|
25674
|
+
}).from(querySnapshots).where(inArray10(querySnapshots.runId, previousRunIds)).all();
|
|
25612
25675
|
const prevMap = /* @__PURE__ */ new Map();
|
|
25613
25676
|
for (const s of previousSnapshots) {
|
|
25614
25677
|
prevMap.set(`${s.queryId}:${s.provider}:${s.location ?? ""}`, s.citationState);
|
|
@@ -26119,7 +26182,7 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
26119
26182
|
|
|
26120
26183
|
// src/agent/memory-store.ts
|
|
26121
26184
|
import crypto32 from "crypto";
|
|
26122
|
-
import { and as
|
|
26185
|
+
import { and as and24, desc as desc17, eq as eq37, like as like2, sql as sql13 } from "drizzle-orm";
|
|
26123
26186
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
26124
26187
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
26125
26188
|
function rowToDto2(row) {
|
|
@@ -26164,12 +26227,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
26164
26227
|
updatedAt: now
|
|
26165
26228
|
}
|
|
26166
26229
|
}).run();
|
|
26167
|
-
const row = db.select().from(agentMemory).where(
|
|
26230
|
+
const row = db.select().from(agentMemory).where(and24(eq37(agentMemory.projectId, args.projectId), eq37(agentMemory.key, args.key))).get();
|
|
26168
26231
|
if (!row) throw new Error("memory upsert produced no row");
|
|
26169
26232
|
return rowToDto2(row);
|
|
26170
26233
|
}
|
|
26171
26234
|
function deleteMemoryEntry(db, projectId, key) {
|
|
26172
|
-
const result = db.delete(agentMemory).where(
|
|
26235
|
+
const result = db.delete(agentMemory).where(and24(eq37(agentMemory.projectId, projectId), eq37(agentMemory.key, key))).run();
|
|
26173
26236
|
const changes = result.changes ?? 0;
|
|
26174
26237
|
return changes > 0;
|
|
26175
26238
|
}
|
|
@@ -26198,7 +26261,7 @@ function writeCompactionNote(db, args) {
|
|
|
26198
26261
|
}).run();
|
|
26199
26262
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
26200
26263
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
26201
|
-
|
|
26264
|
+
and24(
|
|
26202
26265
|
eq37(agentMemory.projectId, args.projectId),
|
|
26203
26266
|
like2(agentMemory.key, `${sessionPrefix}%`)
|
|
26204
26267
|
)
|
|
@@ -26207,7 +26270,7 @@ function writeCompactionNote(db, args) {
|
|
|
26207
26270
|
if (stale.length > 0) {
|
|
26208
26271
|
tx.delete(agentMemory).where(sql13`${agentMemory.id} IN (${sql13.join(stale.map((s) => sql13`${s}`), sql13`, `)})`).run();
|
|
26209
26272
|
}
|
|
26210
|
-
const row = tx.select().from(agentMemory).where(
|
|
26273
|
+
const row = tx.select().from(agentMemory).where(and24(eq37(agentMemory.projectId, args.projectId), eq37(agentMemory.key, key))).get();
|
|
26211
26274
|
if (row) inserted = rowToDto2(row);
|
|
26212
26275
|
});
|
|
26213
26276
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -28308,7 +28371,8 @@ async function createServer(opts) {
|
|
|
28308
28371
|
projectId: input.projectId,
|
|
28309
28372
|
icpDescription: input.icpDescription,
|
|
28310
28373
|
dedupThreshold: input.dedupThreshold,
|
|
28311
|
-
maxProbes: input.maxProbes
|
|
28374
|
+
maxProbes: input.maxProbes,
|
|
28375
|
+
locations: input.locations
|
|
28312
28376
|
}).then(() => runCoordinator.onRunCompleted(input.runId, input.projectId)).catch((err) => {
|
|
28313
28377
|
app.log.error({ runId: input.runId, err }, "Discovery run failed");
|
|
28314
28378
|
});
|
|
@@ -300,6 +300,74 @@ var notificationCreateRequestSchema = z3.object({
|
|
|
300
300
|
|
|
301
301
|
// ../contracts/src/project.ts
|
|
302
302
|
import { z as z4 } from "zod";
|
|
303
|
+
|
|
304
|
+
// ../contracts/src/errors.ts
|
|
305
|
+
var AppError = class extends Error {
|
|
306
|
+
code;
|
|
307
|
+
statusCode;
|
|
308
|
+
details;
|
|
309
|
+
constructor(code, message, statusCode, details) {
|
|
310
|
+
super(message);
|
|
311
|
+
this.name = "AppError";
|
|
312
|
+
this.code = code;
|
|
313
|
+
this.statusCode = statusCode;
|
|
314
|
+
this.details = details;
|
|
315
|
+
}
|
|
316
|
+
toJSON() {
|
|
317
|
+
return {
|
|
318
|
+
error: {
|
|
319
|
+
code: this.code,
|
|
320
|
+
message: this.message,
|
|
321
|
+
...this.details ? { details: this.details } : {}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
function notFound(entity, id) {
|
|
327
|
+
return new AppError("NOT_FOUND", `${entity} '${id}' not found`, 404);
|
|
328
|
+
}
|
|
329
|
+
function validationError(message, details) {
|
|
330
|
+
return new AppError("VALIDATION_ERROR", message, 400, details);
|
|
331
|
+
}
|
|
332
|
+
function authRequired() {
|
|
333
|
+
return new AppError("AUTH_REQUIRED", "Authentication required", 401);
|
|
334
|
+
}
|
|
335
|
+
function authInvalid() {
|
|
336
|
+
return new AppError("AUTH_INVALID", "Invalid API key", 401);
|
|
337
|
+
}
|
|
338
|
+
function providerError(message, details) {
|
|
339
|
+
return new AppError("PROVIDER_ERROR", message, 502, details);
|
|
340
|
+
}
|
|
341
|
+
function runInProgress(projectName) {
|
|
342
|
+
return new AppError("RUN_IN_PROGRESS", `A run is already in progress for '${projectName}'`, 409);
|
|
343
|
+
}
|
|
344
|
+
function runNotCancellable(runId, status) {
|
|
345
|
+
return new AppError("RUN_NOT_CANCELLABLE", `Run '${runId}' is already in terminal state '${status}' and cannot be cancelled`, 409);
|
|
346
|
+
}
|
|
347
|
+
function unsupportedKind(kind) {
|
|
348
|
+
return new AppError("UNSUPPORTED_KIND", `Kind '${kind}' is not supported in this version`, 400);
|
|
349
|
+
}
|
|
350
|
+
function notImplemented(message) {
|
|
351
|
+
return new AppError("NOT_IMPLEMENTED", message, 501);
|
|
352
|
+
}
|
|
353
|
+
function deliveryFailed(message) {
|
|
354
|
+
return new AppError("DELIVERY_FAILED", message, 502);
|
|
355
|
+
}
|
|
356
|
+
function agentBusy(projectName) {
|
|
357
|
+
return new AppError(
|
|
358
|
+
"AGENT_BUSY",
|
|
359
|
+
`Aero is already running a turn for '${projectName}'. Retry after the current turn settles.`,
|
|
360
|
+
409
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
function missingDependency(message, details) {
|
|
364
|
+
return new AppError("MISSING_DEPENDENCY", message, 422, details);
|
|
365
|
+
}
|
|
366
|
+
function internalError(message, details) {
|
|
367
|
+
return new AppError("INTERNAL_ERROR", message, 500, details);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ../contracts/src/project.ts
|
|
303
371
|
var configSourceSchema = z4.enum(["cli", "api", "config-file"]);
|
|
304
372
|
function findDuplicateLocationLabels(locations) {
|
|
305
373
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -317,6 +385,26 @@ function hasLocationLabel(locations, label) {
|
|
|
317
385
|
if (!label) return true;
|
|
318
386
|
return locations.some((location) => location.label === label);
|
|
319
387
|
}
|
|
388
|
+
function resolveLocations(projectLocations, requestedLabels) {
|
|
389
|
+
const normalizedRequest = (requestedLabels ?? []).map((label) => label.trim()).filter((label) => label.length > 0);
|
|
390
|
+
if (normalizedRequest.length === 0) return [...projectLocations];
|
|
391
|
+
const byLabel = new Map(projectLocations.map((loc) => [loc.label.toLowerCase(), loc]));
|
|
392
|
+
const resolved = [];
|
|
393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
394
|
+
for (const label of normalizedRequest) {
|
|
395
|
+
const key = label.toLowerCase();
|
|
396
|
+
if (seen.has(key)) continue;
|
|
397
|
+
const match = byLabel.get(key);
|
|
398
|
+
if (!match) {
|
|
399
|
+
throw validationError(
|
|
400
|
+
`Location "${label}" is not configured for this project. Add it to the project's locations or omit the locations override.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
seen.add(key);
|
|
404
|
+
resolved.push(match);
|
|
405
|
+
}
|
|
406
|
+
return resolved;
|
|
407
|
+
}
|
|
320
408
|
var projectUpsertRequestSchema = z4.object({
|
|
321
409
|
displayName: z4.string().min(1),
|
|
322
410
|
canonicalDomain: z4.string().min(1),
|
|
@@ -542,72 +630,6 @@ function resolveConfigSpecQueries(spec) {
|
|
|
542
630
|
return spec.queries ?? spec.keywords ?? [];
|
|
543
631
|
}
|
|
544
632
|
|
|
545
|
-
// ../contracts/src/errors.ts
|
|
546
|
-
var AppError = class extends Error {
|
|
547
|
-
code;
|
|
548
|
-
statusCode;
|
|
549
|
-
details;
|
|
550
|
-
constructor(code, message, statusCode, details) {
|
|
551
|
-
super(message);
|
|
552
|
-
this.name = "AppError";
|
|
553
|
-
this.code = code;
|
|
554
|
-
this.statusCode = statusCode;
|
|
555
|
-
this.details = details;
|
|
556
|
-
}
|
|
557
|
-
toJSON() {
|
|
558
|
-
return {
|
|
559
|
-
error: {
|
|
560
|
-
code: this.code,
|
|
561
|
-
message: this.message,
|
|
562
|
-
...this.details ? { details: this.details } : {}
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
function notFound(entity, id) {
|
|
568
|
-
return new AppError("NOT_FOUND", `${entity} '${id}' not found`, 404);
|
|
569
|
-
}
|
|
570
|
-
function validationError(message, details) {
|
|
571
|
-
return new AppError("VALIDATION_ERROR", message, 400, details);
|
|
572
|
-
}
|
|
573
|
-
function authRequired() {
|
|
574
|
-
return new AppError("AUTH_REQUIRED", "Authentication required", 401);
|
|
575
|
-
}
|
|
576
|
-
function authInvalid() {
|
|
577
|
-
return new AppError("AUTH_INVALID", "Invalid API key", 401);
|
|
578
|
-
}
|
|
579
|
-
function providerError(message, details) {
|
|
580
|
-
return new AppError("PROVIDER_ERROR", message, 502, details);
|
|
581
|
-
}
|
|
582
|
-
function runInProgress(projectName) {
|
|
583
|
-
return new AppError("RUN_IN_PROGRESS", `A run is already in progress for '${projectName}'`, 409);
|
|
584
|
-
}
|
|
585
|
-
function runNotCancellable(runId, status) {
|
|
586
|
-
return new AppError("RUN_NOT_CANCELLABLE", `Run '${runId}' is already in terminal state '${status}' and cannot be cancelled`, 409);
|
|
587
|
-
}
|
|
588
|
-
function unsupportedKind(kind) {
|
|
589
|
-
return new AppError("UNSUPPORTED_KIND", `Kind '${kind}' is not supported in this version`, 400);
|
|
590
|
-
}
|
|
591
|
-
function notImplemented(message) {
|
|
592
|
-
return new AppError("NOT_IMPLEMENTED", message, 501);
|
|
593
|
-
}
|
|
594
|
-
function deliveryFailed(message) {
|
|
595
|
-
return new AppError("DELIVERY_FAILED", message, 502);
|
|
596
|
-
}
|
|
597
|
-
function agentBusy(projectName) {
|
|
598
|
-
return new AppError(
|
|
599
|
-
"AGENT_BUSY",
|
|
600
|
-
`Aero is already running a turn for '${projectName}'. Retry after the current turn settles.`,
|
|
601
|
-
409
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
function missingDependency(message, details) {
|
|
605
|
-
return new AppError("MISSING_DEPENDENCY", message, 422, details);
|
|
606
|
-
}
|
|
607
|
-
function internalError(message, details) {
|
|
608
|
-
return new AppError("INTERNAL_ERROR", message, 500, details);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
633
|
// ../contracts/src/google.ts
|
|
612
634
|
import { z as z6 } from "zod";
|
|
613
635
|
var googleConnectionTypeSchema = z6.enum(["gsc", "ga4"]);
|
|
@@ -2442,7 +2464,15 @@ var DISCOVERY_MAX_PROBES_CAP = 500;
|
|
|
2442
2464
|
var discoveryRunRequestSchema = z21.object({
|
|
2443
2465
|
icpDescription: z21.string().min(1).optional(),
|
|
2444
2466
|
dedupThreshold: z21.number().min(0).max(1).optional(),
|
|
2445
|
-
maxProbes: z21.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional()
|
|
2467
|
+
maxProbes: z21.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional(),
|
|
2468
|
+
/**
|
|
2469
|
+
* Optional override of the project's location labels, constraining seed
|
|
2470
|
+
* generation to a subset of the configured service areas. Each label must
|
|
2471
|
+
* match a configured project location (resolved server-side via
|
|
2472
|
+
* `resolveLocations`). Omitted means "use every project location" — a
|
|
2473
|
+
* project with no locations is unaffected.
|
|
2474
|
+
*/
|
|
2475
|
+
locations: z21.array(z21.string().min(1)).optional()
|
|
2446
2476
|
});
|
|
2447
2477
|
var discoveryPromoteRequestSchema = z21.object({
|
|
2448
2478
|
buckets: z21.array(discoveryBucketSchema).min(1).optional(),
|
|
@@ -2614,20 +2644,6 @@ export {
|
|
|
2614
2644
|
getProviderLocationHandling,
|
|
2615
2645
|
notificationEventSchema,
|
|
2616
2646
|
notificationCreateRequestSchema,
|
|
2617
|
-
findDuplicateLocationLabels,
|
|
2618
|
-
hasLocationLabel,
|
|
2619
|
-
projectUpsertRequestSchema,
|
|
2620
|
-
queryBatchRequestSchema,
|
|
2621
|
-
keywordBatchRequestSchema,
|
|
2622
|
-
queryGenerateRequestSchema,
|
|
2623
|
-
keywordGenerateRequestSchema,
|
|
2624
|
-
competitorBatchRequestSchema,
|
|
2625
|
-
normalizeProjectDomain,
|
|
2626
|
-
registrableDomain,
|
|
2627
|
-
brandLabelFromDomain,
|
|
2628
|
-
effectiveDomains,
|
|
2629
|
-
projectConfigSchema,
|
|
2630
|
-
resolveConfigSpecQueries,
|
|
2631
2647
|
AppError,
|
|
2632
2648
|
notFound,
|
|
2633
2649
|
validationError,
|
|
@@ -2642,6 +2658,21 @@ export {
|
|
|
2642
2658
|
agentBusy,
|
|
2643
2659
|
missingDependency,
|
|
2644
2660
|
internalError,
|
|
2661
|
+
findDuplicateLocationLabels,
|
|
2662
|
+
hasLocationLabel,
|
|
2663
|
+
resolveLocations,
|
|
2664
|
+
projectUpsertRequestSchema,
|
|
2665
|
+
queryBatchRequestSchema,
|
|
2666
|
+
keywordBatchRequestSchema,
|
|
2667
|
+
queryGenerateRequestSchema,
|
|
2668
|
+
keywordGenerateRequestSchema,
|
|
2669
|
+
competitorBatchRequestSchema,
|
|
2670
|
+
normalizeProjectDomain,
|
|
2671
|
+
registrableDomain,
|
|
2672
|
+
brandLabelFromDomain,
|
|
2673
|
+
effectiveDomains,
|
|
2674
|
+
projectConfigSchema,
|
|
2675
|
+
resolveConfigSpecQueries,
|
|
2645
2676
|
wordpressEnvSchema,
|
|
2646
2677
|
AgentProviderIds,
|
|
2647
2678
|
AGENT_PROVIDER_IDS,
|