@ainyc/canonry 2.0.0 → 2.2.3
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 +37 -18
- package/assets/agent-workspace/skills/canonry-setup/SKILL.md +7 -7
- package/assets/assets/{index-DnlDoqE-.js → index-Bmnz-wvT.js} +75 -75
- package/assets/index.html +1 -1
- package/dist/{chunk-YZKLIUH4.js → chunk-MXUOJWNL.js} +735 -164
- package/dist/{chunk-GH6WGN5B.js → chunk-TAII35VC.js} +29 -1
- package/dist/cli.js +230 -75
- package/dist/index.js +2 -2
- package/dist/{intelligence-service-LHWXONQJ.js → intelligence-service-C5LAYDFM.js} +1 -1
- package/package.json +5 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IntelligenceService,
|
|
3
|
+
agentMemory,
|
|
3
4
|
agentSessions,
|
|
4
5
|
apiKeys,
|
|
5
6
|
auditLog,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
runs,
|
|
27
28
|
schedules,
|
|
28
29
|
usageCounters
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-TAII35VC.js";
|
|
30
31
|
|
|
31
32
|
// src/config.ts
|
|
32
33
|
import fs from "fs";
|
|
@@ -291,6 +292,11 @@ function usageError(displayMessage, options) {
|
|
|
291
292
|
details: options?.details
|
|
292
293
|
});
|
|
293
294
|
}
|
|
295
|
+
function isEndpointMissing(err) {
|
|
296
|
+
if (!(err instanceof CliError)) return false;
|
|
297
|
+
const status = err.details?.httpStatus;
|
|
298
|
+
return status === 404 || status === 405;
|
|
299
|
+
}
|
|
294
300
|
function printCliError(err, format) {
|
|
295
301
|
if (format === "json") {
|
|
296
302
|
if (err instanceof CliError) {
|
|
@@ -337,11 +343,11 @@ function printCliError(err, format) {
|
|
|
337
343
|
|
|
338
344
|
// src/server.ts
|
|
339
345
|
import { createRequire as createRequire2 } from "module";
|
|
340
|
-
import
|
|
346
|
+
import crypto24 from "crypto";
|
|
341
347
|
import fs8 from "fs";
|
|
342
348
|
import path9 from "path";
|
|
343
349
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
344
|
-
import { eq as
|
|
350
|
+
import { eq as eq26 } from "drizzle-orm";
|
|
345
351
|
import Fastify from "fastify";
|
|
346
352
|
|
|
347
353
|
// ../contracts/src/config-schema.ts
|
|
@@ -986,6 +992,13 @@ var querySnapshotDtoSchema = z8.object({
|
|
|
986
992
|
location: z8.string().nullable().optional(),
|
|
987
993
|
createdAt: z8.string()
|
|
988
994
|
});
|
|
995
|
+
var runDetailDtoSchema = runDtoSchema.extend({
|
|
996
|
+
snapshots: z8.array(querySnapshotDtoSchema).optional()
|
|
997
|
+
});
|
|
998
|
+
var latestProjectRunDtoSchema = z8.object({
|
|
999
|
+
totalRuns: z8.number().int().nonnegative(),
|
|
1000
|
+
run: runDetailDtoSchema.nullable()
|
|
1001
|
+
});
|
|
989
1002
|
var auditLogEntrySchema = z8.object({
|
|
990
1003
|
id: z8.string(),
|
|
991
1004
|
projectId: z8.string().nullable().optional(),
|
|
@@ -1404,6 +1417,20 @@ function escapeRegExp(value) {
|
|
|
1404
1417
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1405
1418
|
}
|
|
1406
1419
|
|
|
1420
|
+
// ../contracts/src/agent.ts
|
|
1421
|
+
import { z as z13 } from "zod";
|
|
1422
|
+
var memorySourceSchema = z13.enum(["aero", "user", "compaction"]);
|
|
1423
|
+
var MemorySources = memorySourceSchema.enum;
|
|
1424
|
+
var AGENT_MEMORY_VALUE_MAX_BYTES = 2 * 1024;
|
|
1425
|
+
var AGENT_MEMORY_KEY_MAX_LENGTH = 128;
|
|
1426
|
+
var agentMemoryUpsertRequestSchema = z13.object({
|
|
1427
|
+
key: z13.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH),
|
|
1428
|
+
value: z13.string().min(1)
|
|
1429
|
+
});
|
|
1430
|
+
var agentMemoryDeleteRequestSchema = z13.object({
|
|
1431
|
+
key: z13.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH)
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1407
1434
|
// ../api-routes/src/auth.ts
|
|
1408
1435
|
import crypto2 from "crypto";
|
|
1409
1436
|
import { eq } from "drizzle-orm";
|
|
@@ -1981,7 +2008,7 @@ async function competitorRoutes(app) {
|
|
|
1981
2008
|
|
|
1982
2009
|
// ../api-routes/src/runs.ts
|
|
1983
2010
|
import crypto8 from "crypto";
|
|
1984
|
-
import { eq as eq7, asc, desc } from "drizzle-orm";
|
|
2011
|
+
import { eq as eq7, asc, desc, sql as sql2 } from "drizzle-orm";
|
|
1985
2012
|
|
|
1986
2013
|
// ../api-routes/src/run-queue.ts
|
|
1987
2014
|
import crypto7 from "crypto";
|
|
@@ -2121,6 +2148,19 @@ async function runRoutes(app, opts) {
|
|
|
2121
2148
|
const rows = limit == null ? app.db.select().from(runs).where(eq7(runs.projectId, project.id)).orderBy(asc(runs.createdAt)).all() : app.db.select().from(runs).where(eq7(runs.projectId, project.id)).orderBy(desc(runs.createdAt)).limit(limit).all().reverse();
|
|
2122
2149
|
return reply.send(rows.map(formatRun));
|
|
2123
2150
|
});
|
|
2151
|
+
app.get("/projects/:name/runs/latest", async (request, reply) => {
|
|
2152
|
+
const project = resolveProject(app.db, request.params.name);
|
|
2153
|
+
const countRow = app.db.select({ count: sql2`count(*)` }).from(runs).where(eq7(runs.projectId, project.id)).get();
|
|
2154
|
+
const totalRuns = countRow?.count ?? 0;
|
|
2155
|
+
const latestRun = app.db.select().from(runs).where(eq7(runs.projectId, project.id)).orderBy(desc(runs.createdAt)).limit(1).get();
|
|
2156
|
+
if (!latestRun) {
|
|
2157
|
+
return reply.send({ totalRuns: 0, run: null });
|
|
2158
|
+
}
|
|
2159
|
+
return reply.send({
|
|
2160
|
+
totalRuns,
|
|
2161
|
+
run: loadRunDetail(app, latestRun)
|
|
2162
|
+
});
|
|
2163
|
+
});
|
|
2124
2164
|
app.get("/runs", async (_request, reply) => {
|
|
2125
2165
|
const rows = app.db.select().from(runs).all();
|
|
2126
2166
|
return reply.send(rows.map(formatRun));
|
|
@@ -2209,55 +2249,7 @@ async function runRoutes(app, opts) {
|
|
|
2209
2249
|
app.get("/runs/:id", async (request, reply) => {
|
|
2210
2250
|
const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
|
|
2211
2251
|
if (!run) throw notFound("Run", request.params.id);
|
|
2212
|
-
|
|
2213
|
-
displayName: projects.displayName,
|
|
2214
|
-
canonicalDomain: projects.canonicalDomain,
|
|
2215
|
-
ownedDomains: projects.ownedDomains
|
|
2216
|
-
}).from(projects).where(eq7(projects.id, run.projectId)).get();
|
|
2217
|
-
const snapshots = app.db.select({
|
|
2218
|
-
id: querySnapshots.id,
|
|
2219
|
-
runId: querySnapshots.runId,
|
|
2220
|
-
keywordId: querySnapshots.keywordId,
|
|
2221
|
-
keyword: keywords.keyword,
|
|
2222
|
-
provider: querySnapshots.provider,
|
|
2223
|
-
model: querySnapshots.model,
|
|
2224
|
-
citationState: querySnapshots.citationState,
|
|
2225
|
-
answerMentioned: querySnapshots.answerMentioned,
|
|
2226
|
-
answerText: querySnapshots.answerText,
|
|
2227
|
-
citedDomains: querySnapshots.citedDomains,
|
|
2228
|
-
competitorOverlap: querySnapshots.competitorOverlap,
|
|
2229
|
-
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
2230
|
-
location: querySnapshots.location,
|
|
2231
|
-
rawResponse: querySnapshots.rawResponse,
|
|
2232
|
-
createdAt: querySnapshots.createdAt
|
|
2233
|
-
}).from(querySnapshots).leftJoin(keywords, eq7(querySnapshots.keywordId, keywords.id)).where(eq7(querySnapshots.runId, run.id)).all();
|
|
2234
|
-
return reply.send({
|
|
2235
|
-
...formatRun(run),
|
|
2236
|
-
snapshots: snapshots.map((s) => {
|
|
2237
|
-
const rawParsed = parseSnapshotRawResponse(s.rawResponse);
|
|
2238
|
-
const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
|
|
2239
|
-
return {
|
|
2240
|
-
id: s.id,
|
|
2241
|
-
runId: s.runId,
|
|
2242
|
-
keywordId: s.keywordId,
|
|
2243
|
-
keyword: s.keyword,
|
|
2244
|
-
provider: s.provider,
|
|
2245
|
-
citationState: s.citationState,
|
|
2246
|
-
answerMentioned,
|
|
2247
|
-
visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
|
|
2248
|
-
answerText: s.answerText,
|
|
2249
|
-
citedDomains: parseJsonColumn(s.citedDomains, []),
|
|
2250
|
-
competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
|
|
2251
|
-
recommendedCompetitors: parseJsonColumn(s.recommendedCompetitors, []),
|
|
2252
|
-
matchedTerms: project ? resolveSnapshotMatchedTerms(s, project) : [],
|
|
2253
|
-
model: s.model ?? rawParsed.model,
|
|
2254
|
-
location: s.location,
|
|
2255
|
-
groundingSources: rawParsed.groundingSources,
|
|
2256
|
-
searchQueries: rawParsed.searchQueries,
|
|
2257
|
-
createdAt: s.createdAt
|
|
2258
|
-
};
|
|
2259
|
-
})
|
|
2260
|
-
});
|
|
2252
|
+
return reply.send(loadRunDetail(app, run));
|
|
2261
2253
|
});
|
|
2262
2254
|
}
|
|
2263
2255
|
function formatRun(row) {
|
|
@@ -2282,6 +2274,57 @@ function parseSnapshotRawResponse(raw) {
|
|
|
2282
2274
|
model: parsed.model ?? null
|
|
2283
2275
|
};
|
|
2284
2276
|
}
|
|
2277
|
+
function loadRunDetail(app, run) {
|
|
2278
|
+
const project = app.db.select({
|
|
2279
|
+
displayName: projects.displayName,
|
|
2280
|
+
canonicalDomain: projects.canonicalDomain,
|
|
2281
|
+
ownedDomains: projects.ownedDomains
|
|
2282
|
+
}).from(projects).where(eq7(projects.id, run.projectId)).get();
|
|
2283
|
+
const snapshots = app.db.select({
|
|
2284
|
+
id: querySnapshots.id,
|
|
2285
|
+
runId: querySnapshots.runId,
|
|
2286
|
+
keywordId: querySnapshots.keywordId,
|
|
2287
|
+
keyword: keywords.keyword,
|
|
2288
|
+
provider: querySnapshots.provider,
|
|
2289
|
+
model: querySnapshots.model,
|
|
2290
|
+
citationState: querySnapshots.citationState,
|
|
2291
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
2292
|
+
answerText: querySnapshots.answerText,
|
|
2293
|
+
citedDomains: querySnapshots.citedDomains,
|
|
2294
|
+
competitorOverlap: querySnapshots.competitorOverlap,
|
|
2295
|
+
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
2296
|
+
location: querySnapshots.location,
|
|
2297
|
+
rawResponse: querySnapshots.rawResponse,
|
|
2298
|
+
createdAt: querySnapshots.createdAt
|
|
2299
|
+
}).from(querySnapshots).leftJoin(keywords, eq7(querySnapshots.keywordId, keywords.id)).where(eq7(querySnapshots.runId, run.id)).all();
|
|
2300
|
+
return {
|
|
2301
|
+
...formatRun(run),
|
|
2302
|
+
snapshots: snapshots.map((s) => {
|
|
2303
|
+
const rawParsed = parseSnapshotRawResponse(s.rawResponse);
|
|
2304
|
+
const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
|
|
2305
|
+
return {
|
|
2306
|
+
id: s.id,
|
|
2307
|
+
runId: s.runId,
|
|
2308
|
+
keywordId: s.keywordId,
|
|
2309
|
+
keyword: s.keyword,
|
|
2310
|
+
provider: s.provider,
|
|
2311
|
+
citationState: s.citationState,
|
|
2312
|
+
answerMentioned,
|
|
2313
|
+
visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
|
|
2314
|
+
answerText: s.answerText,
|
|
2315
|
+
citedDomains: parseJsonColumn(s.citedDomains, []),
|
|
2316
|
+
competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
|
|
2317
|
+
recommendedCompetitors: parseJsonColumn(s.recommendedCompetitors, []),
|
|
2318
|
+
matchedTerms: project ? resolveSnapshotMatchedTerms(s, project) : [],
|
|
2319
|
+
model: s.model ?? rawParsed.model,
|
|
2320
|
+
location: s.location,
|
|
2321
|
+
groundingSources: rawParsed.groundingSources,
|
|
2322
|
+
searchQueries: rawParsed.searchQueries,
|
|
2323
|
+
createdAt: s.createdAt
|
|
2324
|
+
};
|
|
2325
|
+
})
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2285
2328
|
|
|
2286
2329
|
// ../api-routes/src/apply.ts
|
|
2287
2330
|
import crypto10 from "crypto";
|
|
@@ -3915,6 +3958,16 @@ var routeCatalog = [
|
|
|
3915
3958
|
200: { description: "Runs returned." }
|
|
3916
3959
|
}
|
|
3917
3960
|
},
|
|
3961
|
+
{
|
|
3962
|
+
method: "get",
|
|
3963
|
+
path: "/api/v1/projects/{name}/runs/latest",
|
|
3964
|
+
summary: "Get the latest project run",
|
|
3965
|
+
tags: ["runs"],
|
|
3966
|
+
parameters: [nameParameter],
|
|
3967
|
+
responses: {
|
|
3968
|
+
200: { description: "Latest run returned." }
|
|
3969
|
+
}
|
|
3970
|
+
},
|
|
3918
3971
|
{
|
|
3919
3972
|
method: "get",
|
|
3920
3973
|
path: "/api/v1/runs",
|
|
@@ -5903,10 +5956,9 @@ async function snapshotRoutes(app, opts) {
|
|
|
5903
5956
|
|
|
5904
5957
|
// ../api-routes/src/telemetry.ts
|
|
5905
5958
|
async function telemetryRoutes(app, opts) {
|
|
5906
|
-
app.get("/telemetry", async (
|
|
5959
|
+
app.get("/telemetry", async () => {
|
|
5907
5960
|
if (!opts.getTelemetryStatus) {
|
|
5908
|
-
|
|
5909
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
5961
|
+
throw notImplemented("Telemetry status is not available in this deployment");
|
|
5910
5962
|
}
|
|
5911
5963
|
const status = opts.getTelemetryStatus();
|
|
5912
5964
|
return {
|
|
@@ -5914,15 +5966,13 @@ async function telemetryRoutes(app, opts) {
|
|
|
5914
5966
|
anonymousId: status.anonymousId ? status.anonymousId.slice(0, 8) + "..." : void 0
|
|
5915
5967
|
};
|
|
5916
5968
|
});
|
|
5917
|
-
app.put("/telemetry", async (request
|
|
5969
|
+
app.put("/telemetry", async (request) => {
|
|
5918
5970
|
if (!opts.setTelemetryEnabled) {
|
|
5919
|
-
|
|
5920
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
5971
|
+
throw notImplemented("Telemetry configuration is not available in this deployment");
|
|
5921
5972
|
}
|
|
5922
5973
|
const { enabled } = request.body ?? {};
|
|
5923
5974
|
if (typeof enabled !== "boolean") {
|
|
5924
|
-
|
|
5925
|
-
return reply.status(err.statusCode).send(err.toJSON());
|
|
5975
|
+
throw validationError("enabled (boolean) is required");
|
|
5926
5976
|
}
|
|
5927
5977
|
opts.setTelemetryEnabled(enabled);
|
|
5928
5978
|
const status = opts.getTelemetryStatus?.();
|
|
@@ -6175,7 +6225,7 @@ function formatNotification(row) {
|
|
|
6175
6225
|
|
|
6176
6226
|
// ../api-routes/src/google.ts
|
|
6177
6227
|
import crypto14 from "crypto";
|
|
6178
|
-
import { eq as eq14, and as and3, desc as desc5, sql as
|
|
6228
|
+
import { eq as eq14, and as and3, desc as desc5, sql as sql3 } from "drizzle-orm";
|
|
6179
6229
|
|
|
6180
6230
|
// ../integration-google/src/constants.ts
|
|
6181
6231
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
@@ -7298,11 +7348,11 @@ async function googleRoutes(app, opts) {
|
|
|
7298
7348
|
const { startDate, endDate, query, page, limit } = request.query;
|
|
7299
7349
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
7300
7350
|
const conditions = [eq14(gscSearchData.projectId, project.id)];
|
|
7301
|
-
if (startDate) conditions.push(
|
|
7302
|
-
else if (cutoffDate) conditions.push(
|
|
7303
|
-
if (endDate) conditions.push(
|
|
7304
|
-
if (query) conditions.push(
|
|
7305
|
-
if (page) conditions.push(
|
|
7351
|
+
if (startDate) conditions.push(sql3`${gscSearchData.date} >= ${startDate}`);
|
|
7352
|
+
else if (cutoffDate) conditions.push(sql3`${gscSearchData.date} >= ${cutoffDate}`);
|
|
7353
|
+
if (endDate) conditions.push(sql3`${gscSearchData.date} <= ${endDate}`);
|
|
7354
|
+
if (query) conditions.push(sql3`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
|
|
7355
|
+
if (page) conditions.push(sql3`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
|
|
7306
7356
|
const rows = app.db.select().from(gscSearchData).where(and3(...conditions)).orderBy(desc5(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
|
|
7307
7357
|
return rows.map((r) => ({
|
|
7308
7358
|
date: r.date,
|
|
@@ -8528,7 +8578,7 @@ async function cdpRoutes(app, opts) {
|
|
|
8528
8578
|
|
|
8529
8579
|
// ../api-routes/src/ga.ts
|
|
8530
8580
|
import crypto16 from "crypto";
|
|
8531
|
-
import { eq as eq17, desc as desc7, and as and6, sql as
|
|
8581
|
+
import { eq as eq17, desc as desc7, and as and6, sql as sql4 } from "drizzle-orm";
|
|
8532
8582
|
function gaLog(level, action, ctx) {
|
|
8533
8583
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
8534
8584
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -8770,8 +8820,8 @@ async function ga4Routes(app, opts) {
|
|
|
8770
8820
|
tx.delete(gaTrafficSnapshots).where(
|
|
8771
8821
|
and6(
|
|
8772
8822
|
eq17(gaTrafficSnapshots.projectId, project.id),
|
|
8773
|
-
|
|
8774
|
-
|
|
8823
|
+
sql4`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
8824
|
+
sql4`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
8775
8825
|
)
|
|
8776
8826
|
).run();
|
|
8777
8827
|
for (const row of rows) {
|
|
@@ -8792,8 +8842,8 @@ async function ga4Routes(app, opts) {
|
|
|
8792
8842
|
tx.delete(gaAiReferrals).where(
|
|
8793
8843
|
and6(
|
|
8794
8844
|
eq17(gaAiReferrals.projectId, project.id),
|
|
8795
|
-
|
|
8796
|
-
|
|
8845
|
+
sql4`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
8846
|
+
sql4`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
8797
8847
|
)
|
|
8798
8848
|
).run();
|
|
8799
8849
|
for (const row of aiReferrals) {
|
|
@@ -8815,8 +8865,8 @@ async function ga4Routes(app, opts) {
|
|
|
8815
8865
|
tx.delete(gaSocialReferrals).where(
|
|
8816
8866
|
and6(
|
|
8817
8867
|
eq17(gaSocialReferrals.projectId, project.id),
|
|
8818
|
-
|
|
8819
|
-
|
|
8868
|
+
sql4`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
8869
|
+
sql4`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
8820
8870
|
)
|
|
8821
8871
|
).run();
|
|
8822
8872
|
for (const row of socialReferrals) {
|
|
@@ -8885,15 +8935,15 @@ async function ga4Routes(app, opts) {
|
|
|
8885
8935
|
const cutoff = windowCutoff(window);
|
|
8886
8936
|
const cutoffDate = cutoff?.slice(0, 10) ?? null;
|
|
8887
8937
|
const snapshotConditions = [eq17(gaTrafficSnapshots.projectId, project.id)];
|
|
8888
|
-
if (cutoffDate) snapshotConditions.push(
|
|
8938
|
+
if (cutoffDate) snapshotConditions.push(sql4`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
8889
8939
|
const aiConditions = [eq17(gaAiReferrals.projectId, project.id)];
|
|
8890
|
-
if (cutoffDate) aiConditions.push(
|
|
8940
|
+
if (cutoffDate) aiConditions.push(sql4`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
8891
8941
|
const socialConditions = [eq17(gaSocialReferrals.projectId, project.id)];
|
|
8892
|
-
if (cutoffDate) socialConditions.push(
|
|
8942
|
+
if (cutoffDate) socialConditions.push(sql4`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
8893
8943
|
const summaryRow = cutoffDate ? app.db.select({
|
|
8894
|
-
totalSessions:
|
|
8895
|
-
totalOrganicSessions:
|
|
8896
|
-
totalUsers:
|
|
8944
|
+
totalSessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
8945
|
+
totalOrganicSessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
8946
|
+
totalUsers: sql4`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
8897
8947
|
}).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).get() : app.db.select({
|
|
8898
8948
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
8899
8949
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
@@ -8905,27 +8955,27 @@ async function ga4Routes(app, opts) {
|
|
|
8905
8955
|
}).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).get();
|
|
8906
8956
|
const rows = app.db.select({
|
|
8907
8957
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
8908
|
-
sessions:
|
|
8909
|
-
organicSessions:
|
|
8910
|
-
users:
|
|
8911
|
-
}).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).groupBy(gaTrafficSnapshots.landingPage).orderBy(
|
|
8958
|
+
sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
|
|
8959
|
+
organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
8960
|
+
users: sql4`SUM(${gaTrafficSnapshots.users})`
|
|
8961
|
+
}).from(gaTrafficSnapshots).where(and6(...snapshotConditions)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql4`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
8912
8962
|
const aiReferrals = app.db.select({
|
|
8913
8963
|
source: gaAiReferrals.source,
|
|
8914
8964
|
medium: gaAiReferrals.medium,
|
|
8915
8965
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8916
|
-
sessions:
|
|
8917
|
-
users:
|
|
8918
|
-
}).from(gaAiReferrals).where(and6(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(
|
|
8966
|
+
sessions: sql4`SUM(${gaAiReferrals.sessions})`,
|
|
8967
|
+
users: sql4`SUM(${gaAiReferrals.users})`
|
|
8968
|
+
}).from(gaAiReferrals).where(and6(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
8919
8969
|
const aiDeduped = app.db.select({
|
|
8920
|
-
sessions:
|
|
8921
|
-
users:
|
|
8970
|
+
sessions: sql4`SUM(max_sessions)`,
|
|
8971
|
+
users: sql4`SUM(max_users)`
|
|
8922
8972
|
}).from(
|
|
8923
|
-
|
|
8973
|
+
sql4`(
|
|
8924
8974
|
SELECT date, source, medium,
|
|
8925
8975
|
MAX(sessions) AS max_sessions,
|
|
8926
8976
|
MAX(users) AS max_users
|
|
8927
8977
|
FROM ga_ai_referrals
|
|
8928
|
-
WHERE project_id = ${project.id}${cutoffDate ?
|
|
8978
|
+
WHERE project_id = ${project.id}${cutoffDate ? sql4` AND date >= ${cutoffDate}` : sql4``}
|
|
8929
8979
|
GROUP BY date, source, medium
|
|
8930
8980
|
)`
|
|
8931
8981
|
).get();
|
|
@@ -8933,12 +8983,12 @@ async function ga4Routes(app, opts) {
|
|
|
8933
8983
|
source: gaSocialReferrals.source,
|
|
8934
8984
|
medium: gaSocialReferrals.medium,
|
|
8935
8985
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
8936
|
-
sessions:
|
|
8937
|
-
users:
|
|
8938
|
-
}).from(gaSocialReferrals).where(and6(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(
|
|
8986
|
+
sessions: sql4`SUM(${gaSocialReferrals.sessions})`,
|
|
8987
|
+
users: sql4`SUM(${gaSocialReferrals.users})`
|
|
8988
|
+
}).from(gaSocialReferrals).where(and6(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql4`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
8939
8989
|
const socialTotals = app.db.select({
|
|
8940
|
-
sessions:
|
|
8941
|
-
users:
|
|
8990
|
+
sessions: sql4`SUM(${gaSocialReferrals.sessions})`,
|
|
8991
|
+
users: sql4`SUM(${gaSocialReferrals.users})`
|
|
8942
8992
|
}).from(gaSocialReferrals).where(and6(...socialConditions)).get();
|
|
8943
8993
|
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq17(gaTrafficSummaries.projectId, project.id)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
8944
8994
|
const total = summaryRow?.totalSessions ?? 0;
|
|
@@ -8988,7 +9038,7 @@ async function ga4Routes(app, opts) {
|
|
|
8988
9038
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8989
9039
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
8990
9040
|
const conditions = [eq17(gaAiReferrals.projectId, project.id)];
|
|
8991
|
-
if (cutoffDate) conditions.push(
|
|
9041
|
+
if (cutoffDate) conditions.push(sql4`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
8992
9042
|
const rows = app.db.select({
|
|
8993
9043
|
date: gaAiReferrals.date,
|
|
8994
9044
|
source: gaAiReferrals.source,
|
|
@@ -9004,7 +9054,7 @@ async function ga4Routes(app, opts) {
|
|
|
9004
9054
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
9005
9055
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
9006
9056
|
const conditions = [eq17(gaSocialReferrals.projectId, project.id)];
|
|
9007
|
-
if (cutoffDate) conditions.push(
|
|
9057
|
+
if (cutoffDate) conditions.push(sql4`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
9008
9058
|
const rows = app.db.select({
|
|
9009
9059
|
date: gaSocialReferrals.date,
|
|
9010
9060
|
source: gaSocialReferrals.source,
|
|
@@ -9025,10 +9075,10 @@ async function ga4Routes(app, opts) {
|
|
|
9025
9075
|
d.setDate(d.getDate() - n);
|
|
9026
9076
|
return fmt(d);
|
|
9027
9077
|
};
|
|
9028
|
-
const sumSocial = (from, to) => app.db.select({ sessions:
|
|
9078
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and6(
|
|
9029
9079
|
eq17(gaSocialReferrals.projectId, project.id),
|
|
9030
|
-
|
|
9031
|
-
|
|
9080
|
+
sql4`${gaSocialReferrals.date} >= ${from}`,
|
|
9081
|
+
sql4`${gaSocialReferrals.date} < ${to}`
|
|
9032
9082
|
)).get();
|
|
9033
9083
|
const current7d = sumSocial(daysAgo2(7), fmt(today));
|
|
9034
9084
|
const prev7d = sumSocial(daysAgo2(14), daysAgo2(7));
|
|
@@ -9037,19 +9087,19 @@ async function ga4Routes(app, opts) {
|
|
|
9037
9087
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
9038
9088
|
const sourceCurrent = app.db.select({
|
|
9039
9089
|
source: gaSocialReferrals.source,
|
|
9040
|
-
sessions:
|
|
9090
|
+
sessions: sql4`SUM(${gaSocialReferrals.sessions})`
|
|
9041
9091
|
}).from(gaSocialReferrals).where(and6(
|
|
9042
9092
|
eq17(gaSocialReferrals.projectId, project.id),
|
|
9043
|
-
|
|
9044
|
-
|
|
9093
|
+
sql4`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
|
|
9094
|
+
sql4`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
9045
9095
|
)).groupBy(gaSocialReferrals.source).all();
|
|
9046
9096
|
const sourcePrev = app.db.select({
|
|
9047
9097
|
source: gaSocialReferrals.source,
|
|
9048
|
-
sessions:
|
|
9098
|
+
sessions: sql4`SUM(${gaSocialReferrals.sessions})`
|
|
9049
9099
|
}).from(gaSocialReferrals).where(and6(
|
|
9050
9100
|
eq17(gaSocialReferrals.projectId, project.id),
|
|
9051
|
-
|
|
9052
|
-
|
|
9101
|
+
sql4`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
|
|
9102
|
+
sql4`${gaSocialReferrals.date} < ${daysAgo2(7)}`
|
|
9053
9103
|
)).groupBy(gaSocialReferrals.source).all();
|
|
9054
9104
|
const prevMap = new Map(sourcePrev.map((r) => [r.source, r.sessions]));
|
|
9055
9105
|
let biggestMover = null;
|
|
@@ -9088,15 +9138,15 @@ async function ga4Routes(app, opts) {
|
|
|
9088
9138
|
return fmt(d);
|
|
9089
9139
|
};
|
|
9090
9140
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
9091
|
-
const sumTotal = (from, to) => app.db.select({ sessions:
|
|
9092
|
-
const sumOrganic = (from, to) => app.db.select({ sessions:
|
|
9093
|
-
const sumAi = (from, to) => app.db.select({ sessions:
|
|
9141
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and6(eq17(gaTrafficSnapshots.projectId, project.id), sql4`${gaTrafficSnapshots.date} >= ${from}`, sql4`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
9142
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and6(eq17(gaTrafficSnapshots.projectId, project.id), sql4`${gaTrafficSnapshots.date} >= ${from}`, sql4`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
9143
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(max_sessions), 0)` }).from(sql4`(
|
|
9094
9144
|
SELECT date, source, medium, MAX(sessions) AS max_sessions
|
|
9095
9145
|
FROM ga_ai_referrals
|
|
9096
9146
|
WHERE project_id = ${project.id} AND date >= ${from} AND date < ${to}
|
|
9097
9147
|
GROUP BY date, source, medium
|
|
9098
9148
|
)`).get();
|
|
9099
|
-
const sumSocial = (from, to) => app.db.select({ sessions:
|
|
9149
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql4`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and6(eq17(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${from}`, sql4`${gaSocialReferrals.date} < ${to}`)).get();
|
|
9100
9150
|
const todayStr = fmt(today);
|
|
9101
9151
|
const buildTrend = (sum) => {
|
|
9102
9152
|
const c7 = sum(daysAgo2(7), todayStr)?.sessions ?? 0;
|
|
@@ -9105,18 +9155,18 @@ async function ga4Routes(app, opts) {
|
|
|
9105
9155
|
const p30 = sum(daysAgo2(60), daysAgo2(30))?.sessions ?? 0;
|
|
9106
9156
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
9107
9157
|
};
|
|
9108
|
-
const aiSourceCurrent = app.db.select({ source:
|
|
9158
|
+
const aiSourceCurrent = app.db.select({ source: sql4`source`, sessions: sql4`COALESCE(SUM(max_sessions), 0)` }).from(sql4`(
|
|
9109
9159
|
SELECT date, source, medium, MAX(sessions) AS max_sessions
|
|
9110
9160
|
FROM ga_ai_referrals
|
|
9111
9161
|
WHERE project_id = ${project.id} AND date >= ${daysAgo2(7)} AND date < ${todayStr}
|
|
9112
9162
|
GROUP BY date, source, medium
|
|
9113
|
-
)`).groupBy(
|
|
9114
|
-
const aiSourcePrev = app.db.select({ source:
|
|
9163
|
+
)`).groupBy(sql4`source`).all();
|
|
9164
|
+
const aiSourcePrev = app.db.select({ source: sql4`source`, sessions: sql4`COALESCE(SUM(max_sessions), 0)` }).from(sql4`(
|
|
9115
9165
|
SELECT date, source, medium, MAX(sessions) AS max_sessions
|
|
9116
9166
|
FROM ga_ai_referrals
|
|
9117
9167
|
WHERE project_id = ${project.id} AND date >= ${daysAgo2(14)} AND date < ${daysAgo2(7)}
|
|
9118
9168
|
GROUP BY date, source, medium
|
|
9119
|
-
)`).groupBy(
|
|
9169
|
+
)`).groupBy(sql4`source`).all();
|
|
9120
9170
|
const findBiggestMover = (current, prev) => {
|
|
9121
9171
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
9122
9172
|
let mover = null;
|
|
@@ -9131,8 +9181,8 @@ async function ga4Routes(app, opts) {
|
|
|
9131
9181
|
}
|
|
9132
9182
|
return mover;
|
|
9133
9183
|
};
|
|
9134
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions:
|
|
9135
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions:
|
|
9184
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql4`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and6(eq17(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql4`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
9185
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql4`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and6(eq17(gaSocialReferrals.projectId, project.id), sql4`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql4`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
9136
9186
|
return {
|
|
9137
9187
|
total: buildTrend(sumTotal),
|
|
9138
9188
|
organic: buildTrend(sumOrganic),
|
|
@@ -9147,12 +9197,12 @@ async function ga4Routes(app, opts) {
|
|
|
9147
9197
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
9148
9198
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
9149
9199
|
const conditions = [eq17(gaTrafficSnapshots.projectId, project.id)];
|
|
9150
|
-
if (cutoffDate) conditions.push(
|
|
9200
|
+
if (cutoffDate) conditions.push(sql4`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
9151
9201
|
const rows = app.db.select({
|
|
9152
9202
|
date: gaTrafficSnapshots.date,
|
|
9153
|
-
sessions:
|
|
9154
|
-
organicSessions:
|
|
9155
|
-
users:
|
|
9203
|
+
sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
|
|
9204
|
+
organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
9205
|
+
users: sql4`SUM(${gaTrafficSnapshots.users})`
|
|
9156
9206
|
}).from(gaTrafficSnapshots).where(and6(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
9157
9207
|
return rows.map((r) => ({
|
|
9158
9208
|
date: r.date,
|
|
@@ -9166,10 +9216,10 @@ async function ga4Routes(app, opts) {
|
|
|
9166
9216
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
9167
9217
|
const trafficPages = app.db.select({
|
|
9168
9218
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
9169
|
-
sessions:
|
|
9170
|
-
organicSessions:
|
|
9171
|
-
users:
|
|
9172
|
-
}).from(gaTrafficSnapshots).where(eq17(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.landingPage).orderBy(
|
|
9219
|
+
sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
|
|
9220
|
+
organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
9221
|
+
users: sql4`SUM(${gaTrafficSnapshots.users})`
|
|
9222
|
+
}).from(gaTrafficSnapshots).where(eq17(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql4`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
9173
9223
|
return {
|
|
9174
9224
|
pages: trafficPages.map((r) => ({
|
|
9175
9225
|
landingPage: r.landingPage,
|
|
@@ -13442,7 +13492,7 @@ import crypto18 from "crypto";
|
|
|
13442
13492
|
import fs4 from "fs";
|
|
13443
13493
|
import path5 from "path";
|
|
13444
13494
|
import os4 from "os";
|
|
13445
|
-
import { and as and7, eq as eq18, inArray as inArray3, sql as
|
|
13495
|
+
import { and as and7, eq as eq18, inArray as inArray3, sql as sql5 } from "drizzle-orm";
|
|
13446
13496
|
|
|
13447
13497
|
// src/citation-utils.ts
|
|
13448
13498
|
function domainMatches(domain, canonicalDomain) {
|
|
@@ -13956,7 +14006,7 @@ var JobRunner = class {
|
|
|
13956
14006
|
updatedAt: now
|
|
13957
14007
|
}).onConflictDoUpdate({
|
|
13958
14008
|
target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
|
|
13959
|
-
set: { count:
|
|
14009
|
+
set: { count: sql5`${usageCounters.count} + ${count}`, updatedAt: now }
|
|
13960
14010
|
}).run();
|
|
13961
14011
|
}
|
|
13962
14012
|
flushProviderUsage(projectId, providerDispatchCounts) {
|
|
@@ -14009,7 +14059,7 @@ function getCurrentUsageDay() {
|
|
|
14009
14059
|
|
|
14010
14060
|
// src/gsc-sync.ts
|
|
14011
14061
|
import crypto19 from "crypto";
|
|
14012
|
-
import { eq as eq19, and as and8, sql as
|
|
14062
|
+
import { eq as eq19, and as and8, sql as sql6 } from "drizzle-orm";
|
|
14013
14063
|
var log2 = createLogger("GscSync");
|
|
14014
14064
|
function formatDate2(d) {
|
|
14015
14065
|
return d.toISOString().split("T")[0];
|
|
@@ -14063,8 +14113,8 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
14063
14113
|
db.delete(gscSearchData).where(
|
|
14064
14114
|
and8(
|
|
14065
14115
|
eq19(gscSearchData.projectId, projectId),
|
|
14066
|
-
|
|
14067
|
-
|
|
14116
|
+
sql6`${gscSearchData.date} >= ${startDate}`,
|
|
14117
|
+
sql6`${gscSearchData.date} <= ${endDate}`
|
|
14068
14118
|
)
|
|
14069
14119
|
).run();
|
|
14070
14120
|
const batchSize = 500;
|
|
@@ -14787,8 +14837,8 @@ var RunCoordinator = class {
|
|
|
14787
14837
|
};
|
|
14788
14838
|
|
|
14789
14839
|
// src/agent/session-registry.ts
|
|
14790
|
-
import
|
|
14791
|
-
import { eq as
|
|
14840
|
+
import crypto23 from "crypto";
|
|
14841
|
+
import { eq as eq24 } from "drizzle-orm";
|
|
14792
14842
|
|
|
14793
14843
|
// src/agent/session.ts
|
|
14794
14844
|
import fs7 from "fs";
|
|
@@ -14814,7 +14864,7 @@ var AGENT_PROVIDERS = {
|
|
|
14814
14864
|
[AgentProviderIds.gemini]: {
|
|
14815
14865
|
piAiProvider: "google",
|
|
14816
14866
|
label: "Google (Gemini)",
|
|
14817
|
-
defaultModel: "gemini-2.5-
|
|
14867
|
+
defaultModel: "gemini-2.5-flash",
|
|
14818
14868
|
autoDetectPriority: 2
|
|
14819
14869
|
},
|
|
14820
14870
|
[AgentProviderIds.zai]: {
|
|
@@ -15005,6 +15055,105 @@ function buildSkillDocTools() {
|
|
|
15005
15055
|
|
|
15006
15056
|
// src/agent/tools.ts
|
|
15007
15057
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
15058
|
+
|
|
15059
|
+
// src/agent/memory-store.ts
|
|
15060
|
+
import crypto22 from "crypto";
|
|
15061
|
+
import { and as and11, desc as desc9, eq as eq23, like, sql as sql7 } from "drizzle-orm";
|
|
15062
|
+
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
15063
|
+
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
15064
|
+
function rowToDto(row) {
|
|
15065
|
+
return {
|
|
15066
|
+
id: row.id,
|
|
15067
|
+
key: row.key,
|
|
15068
|
+
value: row.value,
|
|
15069
|
+
source: row.source,
|
|
15070
|
+
createdAt: row.createdAt,
|
|
15071
|
+
updatedAt: row.updatedAt
|
|
15072
|
+
};
|
|
15073
|
+
}
|
|
15074
|
+
function listMemoryEntries(db, projectId, opts = {}) {
|
|
15075
|
+
const query = db.select().from(agentMemory).where(eq23(agentMemory.projectId, projectId)).orderBy(desc9(agentMemory.updatedAt));
|
|
15076
|
+
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
15077
|
+
return rows.map(rowToDto);
|
|
15078
|
+
}
|
|
15079
|
+
function upsertMemoryEntry(db, args) {
|
|
15080
|
+
if (Buffer.byteLength(args.value, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
|
|
15081
|
+
throw new Error(
|
|
15082
|
+
`memory value exceeds ${AGENT_MEMORY_VALUE_MAX_BYTES} bytes (got ${Buffer.byteLength(args.value, "utf8")})`
|
|
15083
|
+
);
|
|
15084
|
+
}
|
|
15085
|
+
if (args.source !== MemorySources.compaction && args.key.startsWith(COMPACTION_KEY_PREFIX)) {
|
|
15086
|
+
throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
|
|
15087
|
+
}
|
|
15088
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15089
|
+
const id = crypto22.randomUUID();
|
|
15090
|
+
db.insert(agentMemory).values({
|
|
15091
|
+
id,
|
|
15092
|
+
projectId: args.projectId,
|
|
15093
|
+
key: args.key,
|
|
15094
|
+
value: args.value,
|
|
15095
|
+
source: args.source,
|
|
15096
|
+
createdAt: now,
|
|
15097
|
+
updatedAt: now
|
|
15098
|
+
}).onConflictDoUpdate({
|
|
15099
|
+
target: [agentMemory.projectId, agentMemory.key],
|
|
15100
|
+
set: {
|
|
15101
|
+
value: args.value,
|
|
15102
|
+
source: args.source,
|
|
15103
|
+
updatedAt: now
|
|
15104
|
+
}
|
|
15105
|
+
}).run();
|
|
15106
|
+
const row = db.select().from(agentMemory).where(and11(eq23(agentMemory.projectId, args.projectId), eq23(agentMemory.key, args.key))).get();
|
|
15107
|
+
if (!row) throw new Error("memory upsert produced no row");
|
|
15108
|
+
return rowToDto(row);
|
|
15109
|
+
}
|
|
15110
|
+
function deleteMemoryEntry(db, projectId, key) {
|
|
15111
|
+
const result = db.delete(agentMemory).where(and11(eq23(agentMemory.projectId, projectId), eq23(agentMemory.key, key))).run();
|
|
15112
|
+
const changes = result.changes ?? 0;
|
|
15113
|
+
return changes > 0;
|
|
15114
|
+
}
|
|
15115
|
+
function loadRecentForHydrate(db, projectId, limit) {
|
|
15116
|
+
return listMemoryEntries(db, projectId, { limit });
|
|
15117
|
+
}
|
|
15118
|
+
function writeCompactionNote(db, args) {
|
|
15119
|
+
if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
|
|
15120
|
+
throw new Error(
|
|
15121
|
+
`compaction summary exceeds ${AGENT_MEMORY_VALUE_MAX_BYTES} bytes; summarizer produced too much text`
|
|
15122
|
+
);
|
|
15123
|
+
}
|
|
15124
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15125
|
+
const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
|
|
15126
|
+
const id = crypto22.randomUUID();
|
|
15127
|
+
let inserted;
|
|
15128
|
+
db.transaction((tx) => {
|
|
15129
|
+
tx.insert(agentMemory).values({
|
|
15130
|
+
id,
|
|
15131
|
+
projectId: args.projectId,
|
|
15132
|
+
key,
|
|
15133
|
+
value: args.summary,
|
|
15134
|
+
source: MemorySources.compaction,
|
|
15135
|
+
createdAt: now,
|
|
15136
|
+
updatedAt: now
|
|
15137
|
+
}).run();
|
|
15138
|
+
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
15139
|
+
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
15140
|
+
and11(
|
|
15141
|
+
eq23(agentMemory.projectId, args.projectId),
|
|
15142
|
+
like(agentMemory.key, `${sessionPrefix}%`)
|
|
15143
|
+
)
|
|
15144
|
+
).orderBy(desc9(agentMemory.updatedAt)).all();
|
|
15145
|
+
const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
|
|
15146
|
+
if (stale.length > 0) {
|
|
15147
|
+
tx.delete(agentMemory).where(sql7`${agentMemory.id} IN (${sql7.join(stale.map((s) => sql7`${s}`), sql7`, `)})`).run();
|
|
15148
|
+
}
|
|
15149
|
+
const row = tx.select().from(agentMemory).where(and11(eq23(agentMemory.projectId, args.projectId), eq23(agentMemory.key, key))).get();
|
|
15150
|
+
if (row) inserted = rowToDto(row);
|
|
15151
|
+
});
|
|
15152
|
+
if (!inserted) throw new Error("compaction note write produced no row");
|
|
15153
|
+
return inserted;
|
|
15154
|
+
}
|
|
15155
|
+
|
|
15156
|
+
// src/agent/tools.ts
|
|
15008
15157
|
var MAX_TOOL_RESULT_CHARS = 2e4;
|
|
15009
15158
|
function truncate(json) {
|
|
15010
15159
|
if (json.length <= MAX_TOOL_RESULT_CHARS) return json;
|
|
@@ -15144,6 +15293,27 @@ function buildGetRunTool(ctx) {
|
|
|
15144
15293
|
}
|
|
15145
15294
|
};
|
|
15146
15295
|
}
|
|
15296
|
+
var RecallSchema = Type2.Object({
|
|
15297
|
+
limit: Type2.Optional(
|
|
15298
|
+
Type2.Number({
|
|
15299
|
+
description: "Max notes to return, ordered newest-first. Default 50. Max 100.",
|
|
15300
|
+
minimum: 1,
|
|
15301
|
+
maximum: 100
|
|
15302
|
+
})
|
|
15303
|
+
)
|
|
15304
|
+
});
|
|
15305
|
+
function buildRecallTool(ctx) {
|
|
15306
|
+
return {
|
|
15307
|
+
name: "recall",
|
|
15308
|
+
label: "Recall memory",
|
|
15309
|
+
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.",
|
|
15310
|
+
parameters: RecallSchema,
|
|
15311
|
+
execute: async (_toolCallId, params) => {
|
|
15312
|
+
const entries = listMemoryEntries(ctx.db, ctx.projectId, { limit: params.limit ?? 50 });
|
|
15313
|
+
return textResult2({ entries });
|
|
15314
|
+
}
|
|
15315
|
+
};
|
|
15316
|
+
}
|
|
15147
15317
|
function buildReadTools(ctx) {
|
|
15148
15318
|
return [
|
|
15149
15319
|
buildGetStatusTool(ctx),
|
|
@@ -15152,7 +15322,8 @@ function buildReadTools(ctx) {
|
|
|
15152
15322
|
buildGetInsightsTool(ctx),
|
|
15153
15323
|
buildListKeywordsTool(ctx),
|
|
15154
15324
|
buildListCompetitorsTool(ctx),
|
|
15155
|
-
buildGetRunTool(ctx)
|
|
15325
|
+
buildGetRunTool(ctx),
|
|
15326
|
+
buildRecallTool(ctx)
|
|
15156
15327
|
];
|
|
15157
15328
|
}
|
|
15158
15329
|
var RunSweepSchema = Type2.Object({
|
|
@@ -15307,6 +15478,58 @@ function buildAttachAgentWebhookTool(ctx) {
|
|
|
15307
15478
|
}
|
|
15308
15479
|
};
|
|
15309
15480
|
}
|
|
15481
|
+
var RememberSchema = Type2.Object({
|
|
15482
|
+
key: Type2.String({
|
|
15483
|
+
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.`,
|
|
15484
|
+
minLength: 1,
|
|
15485
|
+
maxLength: AGENT_MEMORY_KEY_MAX_LENGTH
|
|
15486
|
+
}),
|
|
15487
|
+
value: Type2.String({
|
|
15488
|
+
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.`,
|
|
15489
|
+
minLength: 1
|
|
15490
|
+
})
|
|
15491
|
+
});
|
|
15492
|
+
function buildRememberTool(ctx) {
|
|
15493
|
+
return {
|
|
15494
|
+
name: "remember",
|
|
15495
|
+
label: "Remember",
|
|
15496
|
+
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.",
|
|
15497
|
+
parameters: RememberSchema,
|
|
15498
|
+
execute: async (_toolCallId, params) => {
|
|
15499
|
+
const entry = upsertMemoryEntry(ctx.db, {
|
|
15500
|
+
projectId: ctx.projectId,
|
|
15501
|
+
key: params.key,
|
|
15502
|
+
value: params.value,
|
|
15503
|
+
source: MemorySources.aero
|
|
15504
|
+
});
|
|
15505
|
+
return textResult2({ status: "remembered", entry });
|
|
15506
|
+
}
|
|
15507
|
+
};
|
|
15508
|
+
}
|
|
15509
|
+
var ForgetSchema = Type2.Object({
|
|
15510
|
+
key: Type2.String({
|
|
15511
|
+
description: "Exact key of the note to remove. No-op (status=missing) when no note exists for that key.",
|
|
15512
|
+
minLength: 1,
|
|
15513
|
+
maxLength: AGENT_MEMORY_KEY_MAX_LENGTH
|
|
15514
|
+
})
|
|
15515
|
+
});
|
|
15516
|
+
function buildForgetTool(ctx) {
|
|
15517
|
+
return {
|
|
15518
|
+
name: "forget",
|
|
15519
|
+
label: "Forget",
|
|
15520
|
+
description: "Delete a durable note by key. Use when a previously-remembered fact is wrong or no longer relevant.",
|
|
15521
|
+
parameters: ForgetSchema,
|
|
15522
|
+
execute: async (_toolCallId, params) => {
|
|
15523
|
+
if (params.key.startsWith(COMPACTION_KEY_PREFIX)) {
|
|
15524
|
+
throw new Error(
|
|
15525
|
+
`cannot forget compaction notes directly \u2014 they are pruned automatically (key prefix "${COMPACTION_KEY_PREFIX}" is reserved)`
|
|
15526
|
+
);
|
|
15527
|
+
}
|
|
15528
|
+
const removed = deleteMemoryEntry(ctx.db, ctx.projectId, params.key);
|
|
15529
|
+
return textResult2({ status: removed ? "forgotten" : "missing", key: params.key });
|
|
15530
|
+
}
|
|
15531
|
+
};
|
|
15532
|
+
}
|
|
15310
15533
|
function buildWriteTools(ctx) {
|
|
15311
15534
|
return [
|
|
15312
15535
|
buildRunSweepTool(ctx),
|
|
@@ -15314,7 +15537,9 @@ function buildWriteTools(ctx) {
|
|
|
15314
15537
|
buildAddKeywordsTool(ctx),
|
|
15315
15538
|
buildAddCompetitorsTool(ctx),
|
|
15316
15539
|
buildUpdateScheduleTool(ctx),
|
|
15317
|
-
buildAttachAgentWebhookTool(ctx)
|
|
15540
|
+
buildAttachAgentWebhookTool(ctx),
|
|
15541
|
+
buildRememberTool(ctx),
|
|
15542
|
+
buildForgetTool(ctx)
|
|
15318
15543
|
];
|
|
15319
15544
|
}
|
|
15320
15545
|
function buildAllTools(ctx) {
|
|
@@ -15366,7 +15591,13 @@ function createAeroSession(opts) {
|
|
|
15366
15591
|
if (!provider) throw new Error(missingProviderMessage());
|
|
15367
15592
|
const model = resolveAeroModel(provider, opts.modelId);
|
|
15368
15593
|
const toolScope = opts.toolScope ?? "all";
|
|
15369
|
-
const
|
|
15594
|
+
const toolCtx = {
|
|
15595
|
+
client: opts.client,
|
|
15596
|
+
projectName: opts.projectName,
|
|
15597
|
+
db: opts.db,
|
|
15598
|
+
projectId: opts.projectId
|
|
15599
|
+
};
|
|
15600
|
+
const stateTools = toolScope === "read-only" ? buildReadTools(toolCtx) : buildAllTools(toolCtx);
|
|
15370
15601
|
const defaultTools = [...stateTools, ...buildSkillDocTools()];
|
|
15371
15602
|
const tools = opts.tools ?? defaultTools;
|
|
15372
15603
|
return new Agent({
|
|
@@ -15387,13 +15618,151 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
15387
15618
|
return { provider, modelId };
|
|
15388
15619
|
}
|
|
15389
15620
|
|
|
15621
|
+
// src/agent/compaction.ts
|
|
15622
|
+
import { complete } from "@mariozechner/pi-ai";
|
|
15623
|
+
|
|
15624
|
+
// src/agent/compaction-config.ts
|
|
15625
|
+
var COMPACTION_TOKEN_THRESHOLD = 6e4;
|
|
15626
|
+
var COMPACTION_TARGET_RATIO = 0.5;
|
|
15627
|
+
var COMPACTION_PRESERVE_TAIL_MESSAGES = 10;
|
|
15628
|
+
var COMPACTION_MAX_MESSAGES = 400;
|
|
15629
|
+
|
|
15630
|
+
// src/agent/token-counter.ts
|
|
15631
|
+
var CHARS_PER_TOKEN = 4;
|
|
15632
|
+
function estimateMessageTokens(message) {
|
|
15633
|
+
const content = message.content;
|
|
15634
|
+
if (content === void 0) return 0;
|
|
15635
|
+
if (typeof content === "string") {
|
|
15636
|
+
return Math.ceil(content.length / CHARS_PER_TOKEN);
|
|
15637
|
+
}
|
|
15638
|
+
if (!Array.isArray(content)) return 0;
|
|
15639
|
+
let chars = 0;
|
|
15640
|
+
for (const part of content) {
|
|
15641
|
+
if (part && typeof part === "object" && "type" in part) {
|
|
15642
|
+
const p = part;
|
|
15643
|
+
switch (p.type) {
|
|
15644
|
+
case "text":
|
|
15645
|
+
chars += (p.text ?? "").length;
|
|
15646
|
+
break;
|
|
15647
|
+
case "thinking":
|
|
15648
|
+
chars += (p.thinking ?? "").length;
|
|
15649
|
+
break;
|
|
15650
|
+
case "toolCall":
|
|
15651
|
+
try {
|
|
15652
|
+
chars += JSON.stringify(p.arguments ?? {}).length;
|
|
15653
|
+
} catch {
|
|
15654
|
+
chars += 64;
|
|
15655
|
+
}
|
|
15656
|
+
break;
|
|
15657
|
+
case "image":
|
|
15658
|
+
chars += 1024;
|
|
15659
|
+
break;
|
|
15660
|
+
default:
|
|
15661
|
+
break;
|
|
15662
|
+
}
|
|
15663
|
+
}
|
|
15664
|
+
}
|
|
15665
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
15666
|
+
}
|
|
15667
|
+
function estimateTranscriptTokens(messages) {
|
|
15668
|
+
let total = 0;
|
|
15669
|
+
for (const m of messages) total += estimateMessageTokens(m);
|
|
15670
|
+
return total;
|
|
15671
|
+
}
|
|
15672
|
+
|
|
15673
|
+
// src/agent/compaction.ts
|
|
15674
|
+
function shouldCompact(messages) {
|
|
15675
|
+
if (messages.length >= COMPACTION_MAX_MESSAGES) return true;
|
|
15676
|
+
return estimateTranscriptTokens(messages) >= COMPACTION_TOKEN_THRESHOLD;
|
|
15677
|
+
}
|
|
15678
|
+
function findSafeSplit(messages, targetIndex) {
|
|
15679
|
+
const maxSplit = messages.length - COMPACTION_PRESERVE_TAIL_MESSAGES;
|
|
15680
|
+
if (maxSplit <= 0) return 0;
|
|
15681
|
+
const boundedTarget = Math.max(0, Math.min(targetIndex, maxSplit));
|
|
15682
|
+
for (let i = boundedTarget; i <= maxSplit; i++) {
|
|
15683
|
+
const m = messages[i];
|
|
15684
|
+
if (m && m.role === "user") return i;
|
|
15685
|
+
}
|
|
15686
|
+
return 0;
|
|
15687
|
+
}
|
|
15688
|
+
function toLlmMessages(messages) {
|
|
15689
|
+
const out = [];
|
|
15690
|
+
for (const m of messages) {
|
|
15691
|
+
if (m.role === "user" || m.role === "assistant" || m.role === "toolResult") {
|
|
15692
|
+
out.push(m);
|
|
15693
|
+
}
|
|
15694
|
+
}
|
|
15695
|
+
return out;
|
|
15696
|
+
}
|
|
15697
|
+
var SUMMARY_SYSTEM_PROMPT = `You compress an AI agent conversation transcript into a short durable note.
|
|
15698
|
+
|
|
15699
|
+
Extract only:
|
|
15700
|
+
- User intents and requests
|
|
15701
|
+
- Actions the agent took and their outcomes
|
|
15702
|
+
- Key findings, insights, decisions
|
|
15703
|
+
- Outstanding TODOs or deferred follow-ups
|
|
15704
|
+
|
|
15705
|
+
Style: dense bullet points. No preamble, no closing remarks, no agent self-commentary. Keep the note under 1500 characters.`;
|
|
15706
|
+
function truncateToByteLimit(text, maxBytes) {
|
|
15707
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
15708
|
+
const suffix = "\u2026[truncated]";
|
|
15709
|
+
const budget = maxBytes - Buffer.byteLength(suffix, "utf8");
|
|
15710
|
+
let buf = Buffer.from(text, "utf8").subarray(0, budget);
|
|
15711
|
+
while (buf.length > 0 && (buf[buf.length - 1] & 192) === 128) {
|
|
15712
|
+
buf = buf.subarray(0, buf.length - 1);
|
|
15713
|
+
}
|
|
15714
|
+
return buf.toString("utf8") + suffix;
|
|
15715
|
+
}
|
|
15716
|
+
async function runSummaryLlm(args) {
|
|
15717
|
+
const context = {
|
|
15718
|
+
systemPrompt: SUMMARY_SYSTEM_PROMPT,
|
|
15719
|
+
messages: toLlmMessages(args.chunk)
|
|
15720
|
+
};
|
|
15721
|
+
const apiKey = args.getApiKey?.(args.model.provider);
|
|
15722
|
+
const resp = await complete(args.model, context, apiKey ? { apiKey } : {});
|
|
15723
|
+
const parts = resp.content.filter((p) => p.type === "text");
|
|
15724
|
+
const text = parts.map((p) => p.text).join("\n").trim();
|
|
15725
|
+
if (!text) throw new Error("summary LLM returned no text content");
|
|
15726
|
+
return text;
|
|
15727
|
+
}
|
|
15728
|
+
async function compactMessages(args) {
|
|
15729
|
+
const target = Math.floor(args.messages.length * COMPACTION_TARGET_RATIO);
|
|
15730
|
+
const split = findSafeSplit(args.messages, target);
|
|
15731
|
+
if (split === 0) return null;
|
|
15732
|
+
const chunk = args.messages.slice(0, split);
|
|
15733
|
+
const suffix = args.messages.slice(split);
|
|
15734
|
+
const summarize = args.summarize ?? runSummaryLlm;
|
|
15735
|
+
const rawSummary = await summarize({ model: args.model, chunk, getApiKey: args.getApiKey });
|
|
15736
|
+
const summary = truncateToByteLimit(rawSummary, AGENT_MEMORY_VALUE_MAX_BYTES);
|
|
15737
|
+
writeCompactionNote(args.db, {
|
|
15738
|
+
projectId: args.projectId,
|
|
15739
|
+
sessionId: args.sessionId,
|
|
15740
|
+
summary,
|
|
15741
|
+
removedCount: chunk.length
|
|
15742
|
+
});
|
|
15743
|
+
return { messages: suffix, removedCount: chunk.length, summary };
|
|
15744
|
+
}
|
|
15745
|
+
|
|
15390
15746
|
// src/agent/session-registry.ts
|
|
15391
15747
|
var log7 = createLogger("SessionRegistry");
|
|
15748
|
+
var MAX_HYDRATE_NOTES = 20;
|
|
15749
|
+
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
15750
|
+
function escapeMemoryFragment(value) {
|
|
15751
|
+
return value.replace(/<(\/?)memory>/gi, "<$1\u200Cmemory>");
|
|
15752
|
+
}
|
|
15392
15753
|
var SessionRegistry = class {
|
|
15393
15754
|
live = /* @__PURE__ */ new Map();
|
|
15394
15755
|
pending = /* @__PURE__ */ new Map();
|
|
15395
15756
|
/** Last tool scope used on the live Agent for a project. Read in getOrCreate to know when to swap. */
|
|
15396
15757
|
scopes = /* @__PURE__ */ new Map();
|
|
15758
|
+
/** Cached resolved project id per project name, used so alignScope can rebuild tool context without a DB roundtrip. */
|
|
15759
|
+
projectIds = /* @__PURE__ */ new Map();
|
|
15760
|
+
/**
|
|
15761
|
+
* In-flight compaction promises keyed by project name. A second
|
|
15762
|
+
* `acquireForTurn` that arrives while the first is still summarizing
|
|
15763
|
+
* awaits the same promise instead of kicking off a duplicate LLM call.
|
|
15764
|
+
*/
|
|
15765
|
+
compactions = /* @__PURE__ */ new Map();
|
|
15397
15766
|
opts;
|
|
15398
15767
|
constructor(opts) {
|
|
15399
15768
|
this.opts = opts;
|
|
@@ -15424,19 +15793,22 @@ var SessionRegistry = class {
|
|
|
15424
15793
|
modelProvider: effectiveProvider,
|
|
15425
15794
|
modelId: effectiveModelId,
|
|
15426
15795
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15427
|
-
}).where(
|
|
15796
|
+
}).where(eq24(agentSessions.projectId, projectId)).run();
|
|
15428
15797
|
}
|
|
15429
15798
|
const agent2 = createAeroSession({
|
|
15430
15799
|
projectName,
|
|
15431
15800
|
client: this.opts.client,
|
|
15432
15801
|
config: this.opts.config,
|
|
15802
|
+
db: this.opts.db,
|
|
15803
|
+
projectId,
|
|
15433
15804
|
provider: effectiveProvider,
|
|
15434
15805
|
modelId: effectiveModelId,
|
|
15435
|
-
systemPromptOverride: row.systemPrompt,
|
|
15806
|
+
systemPromptOverride: this.buildHydratedSystemPrompt(projectId, row.systemPrompt),
|
|
15436
15807
|
initialMessages: persistedMessages,
|
|
15437
15808
|
toolScope: preferences?.toolScope
|
|
15438
15809
|
});
|
|
15439
15810
|
this.scopes.set(projectName, preferences?.toolScope ?? "all");
|
|
15811
|
+
this.projectIds.set(projectName, projectId);
|
|
15440
15812
|
if (queued.length > 0) {
|
|
15441
15813
|
this.appendPending(projectName, queued);
|
|
15442
15814
|
this.updateRow(projectId, { followUpQueue: "[]" });
|
|
@@ -15451,14 +15823,21 @@ var SessionRegistry = class {
|
|
|
15451
15823
|
projectName,
|
|
15452
15824
|
client: this.opts.client,
|
|
15453
15825
|
config: this.opts.config,
|
|
15826
|
+
db: this.opts.db,
|
|
15827
|
+
projectId,
|
|
15454
15828
|
provider,
|
|
15455
15829
|
modelId,
|
|
15456
|
-
|
|
15830
|
+
// Hydrate on the fresh path too — a brand-new session may still see
|
|
15831
|
+
// notes if they were seeded via CLI/API before the first prompt.
|
|
15832
|
+
systemPromptOverride: this.buildHydratedSystemPrompt(projectId, systemPrompt),
|
|
15457
15833
|
toolScope: preferences?.toolScope
|
|
15458
15834
|
});
|
|
15459
15835
|
this.scopes.set(projectName, preferences?.toolScope ?? "all");
|
|
15836
|
+
this.projectIds.set(projectName, projectId);
|
|
15460
15837
|
this.insertRow({
|
|
15461
15838
|
projectId,
|
|
15839
|
+
// Persist the raw (unhydrated) prompt so the DB remains canonical —
|
|
15840
|
+
// the `<memory>` block is rebuilt from the notes table on every load.
|
|
15462
15841
|
systemPrompt,
|
|
15463
15842
|
modelProvider: provider,
|
|
15464
15843
|
modelId,
|
|
@@ -15469,6 +15848,63 @@ var SessionRegistry = class {
|
|
|
15469
15848
|
this.registerDrainHook(agent, projectName);
|
|
15470
15849
|
return agent;
|
|
15471
15850
|
}
|
|
15851
|
+
/**
|
|
15852
|
+
* Append the `<memory>` block to a base system prompt, sourced from the
|
|
15853
|
+
* `agent_memory` table. Returns the base prompt unchanged when no notes
|
|
15854
|
+
* exist — an empty block would just be prompt noise. Truncates to
|
|
15855
|
+
* `MAX_HYDRATE_BYTES`, dropping oldest-first, so the block is bounded
|
|
15856
|
+
* even when notes sit near their 2 KB cap.
|
|
15857
|
+
*
|
|
15858
|
+
* Note values come from LLM-authored compaction summaries and operator
|
|
15859
|
+
* input, so they are treated as untrusted data: closing tags that could
|
|
15860
|
+
* escape the `<memory>` wrapper are neutralized before interpolation.
|
|
15861
|
+
*/
|
|
15862
|
+
buildHydratedSystemPrompt(projectId, basePrompt) {
|
|
15863
|
+
const entries = loadRecentForHydrate(this.opts.db, projectId, MAX_HYDRATE_NOTES);
|
|
15864
|
+
if (entries.length === 0) return basePrompt;
|
|
15865
|
+
let totalBytes = 0;
|
|
15866
|
+
const kept = [];
|
|
15867
|
+
for (const entry of entries) {
|
|
15868
|
+
const escaped = {
|
|
15869
|
+
source: escapeMemoryFragment(entry.source),
|
|
15870
|
+
key: escapeMemoryFragment(entry.key),
|
|
15871
|
+
value: escapeMemoryFragment(entry.value)
|
|
15872
|
+
};
|
|
15873
|
+
const line = `- [${escaped.source}] ${escaped.key}: ${escaped.value}
|
|
15874
|
+
`;
|
|
15875
|
+
const bytes = Buffer.byteLength(line, "utf8");
|
|
15876
|
+
if (totalBytes + bytes > MAX_HYDRATE_BYTES) break;
|
|
15877
|
+
kept.push(escaped);
|
|
15878
|
+
totalBytes += bytes;
|
|
15879
|
+
}
|
|
15880
|
+
if (kept.length === 0) return basePrompt;
|
|
15881
|
+
const lines = kept.map((e) => `- [${e.source}] ${e.key}: ${e.value}`);
|
|
15882
|
+
return `${basePrompt.trimEnd()}
|
|
15883
|
+
|
|
15884
|
+
---
|
|
15885
|
+
|
|
15886
|
+
<memory>
|
|
15887
|
+
Project-scoped durable notes (newest first). Use remember/forget/recall to manage. Entries tagged [compaction] are LLM-summarized transcript slices.
|
|
15888
|
+
|
|
15889
|
+
${lines.join("\n")}
|
|
15890
|
+
</memory>`;
|
|
15891
|
+
}
|
|
15892
|
+
/**
|
|
15893
|
+
* Rebuild the live agent's system prompt from the latest `agent_memory`
|
|
15894
|
+
* rows. Called after out-of-band memory writes (CLI/API PUT/DELETE) so
|
|
15895
|
+
* the next turn on a hot session sees the updated notes without waiting
|
|
15896
|
+
* for compaction or a cold restart. No-op when no live agent exists —
|
|
15897
|
+
* the next `getOrCreate` will hydrate from DB anyway.
|
|
15898
|
+
*/
|
|
15899
|
+
rehydrateLiveMemory(projectName) {
|
|
15900
|
+
const agent = this.live.get(projectName);
|
|
15901
|
+
if (!agent) return;
|
|
15902
|
+
const projectId = this.tryResolveProjectId(projectName);
|
|
15903
|
+
if (!projectId) return;
|
|
15904
|
+
const row = this.loadRow(projectId);
|
|
15905
|
+
if (!row) return;
|
|
15906
|
+
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
15907
|
+
}
|
|
15472
15908
|
/**
|
|
15473
15909
|
* Acquire the Agent for an upcoming prompt/turn.
|
|
15474
15910
|
*
|
|
@@ -15484,7 +15920,7 @@ var SessionRegistry = class {
|
|
|
15484
15920
|
* Persists the new model choice to the DB row so subsequent invocations
|
|
15485
15921
|
* stay on it unless overridden again.
|
|
15486
15922
|
*/
|
|
15487
|
-
acquireForTurn(projectName, preferences) {
|
|
15923
|
+
async acquireForTurn(projectName, preferences) {
|
|
15488
15924
|
const agent = this.getOrCreate(projectName);
|
|
15489
15925
|
if (agent.state.isStreaming) {
|
|
15490
15926
|
throw agentBusy(projectName);
|
|
@@ -15493,11 +15929,70 @@ var SessionRegistry = class {
|
|
|
15493
15929
|
if (preferences?.provider || preferences?.modelId) {
|
|
15494
15930
|
this.alignModel(projectName, agent, preferences);
|
|
15495
15931
|
}
|
|
15932
|
+
await this.maybeCompact(projectName, agent);
|
|
15496
15933
|
return agent;
|
|
15497
15934
|
}
|
|
15935
|
+
/**
|
|
15936
|
+
* Summarize the oldest half of the transcript into a `compaction:`
|
|
15937
|
+
* memory row when the transcript crosses the token/message threshold.
|
|
15938
|
+
* Runs before the caller's next `agent.prompt()` so the model sees the
|
|
15939
|
+
* trimmed transcript + a refreshed `<memory>` block that now includes
|
|
15940
|
+
* the new summary.
|
|
15941
|
+
*
|
|
15942
|
+
* Races are deduped through `this.compactions`: a concurrent call for
|
|
15943
|
+
* the same project awaits the in-flight promise instead of launching a
|
|
15944
|
+
* duplicate summarizer run. Failures are logged and swallowed — a flaky
|
|
15945
|
+
* summarizer must never block a user turn.
|
|
15946
|
+
*/
|
|
15947
|
+
async maybeCompact(projectName, agent) {
|
|
15948
|
+
const inflight = this.compactions.get(projectName);
|
|
15949
|
+
if (inflight) {
|
|
15950
|
+
await inflight;
|
|
15951
|
+
return;
|
|
15952
|
+
}
|
|
15953
|
+
if (!shouldCompact(agent.state.messages)) return;
|
|
15954
|
+
const promise = this.runCompaction(projectName, agent).finally(() => {
|
|
15955
|
+
this.compactions.delete(projectName);
|
|
15956
|
+
});
|
|
15957
|
+
this.compactions.set(projectName, promise);
|
|
15958
|
+
await promise;
|
|
15959
|
+
}
|
|
15960
|
+
async runCompaction(projectName, agent) {
|
|
15961
|
+
const projectId = this.projectIds.get(projectName) ?? this.resolveProjectId(projectName);
|
|
15962
|
+
this.projectIds.set(projectName, projectId);
|
|
15963
|
+
const row = this.loadRow(projectId);
|
|
15964
|
+
if (!row) return;
|
|
15965
|
+
try {
|
|
15966
|
+
const result = await compactMessages({
|
|
15967
|
+
db: this.opts.db,
|
|
15968
|
+
projectId,
|
|
15969
|
+
sessionId: row.id,
|
|
15970
|
+
messages: agent.state.messages,
|
|
15971
|
+
model: agent.state.model,
|
|
15972
|
+
getApiKey: buildApiKeyResolver(this.opts.config)
|
|
15973
|
+
});
|
|
15974
|
+
if (!result) return;
|
|
15975
|
+
agent.state.messages = result.messages;
|
|
15976
|
+
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
15977
|
+
this.save(projectName);
|
|
15978
|
+
log7.info("compaction.completed", {
|
|
15979
|
+
projectName,
|
|
15980
|
+
removedCount: result.removedCount,
|
|
15981
|
+
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
15982
|
+
});
|
|
15983
|
+
} catch (err) {
|
|
15984
|
+
log7.error("compaction.failed", {
|
|
15985
|
+
projectName,
|
|
15986
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15987
|
+
});
|
|
15988
|
+
}
|
|
15989
|
+
}
|
|
15498
15990
|
alignScope(projectName, agent, wantScope) {
|
|
15499
15991
|
if (this.scopes.get(projectName) === wantScope) return;
|
|
15500
|
-
const
|
|
15992
|
+
const projectId = this.projectIds.get(projectName) ?? this.resolveProjectId(projectName);
|
|
15993
|
+
this.projectIds.set(projectName, projectId);
|
|
15994
|
+
const toolCtx = { client: this.opts.client, projectName, db: this.opts.db, projectId };
|
|
15995
|
+
const stateTools = wantScope === "read-only" ? buildReadTools(toolCtx) : buildAllTools(toolCtx);
|
|
15501
15996
|
agent.state.tools = [...stateTools, ...buildSkillDocTools()];
|
|
15502
15997
|
this.scopes.set(projectName, wantScope);
|
|
15503
15998
|
}
|
|
@@ -15516,7 +16011,7 @@ var SessionRegistry = class {
|
|
|
15516
16011
|
modelProvider: nextProvider,
|
|
15517
16012
|
modelId: nextModelId,
|
|
15518
16013
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15519
|
-
}).where(
|
|
16014
|
+
}).where(eq24(agentSessions.projectId, projectId)).run();
|
|
15520
16015
|
}
|
|
15521
16016
|
/** Persist a session's transcript back to the DB. Call after any run settles. */
|
|
15522
16017
|
save(projectName) {
|
|
@@ -15573,7 +16068,7 @@ var SessionRegistry = class {
|
|
|
15573
16068
|
let agent;
|
|
15574
16069
|
try {
|
|
15575
16070
|
const scope = this.scopes.get(projectName) ?? "read-only";
|
|
15576
|
-
agent = this.acquireForTurn(projectName, { toolScope: scope });
|
|
16071
|
+
agent = await this.acquireForTurn(projectName, { toolScope: scope });
|
|
15577
16072
|
} catch (err) {
|
|
15578
16073
|
if (err.code === "AGENT_BUSY") return;
|
|
15579
16074
|
throw err;
|
|
@@ -15608,6 +16103,7 @@ var SessionRegistry = class {
|
|
|
15608
16103
|
this.live.delete(projectName);
|
|
15609
16104
|
this.pending.delete(projectName);
|
|
15610
16105
|
this.scopes.delete(projectName);
|
|
16106
|
+
this.projectIds.delete(projectName);
|
|
15611
16107
|
}
|
|
15612
16108
|
/** Evict every live Agent. Durable state in DB is untouched. */
|
|
15613
16109
|
clear() {
|
|
@@ -15677,17 +16173,17 @@ var SessionRegistry = class {
|
|
|
15677
16173
|
return id;
|
|
15678
16174
|
}
|
|
15679
16175
|
tryResolveProjectId(projectName) {
|
|
15680
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
16176
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq24(projects.name, projectName)).get();
|
|
15681
16177
|
return row?.id;
|
|
15682
16178
|
}
|
|
15683
16179
|
loadRow(projectId) {
|
|
15684
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
16180
|
+
const row = this.opts.db.select().from(agentSessions).where(eq24(agentSessions.projectId, projectId)).get();
|
|
15685
16181
|
return row ?? null;
|
|
15686
16182
|
}
|
|
15687
16183
|
insertRow(params) {
|
|
15688
16184
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15689
16185
|
this.opts.db.insert(agentSessions).values({
|
|
15690
|
-
id:
|
|
16186
|
+
id: crypto23.randomUUID(),
|
|
15691
16187
|
projectId: params.projectId,
|
|
15692
16188
|
systemPrompt: params.systemPrompt,
|
|
15693
16189
|
modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
|
|
@@ -15700,14 +16196,14 @@ var SessionRegistry = class {
|
|
|
15700
16196
|
}
|
|
15701
16197
|
updateRow(projectId, patch) {
|
|
15702
16198
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15703
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
16199
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq24(agentSessions.projectId, projectId)).run();
|
|
15704
16200
|
}
|
|
15705
16201
|
};
|
|
15706
16202
|
|
|
15707
16203
|
// src/agent/agent-routes.ts
|
|
15708
|
-
import { eq as
|
|
16204
|
+
import { eq as eq25 } from "drizzle-orm";
|
|
15709
16205
|
function resolveProject2(db, name) {
|
|
15710
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
16206
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq25(projects.name, name)).get();
|
|
15711
16207
|
if (!row) throw notFound("project", name);
|
|
15712
16208
|
return row;
|
|
15713
16209
|
}
|
|
@@ -15716,7 +16212,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
15716
16212
|
"/projects/:name/agent/transcript",
|
|
15717
16213
|
async (request) => {
|
|
15718
16214
|
const project = resolveProject2(opts.db, request.params.name);
|
|
15719
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
16215
|
+
const row = opts.db.select().from(agentSessions).where(eq25(agentSessions.projectId, project.id)).get();
|
|
15720
16216
|
if (!row) {
|
|
15721
16217
|
return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
15722
16218
|
}
|
|
@@ -15740,7 +16236,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
15740
16236
|
async (request) => {
|
|
15741
16237
|
const project = resolveProject2(opts.db, request.params.name);
|
|
15742
16238
|
opts.sessionRegistry.reset(project.name);
|
|
15743
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
16239
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(agentSessions.projectId, project.id)).run();
|
|
15744
16240
|
return { status: "reset" };
|
|
15745
16241
|
}
|
|
15746
16242
|
);
|
|
@@ -15749,7 +16245,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
15749
16245
|
const promptText = (request.body?.prompt ?? "").trim();
|
|
15750
16246
|
if (!promptText) throw validationError('"prompt" is required');
|
|
15751
16247
|
const requestedScope = request.body?.scope === "all" ? "all" : "read-only";
|
|
15752
|
-
const agent = opts.sessionRegistry.acquireForTurn(project.name, {
|
|
16248
|
+
const agent = await opts.sessionRegistry.acquireForTurn(project.name, {
|
|
15753
16249
|
provider: request.body?.provider,
|
|
15754
16250
|
modelId: request.body?.modelId,
|
|
15755
16251
|
toolScope: requestedScope
|
|
@@ -15800,6 +16296,57 @@ function registerAgentRoutes(app, opts) {
|
|
|
15800
16296
|
}
|
|
15801
16297
|
return reply;
|
|
15802
16298
|
});
|
|
16299
|
+
app.get(
|
|
16300
|
+
"/projects/:name/agent/memory",
|
|
16301
|
+
async (request) => {
|
|
16302
|
+
const project = resolveProject2(opts.db, request.params.name);
|
|
16303
|
+
return { entries: listMemoryEntries(opts.db, project.id) };
|
|
16304
|
+
}
|
|
16305
|
+
);
|
|
16306
|
+
app.put(
|
|
16307
|
+
"/projects/:name/agent/memory",
|
|
16308
|
+
async (request) => {
|
|
16309
|
+
const project = resolveProject2(opts.db, request.params.name);
|
|
16310
|
+
const parsed = agentMemoryUpsertRequestSchema.safeParse(request.body);
|
|
16311
|
+
if (!parsed.success) {
|
|
16312
|
+
throw validationError(parsed.error.issues.map((i) => i.message).join("; "));
|
|
16313
|
+
}
|
|
16314
|
+
if (parsed.data.key.startsWith(COMPACTION_KEY_PREFIX)) {
|
|
16315
|
+
throw validationError(
|
|
16316
|
+
`key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`
|
|
16317
|
+
);
|
|
16318
|
+
}
|
|
16319
|
+
if (Buffer.byteLength(parsed.data.value, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
|
|
16320
|
+
throw validationError(`"value" exceeds ${AGENT_MEMORY_VALUE_MAX_BYTES} bytes`);
|
|
16321
|
+
}
|
|
16322
|
+
const entry = upsertMemoryEntry(opts.db, {
|
|
16323
|
+
projectId: project.id,
|
|
16324
|
+
key: parsed.data.key,
|
|
16325
|
+
value: parsed.data.value,
|
|
16326
|
+
source: MemorySources.user
|
|
16327
|
+
});
|
|
16328
|
+
opts.sessionRegistry.rehydrateLiveMemory(project.name);
|
|
16329
|
+
return { status: "ok", entry };
|
|
16330
|
+
}
|
|
16331
|
+
);
|
|
16332
|
+
app.delete(
|
|
16333
|
+
"/projects/:name/agent/memory",
|
|
16334
|
+
async (request) => {
|
|
16335
|
+
const project = resolveProject2(opts.db, request.params.name);
|
|
16336
|
+
const parsed = agentMemoryDeleteRequestSchema.safeParse(request.body);
|
|
16337
|
+
if (!parsed.success) {
|
|
16338
|
+
throw validationError(parsed.error.issues.map((i) => i.message).join("; "));
|
|
16339
|
+
}
|
|
16340
|
+
if (parsed.data.key.startsWith(COMPACTION_KEY_PREFIX)) {
|
|
16341
|
+
throw validationError(
|
|
16342
|
+
`key prefix "${COMPACTION_KEY_PREFIX}" is reserved; compaction notes are pruned automatically`
|
|
16343
|
+
);
|
|
16344
|
+
}
|
|
16345
|
+
const removed = deleteMemoryEntry(opts.db, project.id, parsed.data.key);
|
|
16346
|
+
if (removed) opts.sessionRegistry.rehydrateLiveMemory(project.name);
|
|
16347
|
+
return { status: removed ? "forgotten" : "missing", key: parsed.data.key };
|
|
16348
|
+
}
|
|
16349
|
+
);
|
|
15803
16350
|
}
|
|
15804
16351
|
|
|
15805
16352
|
// src/client.ts
|
|
@@ -15890,7 +16437,7 @@ var ApiClient = class {
|
|
|
15890
16437
|
const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
|
|
15891
16438
|
const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
|
|
15892
16439
|
const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
|
|
15893
|
-
throw new CliError({ code, message: msg, exitCode });
|
|
16440
|
+
throw new CliError({ code, message: msg, exitCode, details: { httpStatus: res.status } });
|
|
15894
16441
|
}
|
|
15895
16442
|
if (res.status === 204) {
|
|
15896
16443
|
return void 0;
|
|
@@ -15915,6 +16462,26 @@ var ApiClient = class {
|
|
|
15915
16462
|
`/projects/${encodeURIComponent(project)}/agent/providers`
|
|
15916
16463
|
);
|
|
15917
16464
|
}
|
|
16465
|
+
async listAgentMemory(project) {
|
|
16466
|
+
return this.request(
|
|
16467
|
+
"GET",
|
|
16468
|
+
`/projects/${encodeURIComponent(project)}/agent/memory`
|
|
16469
|
+
);
|
|
16470
|
+
}
|
|
16471
|
+
async setAgentMemory(project, body) {
|
|
16472
|
+
return this.request(
|
|
16473
|
+
"PUT",
|
|
16474
|
+
`/projects/${encodeURIComponent(project)}/agent/memory`,
|
|
16475
|
+
body
|
|
16476
|
+
);
|
|
16477
|
+
}
|
|
16478
|
+
async forgetAgentMemory(project, key) {
|
|
16479
|
+
return this.request(
|
|
16480
|
+
"DELETE",
|
|
16481
|
+
`/projects/${encodeURIComponent(project)}/agent/memory`,
|
|
16482
|
+
{ key }
|
|
16483
|
+
);
|
|
16484
|
+
}
|
|
15918
16485
|
/**
|
|
15919
16486
|
* POST a request whose response body the caller intends to consume as a
|
|
15920
16487
|
* stream (e.g. the Aero agent SSE endpoint). Shares the probe + auth +
|
|
@@ -15960,7 +16527,7 @@ var ApiClient = class {
|
|
|
15960
16527
|
const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
|
|
15961
16528
|
const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
|
|
15962
16529
|
const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
|
|
15963
|
-
throw new CliError({ code, message: msg, exitCode });
|
|
16530
|
+
throw new CliError({ code, message: msg, exitCode, details: { httpStatus: res.status } });
|
|
15964
16531
|
}
|
|
15965
16532
|
return res;
|
|
15966
16533
|
}
|
|
@@ -16001,6 +16568,9 @@ var ApiClient = class {
|
|
|
16001
16568
|
const query = limit != null ? `?limit=${encodeURIComponent(String(limit))}` : "";
|
|
16002
16569
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/runs${query}`);
|
|
16003
16570
|
}
|
|
16571
|
+
async getLatestRun(project) {
|
|
16572
|
+
return this.request("GET", `/projects/${encodeURIComponent(project)}/runs/latest`);
|
|
16573
|
+
}
|
|
16004
16574
|
async getRun(id) {
|
|
16005
16575
|
return this.request("GET", `/runs/${encodeURIComponent(id)}`);
|
|
16006
16576
|
}
|
|
@@ -17074,7 +17644,7 @@ function summarizeProviderConfig(provider, config) {
|
|
|
17074
17644
|
};
|
|
17075
17645
|
}
|
|
17076
17646
|
function hashApiKey(key) {
|
|
17077
|
-
return
|
|
17647
|
+
return crypto24.createHash("sha256").update(key).digest("hex");
|
|
17078
17648
|
}
|
|
17079
17649
|
function parseCookies2(header) {
|
|
17080
17650
|
if (!header) return {};
|
|
@@ -17232,7 +17802,7 @@ async function createServer(opts) {
|
|
|
17232
17802
|
intelligenceService,
|
|
17233
17803
|
(runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
|
|
17234
17804
|
async ({ runId, projectId, insightCount, criticalOrHigh }) => {
|
|
17235
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
17805
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq26(projects.id, projectId)).get();
|
|
17236
17806
|
if (!project) return;
|
|
17237
17807
|
sessionRegistry.queueFollowUp(project.name, {
|
|
17238
17808
|
role: "user",
|
|
@@ -17326,7 +17896,7 @@ async function createServer(opts) {
|
|
|
17326
17896
|
return removed;
|
|
17327
17897
|
}
|
|
17328
17898
|
};
|
|
17329
|
-
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ??
|
|
17899
|
+
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto24.randomBytes(32).toString("hex");
|
|
17330
17900
|
const googleConnectionStore = {
|
|
17331
17901
|
listConnections: (domain) => listGoogleConnections(opts.config, domain),
|
|
17332
17902
|
getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
|
|
@@ -17372,11 +17942,11 @@ async function createServer(opts) {
|
|
|
17372
17942
|
const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
|
|
17373
17943
|
if (opts.config.apiKey) {
|
|
17374
17944
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
17375
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
17945
|
+
const existing = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, keyHash)).get();
|
|
17376
17946
|
if (!existing) {
|
|
17377
17947
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
17378
17948
|
opts.db.insert(apiKeys).values({
|
|
17379
|
-
id: `key_${
|
|
17949
|
+
id: `key_${crypto24.randomBytes(8).toString("hex")}`,
|
|
17380
17950
|
name: "default",
|
|
17381
17951
|
keyHash,
|
|
17382
17952
|
keyPrefix: prefix,
|
|
@@ -17400,7 +17970,7 @@ async function createServer(opts) {
|
|
|
17400
17970
|
};
|
|
17401
17971
|
const createSession = (apiKeyId) => {
|
|
17402
17972
|
pruneExpiredSessions();
|
|
17403
|
-
const sessionId =
|
|
17973
|
+
const sessionId = crypto24.randomBytes(32).toString("hex");
|
|
17404
17974
|
sessions.set(sessionId, {
|
|
17405
17975
|
apiKeyId,
|
|
17406
17976
|
expiresAt: Date.now() + SESSION_TTL_MS
|
|
@@ -17424,7 +17994,7 @@ async function createServer(opts) {
|
|
|
17424
17994
|
};
|
|
17425
17995
|
const getDefaultApiKey = () => {
|
|
17426
17996
|
if (!opts.config.apiKey) return void 0;
|
|
17427
|
-
return opts.db.select().from(apiKeys).where(
|
|
17997
|
+
return opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
17428
17998
|
};
|
|
17429
17999
|
const createPasswordSession = (reply) => {
|
|
17430
18000
|
const key = getDefaultApiKey();
|
|
@@ -17481,12 +18051,12 @@ async function createServer(opts) {
|
|
|
17481
18051
|
return reply.send({ authenticated: true });
|
|
17482
18052
|
}
|
|
17483
18053
|
if (apiKey) {
|
|
17484
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
18054
|
+
const key = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
17485
18055
|
if (!key || key.revokedAt) {
|
|
17486
18056
|
const err2 = authInvalid();
|
|
17487
18057
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
17488
18058
|
}
|
|
17489
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
18059
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(apiKeys.id, key.id)).run();
|
|
17490
18060
|
const sessionId = createSession(key.id);
|
|
17491
18061
|
reply.header("set-cookie", serializeSessionCookie({
|
|
17492
18062
|
name: SESSION_COOKIE_NAME,
|
|
@@ -17633,7 +18203,7 @@ async function createServer(opts) {
|
|
|
17633
18203
|
const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
|
|
17634
18204
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17635
18205
|
opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
|
|
17636
|
-
id:
|
|
18206
|
+
id: crypto24.randomUUID(),
|
|
17637
18207
|
projectId,
|
|
17638
18208
|
actor: "api",
|
|
17639
18209
|
action: existing ? "provider.updated" : "provider.created",
|
|
@@ -17890,6 +18460,7 @@ export {
|
|
|
17890
18460
|
EXIT_SYSTEM_ERROR,
|
|
17891
18461
|
CliError,
|
|
17892
18462
|
usageError,
|
|
18463
|
+
isEndpointMissing,
|
|
17893
18464
|
printCliError,
|
|
17894
18465
|
providerQuotaPolicySchema,
|
|
17895
18466
|
ProviderNames,
|