@ainyc/canonry 2.14.2 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/aero/references/memory-patterns.md +9 -9
- package/assets/assets/{index-D1v6Q-fS.js → index-B18l8ugs.js} +97 -97
- package/assets/assets/{index-U2SLimrz.css → index-DMx3Oy9W.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-CILBPOHB.js → chunk-CKABU6PE.js} +230 -578
- package/dist/{chunk-7VWSR5F6.js → chunk-HNVRN5QL.js} +927 -8
- package/dist/cli.js +36 -10
- package/dist/index.js +2 -2
- package/dist/mcp.js +9 -796
- package/package.json +9 -9
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AGENT_MEMORY_KEY_MAX_LENGTH,
|
|
3
2
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
4
3
|
AGENT_PROVIDER_IDS,
|
|
5
4
|
AgentProviderIds,
|
|
@@ -21,6 +20,7 @@ import {
|
|
|
21
20
|
authRequired,
|
|
22
21
|
brandKeyFromText,
|
|
23
22
|
buildRunErrorFromMessages,
|
|
23
|
+
canonryMcpTools,
|
|
24
24
|
categorizeSource,
|
|
25
25
|
categoryLabel,
|
|
26
26
|
competitorBatchRequestSchema,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
visibilityStateFromAnswerMentioned,
|
|
61
61
|
windowCutoff,
|
|
62
62
|
wordpressEnvSchema
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-HNVRN5QL.js";
|
|
64
64
|
import {
|
|
65
65
|
IntelligenceService,
|
|
66
66
|
agentMemory,
|
|
@@ -5547,6 +5547,73 @@ var canonryLocalRouteCatalog = [
|
|
|
5547
5547
|
404: { description: "Project not found." }
|
|
5548
5548
|
}
|
|
5549
5549
|
},
|
|
5550
|
+
{
|
|
5551
|
+
method: "get",
|
|
5552
|
+
path: "/api/v1/projects/{name}/agent/memory",
|
|
5553
|
+
summary: "List durable Aero memory entries for a project",
|
|
5554
|
+
description: "Returns the project-scoped agent_memory rows newest-first. Includes both operator-authored notes (source `user`/`aero`) and LLM-authored compaction summaries (source `compaction`, key prefix `compaction:`). The N most-recent rows are also injected into the system prompt at every new session start.",
|
|
5555
|
+
tags: ["agent"],
|
|
5556
|
+
parameters: [nameParameter],
|
|
5557
|
+
responses: {
|
|
5558
|
+
200: { description: "Memory entries returned." },
|
|
5559
|
+
404: { description: "Project not found." }
|
|
5560
|
+
}
|
|
5561
|
+
},
|
|
5562
|
+
{
|
|
5563
|
+
method: "put",
|
|
5564
|
+
path: "/api/v1/projects/{name}/agent/memory",
|
|
5565
|
+
summary: "Upsert a durable Aero memory entry",
|
|
5566
|
+
description: "Creates or replaces a project-scoped note (max 2 KB, max 128-char key). Same key replaces the prior value. Keys with the reserved `compaction:` prefix are rejected \u2014 that namespace is owned by transcript compaction.",
|
|
5567
|
+
tags: ["agent"],
|
|
5568
|
+
parameters: [nameParameter],
|
|
5569
|
+
requestBody: {
|
|
5570
|
+
required: true,
|
|
5571
|
+
content: {
|
|
5572
|
+
"application/json": {
|
|
5573
|
+
schema: {
|
|
5574
|
+
type: "object",
|
|
5575
|
+
required: ["key", "value"],
|
|
5576
|
+
properties: {
|
|
5577
|
+
key: { type: "string", description: "Stable identifier for this note (max 128 chars)." },
|
|
5578
|
+
value: { type: "string", description: "Plain-text note body (max 2 KB)." }
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
},
|
|
5584
|
+
responses: {
|
|
5585
|
+
200: { description: "Entry upserted." },
|
|
5586
|
+
400: { description: "Validation failed (key length, value size, reserved prefix)." },
|
|
5587
|
+
404: { description: "Project not found." }
|
|
5588
|
+
}
|
|
5589
|
+
},
|
|
5590
|
+
{
|
|
5591
|
+
method: "delete",
|
|
5592
|
+
path: "/api/v1/projects/{name}/agent/memory",
|
|
5593
|
+
summary: "Delete a durable Aero memory entry",
|
|
5594
|
+
description: "Removes a single project-scoped note by key. Returns `status: missing` (non-error) when the key never existed. Keys with the reserved `compaction:` prefix are rejected \u2014 those notes are pruned automatically.",
|
|
5595
|
+
tags: ["agent"],
|
|
5596
|
+
parameters: [nameParameter],
|
|
5597
|
+
requestBody: {
|
|
5598
|
+
required: true,
|
|
5599
|
+
content: {
|
|
5600
|
+
"application/json": {
|
|
5601
|
+
schema: {
|
|
5602
|
+
type: "object",
|
|
5603
|
+
required: ["key"],
|
|
5604
|
+
properties: {
|
|
5605
|
+
key: { type: "string", description: "Exact key of the note to remove." }
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
},
|
|
5611
|
+
responses: {
|
|
5612
|
+
200: { description: "Entry removed or already absent." },
|
|
5613
|
+
400: { description: "Validation failed (reserved prefix)." },
|
|
5614
|
+
404: { description: "Project not found." }
|
|
5615
|
+
}
|
|
5616
|
+
},
|
|
5550
5617
|
{
|
|
5551
5618
|
method: "get",
|
|
5552
5619
|
path: "/api/v1/projects/{name}/agent/providers",
|
|
@@ -8848,6 +8915,10 @@ async function ga4Routes(app, opts) {
|
|
|
8848
8915
|
GROUP BY date, source, medium
|
|
8849
8916
|
)`
|
|
8850
8917
|
).get();
|
|
8918
|
+
const aiBySession = app.db.select({
|
|
8919
|
+
sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
8920
|
+
users: sql5`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
8921
|
+
}).from(gaAiReferrals).where(and8(...aiConditions, eq19(gaAiReferrals.sourceDimension, "session"))).get();
|
|
8851
8922
|
const socialReferrals = app.db.select({
|
|
8852
8923
|
source: gaSocialReferrals.source,
|
|
8853
8924
|
medium: gaSocialReferrals.medium,
|
|
@@ -8883,6 +8954,8 @@ async function ga4Routes(app, opts) {
|
|
|
8883
8954
|
})),
|
|
8884
8955
|
aiSessionsDeduped: aiDeduped?.sessions ?? 0,
|
|
8885
8956
|
aiUsersDeduped: aiDeduped?.users ?? 0,
|
|
8957
|
+
aiSessionsBySession: aiBySession?.sessions ?? 0,
|
|
8958
|
+
aiUsersBySession: aiBySession?.users ?? 0,
|
|
8886
8959
|
socialReferrals: socialReferrals.map((r) => ({
|
|
8887
8960
|
source: r.source,
|
|
8888
8961
|
medium: r.medium,
|
|
@@ -8894,6 +8967,7 @@ async function ga4Routes(app, opts) {
|
|
|
8894
8967
|
socialUsers: socialTotals?.users ?? 0,
|
|
8895
8968
|
organicSharePct: total > 0 ? Math.round((summaryRow?.totalOrganicSessions ?? 0) / total * 100) : 0,
|
|
8896
8969
|
aiSharePct: total > 0 ? Math.round((aiDeduped?.sessions ?? 0) / total * 100) : 0,
|
|
8970
|
+
aiSharePctBySession: total > 0 ? Math.round((aiBySession?.sessions ?? 0) / total * 100) : 0,
|
|
8897
8971
|
directSharePct: total > 0 ? Math.round(totalDirectSessions / total * 100) : 0,
|
|
8898
8972
|
socialSharePct: total > 0 ? Math.round((socialTotals?.sessions ?? 0) / total * 100) : 0,
|
|
8899
8973
|
lastSyncedAt: latestSync?.syncedAt ?? null,
|
|
@@ -9013,12 +9087,13 @@ async function ga4Routes(app, opts) {
|
|
|
9013
9087
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
9014
9088
|
const sumTotal = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and8(eq19(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
9015
9089
|
const sumOrganic = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and8(eq19(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
9016
|
-
const
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9090
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and8(eq19(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
9091
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and8(
|
|
9092
|
+
eq19(gaAiReferrals.projectId, project.id),
|
|
9093
|
+
sql5`${gaAiReferrals.date} >= ${from}`,
|
|
9094
|
+
sql5`${gaAiReferrals.date} < ${to}`,
|
|
9095
|
+
eq19(gaAiReferrals.sourceDimension, "session")
|
|
9096
|
+
)).get();
|
|
9022
9097
|
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and8(eq19(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${from}`, sql5`${gaSocialReferrals.date} < ${to}`)).get();
|
|
9023
9098
|
const todayStr = fmt(today);
|
|
9024
9099
|
const buildTrend = (sum) => {
|
|
@@ -9028,18 +9103,18 @@ async function ga4Routes(app, opts) {
|
|
|
9028
9103
|
const p30 = sum(daysAgo2(60), daysAgo2(30))?.sessions ?? 0;
|
|
9029
9104
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
9030
9105
|
};
|
|
9031
|
-
const aiSourceCurrent = app.db.select({ source:
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
const aiSourcePrev = app.db.select({ source:
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9106
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and8(
|
|
9107
|
+
eq19(gaAiReferrals.projectId, project.id),
|
|
9108
|
+
sql5`${gaAiReferrals.date} >= ${daysAgo2(7)}`,
|
|
9109
|
+
sql5`${gaAiReferrals.date} < ${todayStr}`,
|
|
9110
|
+
eq19(gaAiReferrals.sourceDimension, "session")
|
|
9111
|
+
)).groupBy(gaAiReferrals.source).all();
|
|
9112
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and8(
|
|
9113
|
+
eq19(gaAiReferrals.projectId, project.id),
|
|
9114
|
+
sql5`${gaAiReferrals.date} >= ${daysAgo2(14)}`,
|
|
9115
|
+
sql5`${gaAiReferrals.date} < ${daysAgo2(7)}`,
|
|
9116
|
+
eq19(gaAiReferrals.sourceDimension, "session")
|
|
9117
|
+
)).groupBy(gaAiReferrals.source).all();
|
|
9043
9118
|
const findBiggestMover = (current, prev) => {
|
|
9044
9119
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
9045
9120
|
let mover = null;
|
|
@@ -9061,6 +9136,7 @@ async function ga4Routes(app, opts) {
|
|
|
9061
9136
|
organic: buildTrend(sumOrganic),
|
|
9062
9137
|
ai: buildTrend(sumAi),
|
|
9063
9138
|
social: buildTrend(sumSocial),
|
|
9139
|
+
direct: buildTrend(sumDirect),
|
|
9064
9140
|
aiBiggestMover: findBiggestMover(aiSourceCurrent, aiSourcePrev),
|
|
9065
9141
|
socialBiggestMover: findBiggestMover(socialSourceCurrent, socialSourcePrev)
|
|
9066
9142
|
};
|
|
@@ -16670,8 +16746,138 @@ function buildSkillDocTools() {
|
|
|
16670
16746
|
];
|
|
16671
16747
|
}
|
|
16672
16748
|
|
|
16673
|
-
// src/agent/
|
|
16749
|
+
// src/agent/mcp-to-agent-tool.ts
|
|
16674
16750
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
16751
|
+
var MAX_TOOL_RESULT_CHARS = 2e4;
|
|
16752
|
+
function truncate(json) {
|
|
16753
|
+
if (json.length <= MAX_TOOL_RESULT_CHARS) return json;
|
|
16754
|
+
return json.slice(0, MAX_TOOL_RESULT_CHARS) + "\n... (truncated \u2014 result too large)";
|
|
16755
|
+
}
|
|
16756
|
+
function textResult2(details) {
|
|
16757
|
+
return {
|
|
16758
|
+
content: [{ type: "text", text: truncate(JSON.stringify(details, null, 2)) }],
|
|
16759
|
+
details
|
|
16760
|
+
};
|
|
16761
|
+
}
|
|
16762
|
+
function stripProjectFromJsonSchema(jsonSchema) {
|
|
16763
|
+
if (!jsonSchema || typeof jsonSchema !== "object") {
|
|
16764
|
+
return { schema: jsonSchema, hadProject: false };
|
|
16765
|
+
}
|
|
16766
|
+
const obj = jsonSchema;
|
|
16767
|
+
const properties = obj.properties;
|
|
16768
|
+
if (!properties || typeof properties !== "object" || !("project" in properties)) {
|
|
16769
|
+
return { schema: jsonSchema, hadProject: false };
|
|
16770
|
+
}
|
|
16771
|
+
const { project: _project, ...remainingProps } = properties;
|
|
16772
|
+
const required = Array.isArray(obj.required) ? obj.required.filter((name) => name !== "project") : obj.required;
|
|
16773
|
+
const stripped = { ...obj, properties: remainingProps };
|
|
16774
|
+
if (required === void 0) {
|
|
16775
|
+
delete stripped.required;
|
|
16776
|
+
} else {
|
|
16777
|
+
stripped.required = required;
|
|
16778
|
+
}
|
|
16779
|
+
return { schema: stripped, hadProject: true };
|
|
16780
|
+
}
|
|
16781
|
+
function mcpToAgentTool(tool, ctx) {
|
|
16782
|
+
const { schema: visibleSchema, hadProject } = stripProjectFromJsonSchema(tool.inputJsonSchema);
|
|
16783
|
+
const parameters = Type2.Unsafe(visibleSchema);
|
|
16784
|
+
const execute = async (_toolCallId, params) => {
|
|
16785
|
+
const handlerInput = hadProject ? { ...params, project: ctx.projectName } : params;
|
|
16786
|
+
const result = await tool.handler(ctx.client, handlerInput);
|
|
16787
|
+
return textResult2(result);
|
|
16788
|
+
};
|
|
16789
|
+
return {
|
|
16790
|
+
name: tool.name,
|
|
16791
|
+
label: tool.title,
|
|
16792
|
+
description: tool.description,
|
|
16793
|
+
parameters,
|
|
16794
|
+
execute
|
|
16795
|
+
};
|
|
16796
|
+
}
|
|
16797
|
+
var AERO_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
16798
|
+
"canonry_agent_clear"
|
|
16799
|
+
]);
|
|
16800
|
+
function buildMcpAgentTools(registry, ctx, opts = {}) {
|
|
16801
|
+
return registry.filter((tool) => !AERO_EXCLUDED_MCP_TOOLS.has(tool.name)).filter((tool) => opts.readOnly ? tool.access === "read" : true).map((tool) => mcpToAgentTool(tool, ctx));
|
|
16802
|
+
}
|
|
16803
|
+
|
|
16804
|
+
// src/agent/tools.ts
|
|
16805
|
+
function buildReadTools(ctx) {
|
|
16806
|
+
return buildMcpAgentTools(canonryMcpTools, ctx, { readOnly: true });
|
|
16807
|
+
}
|
|
16808
|
+
function buildAllTools(ctx) {
|
|
16809
|
+
return buildMcpAgentTools(canonryMcpTools, ctx);
|
|
16810
|
+
}
|
|
16811
|
+
|
|
16812
|
+
// src/agent/session.ts
|
|
16813
|
+
var builtinsRegistered = false;
|
|
16814
|
+
function ensureBuiltinsRegistered() {
|
|
16815
|
+
if (!builtinsRegistered) {
|
|
16816
|
+
registerBuiltInApiProviders();
|
|
16817
|
+
validateAgentProviderRegistry();
|
|
16818
|
+
builtinsRegistered = true;
|
|
16819
|
+
}
|
|
16820
|
+
}
|
|
16821
|
+
function loadAeroSystemPrompt(pkgDir) {
|
|
16822
|
+
const skillDir = resolveAeroSkillDir(pkgDir);
|
|
16823
|
+
const skillBody = fs11.readFileSync(path13.join(skillDir, "SKILL.md"), "utf-8");
|
|
16824
|
+
const soulPath = path13.join(skillDir, "soul.md");
|
|
16825
|
+
if (!fs11.existsSync(soulPath)) return skillBody;
|
|
16826
|
+
const soulBody = fs11.readFileSync(soulPath, "utf-8");
|
|
16827
|
+
return `${soulBody.trimEnd()}
|
|
16828
|
+
|
|
16829
|
+
---
|
|
16830
|
+
|
|
16831
|
+
${skillBody}`;
|
|
16832
|
+
}
|
|
16833
|
+
function missingProviderMessage() {
|
|
16834
|
+
const configHints = agentProvidersByPriority().join(", ");
|
|
16835
|
+
const envHints = agentProvidersByPriority().map((p) => `${AGENT_PROVIDERS[p].piAiProvider.toUpperCase()}_API_KEY`).join(" / ");
|
|
16836
|
+
return `No agent LLM provider configured. Add an API key for one of: ${configHints} in ~/.canonry/config.yaml, or export ${envHints}.`;
|
|
16837
|
+
}
|
|
16838
|
+
function detectAgentProvider(config) {
|
|
16839
|
+
for (const provider of agentProvidersByPriority()) {
|
|
16840
|
+
if (resolveApiKeyFor(provider, config)) return provider;
|
|
16841
|
+
}
|
|
16842
|
+
return void 0;
|
|
16843
|
+
}
|
|
16844
|
+
function resolveAeroModel(provider, modelId) {
|
|
16845
|
+
ensureBuiltinsRegistered();
|
|
16846
|
+
return resolveModelForProvider(provider, modelId);
|
|
16847
|
+
}
|
|
16848
|
+
function buildApiKeyResolver(config) {
|
|
16849
|
+
return (piAiProvider) => resolveApiKeyFor(piAiProvider, config);
|
|
16850
|
+
}
|
|
16851
|
+
function createAeroSession(opts) {
|
|
16852
|
+
const systemPrompt = opts.systemPromptOverride ?? loadAeroSystemPrompt();
|
|
16853
|
+
const provider = opts.provider ?? detectAgentProvider(opts.config);
|
|
16854
|
+
if (!provider) throw new Error(missingProviderMessage());
|
|
16855
|
+
const model = resolveAeroModel(provider, opts.modelId);
|
|
16856
|
+
const toolScope = opts.toolScope ?? "all";
|
|
16857
|
+
const toolCtx = {
|
|
16858
|
+
client: opts.client,
|
|
16859
|
+
projectName: opts.projectName
|
|
16860
|
+
};
|
|
16861
|
+
const stateTools = toolScope === "read-only" ? buildReadTools(toolCtx) : buildAllTools(toolCtx);
|
|
16862
|
+
const defaultTools = [...stateTools, ...buildSkillDocTools()];
|
|
16863
|
+
const tools = opts.tools ?? defaultTools;
|
|
16864
|
+
return new Agent({
|
|
16865
|
+
initialState: {
|
|
16866
|
+
systemPrompt,
|
|
16867
|
+
model,
|
|
16868
|
+
tools,
|
|
16869
|
+
...opts.initialMessages ? { messages: opts.initialMessages } : {}
|
|
16870
|
+
},
|
|
16871
|
+
streamFn: opts.streamFn,
|
|
16872
|
+
getApiKey: buildApiKeyResolver(opts.config)
|
|
16873
|
+
});
|
|
16874
|
+
}
|
|
16875
|
+
function resolveSessionProviderAndModel(config, opts) {
|
|
16876
|
+
const provider = opts?.provider ?? detectAgentProvider(config);
|
|
16877
|
+
if (!provider) throw new Error(missingProviderMessage());
|
|
16878
|
+
const modelId = opts?.modelId ?? getAgentProvider(provider).defaultModel;
|
|
16879
|
+
return { provider, modelId };
|
|
16880
|
+
}
|
|
16675
16881
|
|
|
16676
16882
|
// src/agent/memory-store.ts
|
|
16677
16883
|
import crypto26 from "crypto";
|
|
@@ -16770,556 +16976,6 @@ function writeCompactionNote(db, args) {
|
|
|
16770
16976
|
return inserted;
|
|
16771
16977
|
}
|
|
16772
16978
|
|
|
16773
|
-
// src/agent/tools.ts
|
|
16774
|
-
var MAX_TOOL_RESULT_CHARS = 2e4;
|
|
16775
|
-
function truncate(json) {
|
|
16776
|
-
if (json.length <= MAX_TOOL_RESULT_CHARS) return json;
|
|
16777
|
-
return json.slice(0, MAX_TOOL_RESULT_CHARS) + "\n... (truncated \u2014 result too large)";
|
|
16778
|
-
}
|
|
16779
|
-
function textResult2(details) {
|
|
16780
|
-
return {
|
|
16781
|
-
content: [{ type: "text", text: truncate(JSON.stringify(details, null, 2)) }],
|
|
16782
|
-
details
|
|
16783
|
-
};
|
|
16784
|
-
}
|
|
16785
|
-
var StatusSchema = Type2.Object({
|
|
16786
|
-
runLimit: Type2.Optional(
|
|
16787
|
-
Type2.Number({
|
|
16788
|
-
description: "Max recent runs to include. Default 5.",
|
|
16789
|
-
minimum: 1,
|
|
16790
|
-
maximum: 50
|
|
16791
|
-
})
|
|
16792
|
-
)
|
|
16793
|
-
});
|
|
16794
|
-
function buildGetStatusTool(ctx) {
|
|
16795
|
-
return {
|
|
16796
|
-
name: "get_status",
|
|
16797
|
-
label: "Get status",
|
|
16798
|
-
description: "Current project overview with its most recent runs.",
|
|
16799
|
-
parameters: StatusSchema,
|
|
16800
|
-
execute: async (_toolCallId, params) => {
|
|
16801
|
-
const runLimit = params.runLimit ?? 5;
|
|
16802
|
-
const [project, runs2] = await Promise.all([
|
|
16803
|
-
ctx.client.getProject(ctx.projectName),
|
|
16804
|
-
ctx.client.listRuns(ctx.projectName, runLimit)
|
|
16805
|
-
]);
|
|
16806
|
-
return textResult2({ project, runs: runs2 });
|
|
16807
|
-
}
|
|
16808
|
-
};
|
|
16809
|
-
}
|
|
16810
|
-
var HealthSchema = Type2.Object({});
|
|
16811
|
-
function buildGetHealthTool(ctx) {
|
|
16812
|
-
return {
|
|
16813
|
-
name: "get_health",
|
|
16814
|
-
label: "Get health",
|
|
16815
|
-
description: 'Latest visibility health snapshot including overall cited rate, pair counts, and per-provider breakdown. Returns `status: "no-data"` with `reason: "no-runs-yet"` and zeroed metrics for projects with no successful runs yet.',
|
|
16816
|
-
parameters: HealthSchema,
|
|
16817
|
-
execute: async () => {
|
|
16818
|
-
const health = await ctx.client.getHealth(ctx.projectName);
|
|
16819
|
-
return textResult2(health);
|
|
16820
|
-
}
|
|
16821
|
-
};
|
|
16822
|
-
}
|
|
16823
|
-
var TimelineSchema = Type2.Object({
|
|
16824
|
-
keyword: Type2.Optional(
|
|
16825
|
-
Type2.String({
|
|
16826
|
-
description: "Restrict the timeline to a single keyword. Omit to return all keywords."
|
|
16827
|
-
})
|
|
16828
|
-
)
|
|
16829
|
-
});
|
|
16830
|
-
function buildGetTimelineTool(ctx) {
|
|
16831
|
-
return {
|
|
16832
|
-
name: "get_timeline",
|
|
16833
|
-
label: "Get timeline",
|
|
16834
|
-
description: "Per-keyword citation timeline showing how visibility evolved across runs. Use to identify regressions, emerging citations, or competitor movement.",
|
|
16835
|
-
parameters: TimelineSchema,
|
|
16836
|
-
execute: async (_toolCallId, params) => {
|
|
16837
|
-
const timeline = await ctx.client.getTimeline(ctx.projectName);
|
|
16838
|
-
const filtered = params.keyword ? timeline.filter((row) => row.keyword === params.keyword) : timeline;
|
|
16839
|
-
return textResult2(filtered);
|
|
16840
|
-
}
|
|
16841
|
-
};
|
|
16842
|
-
}
|
|
16843
|
-
var InsightsSchema = Type2.Object({
|
|
16844
|
-
includeDismissed: Type2.Optional(
|
|
16845
|
-
Type2.Boolean({
|
|
16846
|
-
description: "Include dismissed insights. Default false (only active insights)."
|
|
16847
|
-
})
|
|
16848
|
-
),
|
|
16849
|
-
runId: Type2.Optional(
|
|
16850
|
-
Type2.String({
|
|
16851
|
-
description: "Restrict insights to a specific run id. Omit for all runs."
|
|
16852
|
-
})
|
|
16853
|
-
)
|
|
16854
|
-
});
|
|
16855
|
-
function buildGetInsightsTool(ctx) {
|
|
16856
|
-
return {
|
|
16857
|
-
name: "get_insights",
|
|
16858
|
-
label: "Get insights",
|
|
16859
|
-
description: "Insights produced by the canonry intelligence engine \u2014 regressions, gains, and opportunities with cause/recommendation metadata. Query this before re-deriving conclusions from raw timeline data.",
|
|
16860
|
-
parameters: InsightsSchema,
|
|
16861
|
-
execute: async (_toolCallId, params) => {
|
|
16862
|
-
const insights2 = await ctx.client.getInsights(ctx.projectName, {
|
|
16863
|
-
dismissed: params.includeDismissed,
|
|
16864
|
-
runId: params.runId
|
|
16865
|
-
});
|
|
16866
|
-
return textResult2(insights2);
|
|
16867
|
-
}
|
|
16868
|
-
};
|
|
16869
|
-
}
|
|
16870
|
-
var KeywordsSchema = Type2.Object({});
|
|
16871
|
-
function buildListKeywordsTool(ctx) {
|
|
16872
|
-
return {
|
|
16873
|
-
name: "list_keywords",
|
|
16874
|
-
label: "List keywords",
|
|
16875
|
-
description: "All keywords currently tracked for this project.",
|
|
16876
|
-
parameters: KeywordsSchema,
|
|
16877
|
-
execute: async () => {
|
|
16878
|
-
const keywords2 = await ctx.client.listKeywords(ctx.projectName);
|
|
16879
|
-
return textResult2(keywords2);
|
|
16880
|
-
}
|
|
16881
|
-
};
|
|
16882
|
-
}
|
|
16883
|
-
var CompetitorsSchema = Type2.Object({});
|
|
16884
|
-
function buildListCompetitorsTool(ctx) {
|
|
16885
|
-
return {
|
|
16886
|
-
name: "list_competitors",
|
|
16887
|
-
label: "List competitors",
|
|
16888
|
-
description: "Competitor domains tracked alongside this project for side-by-side comparison.",
|
|
16889
|
-
parameters: CompetitorsSchema,
|
|
16890
|
-
execute: async () => {
|
|
16891
|
-
const competitors2 = await ctx.client.listCompetitors(ctx.projectName);
|
|
16892
|
-
return textResult2(competitors2);
|
|
16893
|
-
}
|
|
16894
|
-
};
|
|
16895
|
-
}
|
|
16896
|
-
var ContentTargetsSchema = Type2.Object({
|
|
16897
|
-
limit: Type2.Optional(
|
|
16898
|
-
Type2.Number({
|
|
16899
|
-
description: "Max rows. Defaults to all. Use a small number (3\u201310) when summarizing for the user."
|
|
16900
|
-
})
|
|
16901
|
-
),
|
|
16902
|
-
includeInProgress: Type2.Optional(
|
|
16903
|
-
Type2.Boolean({
|
|
16904
|
-
description: "Include rows that already have an in-flight tracked action. Default false."
|
|
16905
|
-
})
|
|
16906
|
-
)
|
|
16907
|
-
});
|
|
16908
|
-
function buildGetContentTargetsTool(ctx) {
|
|
16909
|
-
return {
|
|
16910
|
-
name: "get_content_targets",
|
|
16911
|
-
label: "Get content targets",
|
|
16912
|
-
description: "Ranked, action-typed content opportunities. Each row is `{query, action \u2208 create|expand|refresh|add-schema, ourBestPage?, winningCompetitor?, score, scoreBreakdown, drivers[], demandSource, actionConfidence}`. Use this to recommend which post the user should write/refresh next.",
|
|
16913
|
-
parameters: ContentTargetsSchema,
|
|
16914
|
-
execute: async (_toolCallId, params) => {
|
|
16915
|
-
const response = await ctx.client.getContentTargets(ctx.projectName, {
|
|
16916
|
-
limit: params.limit,
|
|
16917
|
-
includeInProgress: params.includeInProgress === true
|
|
16918
|
-
});
|
|
16919
|
-
return textResult2(response);
|
|
16920
|
-
}
|
|
16921
|
-
};
|
|
16922
|
-
}
|
|
16923
|
-
var ContentSourcesSchema = Type2.Object({});
|
|
16924
|
-
function buildGetContentSourcesTool(ctx) {
|
|
16925
|
-
return {
|
|
16926
|
-
name: "get_grounding_sources",
|
|
16927
|
-
label: "Get grounding sources",
|
|
16928
|
-
description: "URL-level competitive grounding-source map. Per query, lists every URL the LLM cited (our domain vs competitors), with citation count and providers. Read this to understand what specific competitor URL is winning a query.",
|
|
16929
|
-
parameters: ContentSourcesSchema,
|
|
16930
|
-
execute: async () => {
|
|
16931
|
-
const response = await ctx.client.getContentSources(ctx.projectName);
|
|
16932
|
-
return textResult2(response);
|
|
16933
|
-
}
|
|
16934
|
-
};
|
|
16935
|
-
}
|
|
16936
|
-
var ContentGapsSchema = Type2.Object({});
|
|
16937
|
-
function buildGetContentGapsTool(ctx) {
|
|
16938
|
-
return {
|
|
16939
|
-
name: "get_content_gaps",
|
|
16940
|
-
label: "Get content gaps",
|
|
16941
|
-
description: 'Queries where competitors are cited but our domain is not. Ranked by miss rate. The blunt-instrument view of "what competitors are winning that we are not." Use `get_content_targets` for action-typed recommendations on the same data.',
|
|
16942
|
-
parameters: ContentGapsSchema,
|
|
16943
|
-
execute: async () => {
|
|
16944
|
-
const response = await ctx.client.getContentGaps(ctx.projectName);
|
|
16945
|
-
return textResult2(response);
|
|
16946
|
-
}
|
|
16947
|
-
};
|
|
16948
|
-
}
|
|
16949
|
-
var RunDetailSchema = Type2.Object({
|
|
16950
|
-
runId: Type2.String({
|
|
16951
|
-
description: "Run id (UUID) to fetch. Typically obtained from get_status runs[].id."
|
|
16952
|
-
})
|
|
16953
|
-
});
|
|
16954
|
-
function buildGetRunTool(ctx) {
|
|
16955
|
-
return {
|
|
16956
|
-
name: "get_run",
|
|
16957
|
-
label: "Get run detail",
|
|
16958
|
-
description: "Full detail for a specific run including per-keyword snapshots, error messages, and provider breakdown. Use to investigate failed runs or drill into a particular sweep.",
|
|
16959
|
-
parameters: RunDetailSchema,
|
|
16960
|
-
execute: async (_toolCallId, params) => {
|
|
16961
|
-
const run = await ctx.client.getRun(params.runId);
|
|
16962
|
-
return textResult2(run);
|
|
16963
|
-
}
|
|
16964
|
-
};
|
|
16965
|
-
}
|
|
16966
|
-
var BacklinksSchema = Type2.Object({
|
|
16967
|
-
limit: Type2.Optional(
|
|
16968
|
-
Type2.Number({
|
|
16969
|
-
description: "Max linking-domain rows to include. Default 50, max 200.",
|
|
16970
|
-
minimum: 1,
|
|
16971
|
-
maximum: 200
|
|
16972
|
-
})
|
|
16973
|
-
),
|
|
16974
|
-
release: Type2.Optional(
|
|
16975
|
-
Type2.String({
|
|
16976
|
-
description: "Common Crawl release id (e.g., cc-main-2026-jan-feb-mar). Omit for the most recent release with data."
|
|
16977
|
-
})
|
|
16978
|
-
)
|
|
16979
|
-
});
|
|
16980
|
-
function buildListBacklinksTool(ctx) {
|
|
16981
|
-
return {
|
|
16982
|
-
name: "list_backlinks",
|
|
16983
|
-
label: "List backlinks",
|
|
16984
|
-
description: "Backlink summary and top linking domains from the most recent ready Common Crawl release. Off-site authority signal that correlates with citation likelihood. Returns null summary when no release sync has completed for this workspace.",
|
|
16985
|
-
parameters: BacklinksSchema,
|
|
16986
|
-
execute: async (_toolCallId, params) => {
|
|
16987
|
-
const response = await ctx.client.backlinksDomains(ctx.projectName, {
|
|
16988
|
-
limit: params.limit ?? 50,
|
|
16989
|
-
release: params.release
|
|
16990
|
-
});
|
|
16991
|
-
return textResult2(response);
|
|
16992
|
-
}
|
|
16993
|
-
};
|
|
16994
|
-
}
|
|
16995
|
-
var RecallSchema = Type2.Object({
|
|
16996
|
-
limit: Type2.Optional(
|
|
16997
|
-
Type2.Number({
|
|
16998
|
-
description: "Max notes to return, ordered newest-first. Default 50. Max 100.",
|
|
16999
|
-
minimum: 1,
|
|
17000
|
-
maximum: 100
|
|
17001
|
-
})
|
|
17002
|
-
)
|
|
17003
|
-
});
|
|
17004
|
-
function buildRecallTool(ctx) {
|
|
17005
|
-
return {
|
|
17006
|
-
name: "recall",
|
|
17007
|
-
label: "Recall memory",
|
|
17008
|
-
description: "Read project-scoped durable notes Aero has stored via `remember` (plus compaction summaries). Returns entries newest-first. The N most-recent entries are also injected into the system prompt at session start, so you usually do not need to call this \u2014 reach for it when you need older context or the full note value.",
|
|
17009
|
-
parameters: RecallSchema,
|
|
17010
|
-
execute: async (_toolCallId, params) => {
|
|
17011
|
-
const entries = listMemoryEntries(ctx.db, ctx.projectId, { limit: params.limit ?? 50 });
|
|
17012
|
-
return textResult2({ entries });
|
|
17013
|
-
}
|
|
17014
|
-
};
|
|
17015
|
-
}
|
|
17016
|
-
function buildReadTools(ctx) {
|
|
17017
|
-
return [
|
|
17018
|
-
buildGetStatusTool(ctx),
|
|
17019
|
-
buildGetHealthTool(ctx),
|
|
17020
|
-
buildGetTimelineTool(ctx),
|
|
17021
|
-
buildGetInsightsTool(ctx),
|
|
17022
|
-
buildListKeywordsTool(ctx),
|
|
17023
|
-
buildListCompetitorsTool(ctx),
|
|
17024
|
-
buildGetRunTool(ctx),
|
|
17025
|
-
buildRecallTool(ctx),
|
|
17026
|
-
buildListBacklinksTool(ctx),
|
|
17027
|
-
buildGetContentTargetsTool(ctx),
|
|
17028
|
-
buildGetContentSourcesTool(ctx),
|
|
17029
|
-
buildGetContentGapsTool(ctx)
|
|
17030
|
-
];
|
|
17031
|
-
}
|
|
17032
|
-
var RunSweepSchema = Type2.Object({
|
|
17033
|
-
providers: Type2.Optional(
|
|
17034
|
-
Type2.Array(Type2.String(), {
|
|
17035
|
-
description: "Subset of providers to run. Omit to use every configured provider on the project."
|
|
17036
|
-
})
|
|
17037
|
-
),
|
|
17038
|
-
noLocation: Type2.Optional(
|
|
17039
|
-
Type2.Boolean({
|
|
17040
|
-
description: "Run without a location context. Default: use the project default location."
|
|
17041
|
-
})
|
|
17042
|
-
)
|
|
17043
|
-
});
|
|
17044
|
-
function buildRunSweepTool(ctx) {
|
|
17045
|
-
return {
|
|
17046
|
-
name: "run_sweep",
|
|
17047
|
-
label: "Trigger sweep",
|
|
17048
|
-
description: "Trigger a new answer-visibility sweep for this project across configured AI providers. Returns the run id(s). Use when fresh citation data is needed.",
|
|
17049
|
-
parameters: RunSweepSchema,
|
|
17050
|
-
execute: async (_toolCallId, params) => {
|
|
17051
|
-
const body = {};
|
|
17052
|
-
if (params.providers?.length) body.providers = params.providers;
|
|
17053
|
-
if (params.noLocation) body.noLocation = true;
|
|
17054
|
-
const result = await ctx.client.triggerRun(ctx.projectName, body);
|
|
17055
|
-
return textResult2(result);
|
|
17056
|
-
}
|
|
17057
|
-
};
|
|
17058
|
-
}
|
|
17059
|
-
var DismissInsightSchema = Type2.Object({
|
|
17060
|
-
insightId: Type2.String({
|
|
17061
|
-
description: "Insight id to dismiss. Obtain from get_insights details[].id."
|
|
17062
|
-
})
|
|
17063
|
-
});
|
|
17064
|
-
function buildDismissInsightTool(ctx) {
|
|
17065
|
-
return {
|
|
17066
|
-
name: "dismiss_insight",
|
|
17067
|
-
label: "Dismiss insight",
|
|
17068
|
-
description: "Mark an insight as dismissed so it no longer surfaces in active insight lists. Reversible via the dashboard.",
|
|
17069
|
-
parameters: DismissInsightSchema,
|
|
17070
|
-
execute: async (_toolCallId, params) => {
|
|
17071
|
-
const result = await ctx.client.dismissInsight(ctx.projectName, params.insightId);
|
|
17072
|
-
return textResult2(result);
|
|
17073
|
-
}
|
|
17074
|
-
};
|
|
17075
|
-
}
|
|
17076
|
-
var AddKeywordsSchema = Type2.Object({
|
|
17077
|
-
keywords: Type2.Array(Type2.String(), {
|
|
17078
|
-
minItems: 1,
|
|
17079
|
-
description: "Keywords to add to the tracking list. Duplicates against existing keywords are ignored server-side."
|
|
17080
|
-
})
|
|
17081
|
-
});
|
|
17082
|
-
function buildAddKeywordsTool(ctx) {
|
|
17083
|
-
return {
|
|
17084
|
-
name: "add_keywords",
|
|
17085
|
-
label: "Add keywords",
|
|
17086
|
-
description: "Append keywords to the project tracking list. Additive only \u2014 existing keywords are preserved. Use exact phrasing you want tracked.",
|
|
17087
|
-
parameters: AddKeywordsSchema,
|
|
17088
|
-
execute: async (_toolCallId, params) => {
|
|
17089
|
-
await ctx.client.appendKeywords(ctx.projectName, params.keywords);
|
|
17090
|
-
return textResult2({ added: params.keywords });
|
|
17091
|
-
}
|
|
17092
|
-
};
|
|
17093
|
-
}
|
|
17094
|
-
var AddCompetitorsSchema = Type2.Object({
|
|
17095
|
-
domains: Type2.Array(Type2.String(), {
|
|
17096
|
-
minItems: 1,
|
|
17097
|
-
description: 'Competitor domains to track. Provide bare domains (e.g. "example.com"), not URLs.'
|
|
17098
|
-
})
|
|
17099
|
-
});
|
|
17100
|
-
function buildAddCompetitorsTool(ctx) {
|
|
17101
|
-
return {
|
|
17102
|
-
name: "add_competitors",
|
|
17103
|
-
label: "Add competitors",
|
|
17104
|
-
description: "Append competitor domains to the project. Existing competitors are skipped by the API.",
|
|
17105
|
-
parameters: AddCompetitorsSchema,
|
|
17106
|
-
execute: async (_toolCallId, params) => {
|
|
17107
|
-
const existing = await ctx.client.listCompetitors(ctx.projectName);
|
|
17108
|
-
const existingDomains = new Set(existing.map((c) => c.domain));
|
|
17109
|
-
const newDomains = params.domains.filter((d) => !existingDomains.has(d));
|
|
17110
|
-
if (newDomains.length === 0) {
|
|
17111
|
-
return textResult2({ added: [], alreadyTracked: params.domains });
|
|
17112
|
-
}
|
|
17113
|
-
await ctx.client.appendCompetitors(ctx.projectName, newDomains);
|
|
17114
|
-
return textResult2({ added: newDomains, alreadyTracked: params.domains.filter((d) => existingDomains.has(d)) });
|
|
17115
|
-
}
|
|
17116
|
-
};
|
|
17117
|
-
}
|
|
17118
|
-
var UpdateScheduleSchema = Type2.Object({
|
|
17119
|
-
cron: Type2.Optional(
|
|
17120
|
-
Type2.String({ description: 'Cron expression (e.g. "0 */6 * * *"). Provide cron OR preset, not both.' })
|
|
17121
|
-
),
|
|
17122
|
-
preset: Type2.Optional(
|
|
17123
|
-
Type2.String({ description: 'Preset keyword (e.g. "daily", "hourly"). Provide cron OR preset, not both.' })
|
|
17124
|
-
),
|
|
17125
|
-
timezone: Type2.Optional(Type2.String({ description: 'IANA timezone. Default: "UTC".' })),
|
|
17126
|
-
enabled: Type2.Optional(
|
|
17127
|
-
Type2.Boolean({ description: "Whether the schedule is active. Default: true." })
|
|
17128
|
-
),
|
|
17129
|
-
providers: Type2.Optional(
|
|
17130
|
-
Type2.Array(Type2.String(), {
|
|
17131
|
-
description: "Providers to run on each scheduled sweep. Omit to use all configured providers."
|
|
17132
|
-
})
|
|
17133
|
-
)
|
|
17134
|
-
});
|
|
17135
|
-
function buildUpdateScheduleTool(ctx) {
|
|
17136
|
-
return {
|
|
17137
|
-
name: "update_schedule",
|
|
17138
|
-
label: "Update schedule",
|
|
17139
|
-
description: "Create or update the recurring sweep schedule for this project. Provide exactly one of `cron` (expression) or `preset` (keyword). Fully replaces any existing schedule.",
|
|
17140
|
-
parameters: UpdateScheduleSchema,
|
|
17141
|
-
execute: async (_toolCallId, params) => {
|
|
17142
|
-
if (params.cron && params.preset || !params.cron && !params.preset) {
|
|
17143
|
-
throw new Error("update_schedule: provide exactly one of `cron` or `preset`");
|
|
17144
|
-
}
|
|
17145
|
-
const body = {};
|
|
17146
|
-
if (params.cron) body.cron = params.cron;
|
|
17147
|
-
if (params.preset) body.preset = params.preset;
|
|
17148
|
-
if (params.timezone) body.timezone = params.timezone;
|
|
17149
|
-
if (params.enabled !== void 0) body.enabled = params.enabled;
|
|
17150
|
-
if (params.providers?.length) body.providers = params.providers;
|
|
17151
|
-
const result = await ctx.client.putSchedule(ctx.projectName, body);
|
|
17152
|
-
return textResult2(result);
|
|
17153
|
-
}
|
|
17154
|
-
};
|
|
17155
|
-
}
|
|
17156
|
-
var AttachAgentWebhookSchema = Type2.Object({
|
|
17157
|
-
url: Type2.String({
|
|
17158
|
-
description: "External agent webhook URL. Canonry will POST run.completed, insight.critical, insight.high, and citation.gained events to it."
|
|
17159
|
-
})
|
|
17160
|
-
});
|
|
17161
|
-
function buildAttachAgentWebhookTool(ctx) {
|
|
17162
|
-
return {
|
|
17163
|
-
name: "attach_agent_webhook",
|
|
17164
|
-
label: "Attach agent webhook",
|
|
17165
|
-
description: "Register an external agent webhook for this project. Use when wiring a Claude Code / Codex / custom agent to receive canonry run and insight events. Idempotent \u2014 skips if one already exists.",
|
|
17166
|
-
parameters: AttachAgentWebhookSchema,
|
|
17167
|
-
execute: async (_toolCallId, params) => {
|
|
17168
|
-
const existing = await ctx.client.listNotifications(ctx.projectName);
|
|
17169
|
-
const hasAgent = existing.some((n) => n.source === "agent");
|
|
17170
|
-
if (hasAgent) {
|
|
17171
|
-
return textResult2({ status: "already-attached" });
|
|
17172
|
-
}
|
|
17173
|
-
const result = await ctx.client.createNotification(ctx.projectName, {
|
|
17174
|
-
channel: "webhook",
|
|
17175
|
-
url: params.url,
|
|
17176
|
-
events: ["run.completed", "insight.critical", "insight.high", "citation.gained"],
|
|
17177
|
-
source: "agent"
|
|
17178
|
-
});
|
|
17179
|
-
return textResult2({ status: "attached", notificationId: result.id, url: params.url });
|
|
17180
|
-
}
|
|
17181
|
-
};
|
|
17182
|
-
}
|
|
17183
|
-
var RememberSchema = Type2.Object({
|
|
17184
|
-
key: Type2.String({
|
|
17185
|
-
description: `Stable identifier for this note (max ${AGENT_MEMORY_KEY_MAX_LENGTH} chars). Writing the same key overwrites the prior value. Do NOT use the "${COMPACTION_KEY_PREFIX}" prefix \u2014 that namespace is reserved for transcript compaction summaries.`,
|
|
17186
|
-
minLength: 1,
|
|
17187
|
-
maxLength: AGENT_MEMORY_KEY_MAX_LENGTH
|
|
17188
|
-
}),
|
|
17189
|
-
value: Type2.String({
|
|
17190
|
-
description: `Plain-text note to persist (max ${AGENT_MEMORY_VALUE_MAX_BYTES} bytes). Use for durable operator preferences, migration context, or non-obvious reasoning you'll want on a future turn. Do NOT duplicate data canonry already tracks (runs, insights, timelines) \u2014 query those instead.`,
|
|
17191
|
-
minLength: 1
|
|
17192
|
-
})
|
|
17193
|
-
});
|
|
17194
|
-
function buildRememberTool(ctx) {
|
|
17195
|
-
return {
|
|
17196
|
-
name: "remember",
|
|
17197
|
-
label: "Remember",
|
|
17198
|
-
description: "Persist a project-scoped durable note visible to every future Aero session for this project. Upsert \u2014 writing the same key replaces the prior value. Capped at 2 KB per note.",
|
|
17199
|
-
parameters: RememberSchema,
|
|
17200
|
-
execute: async (_toolCallId, params) => {
|
|
17201
|
-
const entry = upsertMemoryEntry(ctx.db, {
|
|
17202
|
-
projectId: ctx.projectId,
|
|
17203
|
-
key: params.key,
|
|
17204
|
-
value: params.value,
|
|
17205
|
-
source: MemorySources.aero
|
|
17206
|
-
});
|
|
17207
|
-
return textResult2({ status: "remembered", entry });
|
|
17208
|
-
}
|
|
17209
|
-
};
|
|
17210
|
-
}
|
|
17211
|
-
var ForgetSchema = Type2.Object({
|
|
17212
|
-
key: Type2.String({
|
|
17213
|
-
description: "Exact key of the note to remove. No-op (status=missing) when no note exists for that key.",
|
|
17214
|
-
minLength: 1,
|
|
17215
|
-
maxLength: AGENT_MEMORY_KEY_MAX_LENGTH
|
|
17216
|
-
})
|
|
17217
|
-
});
|
|
17218
|
-
function buildForgetTool(ctx) {
|
|
17219
|
-
return {
|
|
17220
|
-
name: "forget",
|
|
17221
|
-
label: "Forget",
|
|
17222
|
-
description: "Delete a durable note by key. Use when a previously-remembered fact is wrong or no longer relevant.",
|
|
17223
|
-
parameters: ForgetSchema,
|
|
17224
|
-
execute: async (_toolCallId, params) => {
|
|
17225
|
-
if (params.key.startsWith(COMPACTION_KEY_PREFIX)) {
|
|
17226
|
-
throw new Error(
|
|
17227
|
-
`cannot forget compaction notes directly \u2014 they are pruned automatically (key prefix "${COMPACTION_KEY_PREFIX}" is reserved)`
|
|
17228
|
-
);
|
|
17229
|
-
}
|
|
17230
|
-
const removed = deleteMemoryEntry(ctx.db, ctx.projectId, params.key);
|
|
17231
|
-
return textResult2({ status: removed ? "forgotten" : "missing", key: params.key });
|
|
17232
|
-
}
|
|
17233
|
-
};
|
|
17234
|
-
}
|
|
17235
|
-
function buildWriteTools(ctx) {
|
|
17236
|
-
return [
|
|
17237
|
-
buildRunSweepTool(ctx),
|
|
17238
|
-
buildDismissInsightTool(ctx),
|
|
17239
|
-
buildAddKeywordsTool(ctx),
|
|
17240
|
-
buildAddCompetitorsTool(ctx),
|
|
17241
|
-
buildUpdateScheduleTool(ctx),
|
|
17242
|
-
buildAttachAgentWebhookTool(ctx),
|
|
17243
|
-
buildRememberTool(ctx),
|
|
17244
|
-
buildForgetTool(ctx)
|
|
17245
|
-
];
|
|
17246
|
-
}
|
|
17247
|
-
function buildAllTools(ctx) {
|
|
17248
|
-
return [...buildReadTools(ctx), ...buildWriteTools(ctx)];
|
|
17249
|
-
}
|
|
17250
|
-
|
|
17251
|
-
// src/agent/session.ts
|
|
17252
|
-
var builtinsRegistered = false;
|
|
17253
|
-
function ensureBuiltinsRegistered() {
|
|
17254
|
-
if (!builtinsRegistered) {
|
|
17255
|
-
registerBuiltInApiProviders();
|
|
17256
|
-
validateAgentProviderRegistry();
|
|
17257
|
-
builtinsRegistered = true;
|
|
17258
|
-
}
|
|
17259
|
-
}
|
|
17260
|
-
function loadAeroSystemPrompt(pkgDir) {
|
|
17261
|
-
const skillDir = resolveAeroSkillDir(pkgDir);
|
|
17262
|
-
const skillBody = fs11.readFileSync(path13.join(skillDir, "SKILL.md"), "utf-8");
|
|
17263
|
-
const soulPath = path13.join(skillDir, "soul.md");
|
|
17264
|
-
if (!fs11.existsSync(soulPath)) return skillBody;
|
|
17265
|
-
const soulBody = fs11.readFileSync(soulPath, "utf-8");
|
|
17266
|
-
return `${soulBody.trimEnd()}
|
|
17267
|
-
|
|
17268
|
-
---
|
|
17269
|
-
|
|
17270
|
-
${skillBody}`;
|
|
17271
|
-
}
|
|
17272
|
-
function missingProviderMessage() {
|
|
17273
|
-
const configHints = agentProvidersByPriority().join(", ");
|
|
17274
|
-
const envHints = agentProvidersByPriority().map((p) => `${AGENT_PROVIDERS[p].piAiProvider.toUpperCase()}_API_KEY`).join(" / ");
|
|
17275
|
-
return `No agent LLM provider configured. Add an API key for one of: ${configHints} in ~/.canonry/config.yaml, or export ${envHints}.`;
|
|
17276
|
-
}
|
|
17277
|
-
function detectAgentProvider(config) {
|
|
17278
|
-
for (const provider of agentProvidersByPriority()) {
|
|
17279
|
-
if (resolveApiKeyFor(provider, config)) return provider;
|
|
17280
|
-
}
|
|
17281
|
-
return void 0;
|
|
17282
|
-
}
|
|
17283
|
-
function resolveAeroModel(provider, modelId) {
|
|
17284
|
-
ensureBuiltinsRegistered();
|
|
17285
|
-
return resolveModelForProvider(provider, modelId);
|
|
17286
|
-
}
|
|
17287
|
-
function buildApiKeyResolver(config) {
|
|
17288
|
-
return (piAiProvider) => resolveApiKeyFor(piAiProvider, config);
|
|
17289
|
-
}
|
|
17290
|
-
function createAeroSession(opts) {
|
|
17291
|
-
const systemPrompt = opts.systemPromptOverride ?? loadAeroSystemPrompt();
|
|
17292
|
-
const provider = opts.provider ?? detectAgentProvider(opts.config);
|
|
17293
|
-
if (!provider) throw new Error(missingProviderMessage());
|
|
17294
|
-
const model = resolveAeroModel(provider, opts.modelId);
|
|
17295
|
-
const toolScope = opts.toolScope ?? "all";
|
|
17296
|
-
const toolCtx = {
|
|
17297
|
-
client: opts.client,
|
|
17298
|
-
projectName: opts.projectName,
|
|
17299
|
-
db: opts.db,
|
|
17300
|
-
projectId: opts.projectId
|
|
17301
|
-
};
|
|
17302
|
-
const stateTools = toolScope === "read-only" ? buildReadTools(toolCtx) : buildAllTools(toolCtx);
|
|
17303
|
-
const defaultTools = [...stateTools, ...buildSkillDocTools()];
|
|
17304
|
-
const tools = opts.tools ?? defaultTools;
|
|
17305
|
-
return new Agent({
|
|
17306
|
-
initialState: {
|
|
17307
|
-
systemPrompt,
|
|
17308
|
-
model,
|
|
17309
|
-
tools,
|
|
17310
|
-
...opts.initialMessages ? { messages: opts.initialMessages } : {}
|
|
17311
|
-
},
|
|
17312
|
-
streamFn: opts.streamFn,
|
|
17313
|
-
getApiKey: buildApiKeyResolver(opts.config)
|
|
17314
|
-
});
|
|
17315
|
-
}
|
|
17316
|
-
function resolveSessionProviderAndModel(config, opts) {
|
|
17317
|
-
const provider = opts?.provider ?? detectAgentProvider(config);
|
|
17318
|
-
if (!provider) throw new Error(missingProviderMessage());
|
|
17319
|
-
const modelId = opts?.modelId ?? getAgentProvider(provider).defaultModel;
|
|
17320
|
-
return { provider, modelId };
|
|
17321
|
-
}
|
|
17322
|
-
|
|
17323
16979
|
// src/agent/compaction.ts
|
|
17324
16980
|
import { complete } from "@mariozechner/pi-ai";
|
|
17325
16981
|
|
|
@@ -17501,8 +17157,6 @@ var SessionRegistry = class {
|
|
|
17501
17157
|
projectName,
|
|
17502
17158
|
client: this.opts.client,
|
|
17503
17159
|
config: this.opts.config,
|
|
17504
|
-
db: this.opts.db,
|
|
17505
|
-
projectId,
|
|
17506
17160
|
provider: effectiveProvider,
|
|
17507
17161
|
modelId: effectiveModelId,
|
|
17508
17162
|
systemPromptOverride: this.buildHydratedSystemPrompt(projectId, row.systemPrompt),
|
|
@@ -17525,8 +17179,6 @@ var SessionRegistry = class {
|
|
|
17525
17179
|
projectName,
|
|
17526
17180
|
client: this.opts.client,
|
|
17527
17181
|
config: this.opts.config,
|
|
17528
|
-
db: this.opts.db,
|
|
17529
|
-
projectId,
|
|
17530
17182
|
provider,
|
|
17531
17183
|
modelId,
|
|
17532
17184
|
// Hydrate on the fresh path too — a brand-new session may still see
|
|
@@ -17586,7 +17238,7 @@ var SessionRegistry = class {
|
|
|
17586
17238
|
---
|
|
17587
17239
|
|
|
17588
17240
|
<memory>
|
|
17589
|
-
Project-scoped durable notes (newest first). Use
|
|
17241
|
+
Project-scoped durable notes (newest first). Use canonry_memory_set/canonry_memory_forget/canonry_memory_list to manage. Entries tagged [compaction] are LLM-summarized transcript slices.
|
|
17590
17242
|
|
|
17591
17243
|
${lines.join("\n")}
|
|
17592
17244
|
</memory>`;
|
|
@@ -17693,7 +17345,7 @@ ${lines.join("\n")}
|
|
|
17693
17345
|
if (this.scopes.get(projectName) === wantScope) return;
|
|
17694
17346
|
const projectId = this.projectIds.get(projectName) ?? this.resolveProjectId(projectName);
|
|
17695
17347
|
this.projectIds.set(projectName, projectId);
|
|
17696
|
-
const toolCtx = { client: this.opts.client, projectName
|
|
17348
|
+
const toolCtx = { client: this.opts.client, projectName };
|
|
17697
17349
|
const stateTools = wantScope === "read-only" ? buildReadTools(toolCtx) : buildAllTools(toolCtx);
|
|
17698
17350
|
agent.state.tools = [...stateTools, ...buildSkillDocTools()];
|
|
17699
17351
|
this.scopes.set(projectName, wantScope);
|
|
@@ -18964,7 +18616,7 @@ async function createServer(opts) {
|
|
|
18964
18616
|
if (!project) return;
|
|
18965
18617
|
sessionRegistry.queueFollowUp(project.name, {
|
|
18966
18618
|
role: "user",
|
|
18967
|
-
content: `[system] Run ${runId} completed for project ${project.name}. ${insightCount} insights generated (${criticalOrHigh} critical/high). Use
|
|
18619
|
+
content: `[system] Run ${runId} completed for project ${project.name}. ${insightCount} insights generated (${criticalOrHigh} critical/high). Use canonry_run_get to inspect the run and canonry_insights_list to review new findings. Surface anything notable briefly \u2014 skip chit-chat.`,
|
|
18968
18620
|
timestamp: Date.now()
|
|
18969
18621
|
});
|
|
18970
18622
|
void sessionRegistry.drainNow(project.name);
|