mcp-scraper 0.49.0 → 0.51.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/README.md +11 -3
- package/dist/{analytics-repository-WWXVU3VW.js → analytics-repository-IKE53S3E.js} +10 -2
- package/dist/bin/api-server.cjs +840 -177
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +4 -4
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +141 -35
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +4 -4
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-MFNGUM4L.js → chunk-2XTYLJQK.js} +15 -1
- package/dist/chunk-2XTYLJQK.js.map +1 -0
- package/dist/{chunk-YW2LXLDU.js → chunk-5BEPOFKG.js} +160 -15
- package/dist/chunk-5BEPOFKG.js.map +1 -0
- package/dist/chunk-73MUQSWC.js +7 -0
- package/dist/chunk-73MUQSWC.js.map +1 -0
- package/dist/{chunk-MFE6RMIA.js → chunk-GOZIG6HD.js} +2 -2
- package/dist/{chunk-R66PJOZW.js → chunk-NNZXZTJ2.js} +2 -2
- package/dist/{chunk-64WBUDPC.js → chunk-O63VVCCE.js} +143 -37
- package/dist/chunk-O63VVCCE.js.map +1 -0
- package/dist/{chunk-6DPIE262.js → chunk-QHVFKFME.js} +2 -2
- package/dist/{chunk-PODASLGT.js → chunk-QXAY44SA.js} +2 -2
- package/dist/{chunk-ZEMHL36J.js → chunk-YMSZO62N.js} +2 -2
- package/dist/{db-A4YUPV4Z.js → db-UPSS5BGM.js} +2 -2
- package/dist/editorial-reading-room/assets/app.js +62 -0
- package/dist/editorial-reading-room/assets/index.html +8 -0
- package/dist/editorial-reading-room/assets/styles.css +39 -0
- package/dist/{extract-bundle-2HONVC5T.js → extract-bundle-RCTNANCH.js} +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{location-data-repository-YWFJGNIP.js → location-data-repository-UUSHH5M2.js} +3 -3
- package/dist/{server-JE7SLZLJ.js → server-IGIIEHXY.js} +518 -135
- package/dist/server-IGIIEHXY.js.map +1 -0
- package/dist/{site-extract-repository-STNOI5OH.js → site-extract-repository-3VQAALNR.js} +3 -3
- package/dist/{worker-4MHZS3OB.js → worker-KQN673JF.js} +5 -5
- package/package.json +1 -1
- package/dist/chunk-64WBUDPC.js.map +0 -1
- package/dist/chunk-MFNGUM4L.js.map +0 -1
- package/dist/chunk-XLEDHV3J.js +0 -7
- package/dist/chunk-XLEDHV3J.js.map +0 -1
- package/dist/chunk-YW2LXLDU.js.map +0 -1
- package/dist/server-JE7SLZLJ.js.map +0 -1
- /package/dist/{analytics-repository-WWXVU3VW.js.map → analytics-repository-IKE53S3E.js.map} +0 -0
- /package/dist/{chunk-MFE6RMIA.js.map → chunk-GOZIG6HD.js.map} +0 -0
- /package/dist/{chunk-R66PJOZW.js.map → chunk-NNZXZTJ2.js.map} +0 -0
- /package/dist/{chunk-6DPIE262.js.map → chunk-QHVFKFME.js.map} +0 -0
- /package/dist/{chunk-PODASLGT.js.map → chunk-QXAY44SA.js.map} +0 -0
- /package/dist/{chunk-ZEMHL36J.js.map → chunk-YMSZO62N.js.map} +0 -0
- /package/dist/{db-A4YUPV4Z.js.map → db-UPSS5BGM.js.map} +0 -0
- /package/dist/{extract-bundle-2HONVC5T.js.map → extract-bundle-RCTNANCH.js.map} +0 -0
- /package/dist/{location-data-repository-YWFJGNIP.js.map → location-data-repository-UUSHH5M2.js.map} +0 -0
- /package/dist/{site-extract-repository-STNOI5OH.js.map → site-extract-repository-3VQAALNR.js.map} +0 -0
- /package/dist/{worker-4MHZS3OB.js.map → worker-KQN673JF.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -41,11 +41,12 @@ function harvestTimeoutBudget(maxQuestions, serpOnly = false) {
|
|
|
41
41
|
const clientMs = Math.min(serverMs + CLIENT_OVER_SERVER_MARGIN_MS, VERCEL_FUNCTION_MAX_MS - 5e3);
|
|
42
42
|
return { serverMs, clientMs };
|
|
43
43
|
}
|
|
44
|
-
var VERCEL_FUNCTION_MAX_MS, CLIENT_OVER_SERVER_MARGIN_MS;
|
|
44
|
+
var VERCEL_FUNCTION_MAX_MS, DEFAULT_TOOL_CLIENT_TIMEOUT_MS, CLIENT_OVER_SERVER_MARGIN_MS;
|
|
45
45
|
var init_harvest_timeout = __esm({
|
|
46
46
|
"src/harvest-timeout.ts"() {
|
|
47
47
|
"use strict";
|
|
48
48
|
VERCEL_FUNCTION_MAX_MS = 3e5;
|
|
49
|
+
DEFAULT_TOOL_CLIENT_TIMEOUT_MS = 3e5;
|
|
49
50
|
CLIENT_OVER_SERVER_MARGIN_MS = 15e3;
|
|
50
51
|
}
|
|
51
52
|
});
|
|
@@ -4067,6 +4068,7 @@ async function migrate(options = {}) {
|
|
|
4067
4068
|
`);
|
|
4068
4069
|
await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_user_status ON concurrency_locks(user_id, status, expires_at)`);
|
|
4069
4070
|
await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_operation ON concurrency_locks(operation, status, expires_at)`);
|
|
4071
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_active_user ON concurrency_locks(user_id, expires_at) WHERE status = 'active'`);
|
|
4070
4072
|
await db.execute(`
|
|
4071
4073
|
CREATE TABLE IF NOT EXISTS ledger (
|
|
4072
4074
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -4253,6 +4255,16 @@ async function migrate(options = {}) {
|
|
|
4253
4255
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
4254
4256
|
)
|
|
4255
4257
|
`);
|
|
4258
|
+
await db.execute(`
|
|
4259
|
+
CREATE TABLE IF NOT EXISTS connected_account_charges (
|
|
4260
|
+
user_id INTEGER NOT NULL,
|
|
4261
|
+
connection_id TEXT NOT NULL,
|
|
4262
|
+
period_start TEXT NOT NULL,
|
|
4263
|
+
amount_mc INTEGER NOT NULL,
|
|
4264
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
4265
|
+
PRIMARY KEY (user_id, connection_id, period_start)
|
|
4266
|
+
)
|
|
4267
|
+
`);
|
|
4256
4268
|
await db.execute(`
|
|
4257
4269
|
CREATE TABLE IF NOT EXISTS billing_credits (
|
|
4258
4270
|
idempotency_key TEXT PRIMARY KEY,
|
|
@@ -4441,6 +4453,7 @@ async function migrate(options = {}) {
|
|
|
4441
4453
|
)
|
|
4442
4454
|
`);
|
|
4443
4455
|
await db.execute(`CREATE INDEX IF NOT EXISTS request_events_user_created_at ON request_events(user_id, created_at DESC)`);
|
|
4456
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS request_events_created_at ON request_events(created_at)`);
|
|
4444
4457
|
await db.execute(`
|
|
4445
4458
|
CREATE TABLE IF NOT EXISTS workflow_schedules (
|
|
4446
4459
|
id TEXT PRIMARY KEY,
|
|
@@ -6746,6 +6759,7 @@ var init_db = __esm({
|
|
|
6746
6759
|
"billing_debits_user_status",
|
|
6747
6760
|
"billing_events_authorization",
|
|
6748
6761
|
"billing_events_user_created_at",
|
|
6762
|
+
"concurrency_locks_active_user",
|
|
6749
6763
|
"concurrency_locks_operation",
|
|
6750
6764
|
"concurrency_locks_user_status",
|
|
6751
6765
|
"credit_lots_user_active",
|
|
@@ -6765,6 +6779,7 @@ var init_db = __esm({
|
|
|
6765
6779
|
"oauth_tokens_identity",
|
|
6766
6780
|
"page_snapshots_user_checked_at",
|
|
6767
6781
|
"prt_user_id",
|
|
6782
|
+
"request_events_created_at",
|
|
6768
6783
|
"request_events_user_created_at",
|
|
6769
6784
|
"scheduled_run_view_links_expiry",
|
|
6770
6785
|
"scheduled_run_view_links_owner_run",
|
|
@@ -17503,7 +17518,8 @@ ${t.topQuestions.map((q) => `- ${q}`).join("\n")}` : ""
|
|
|
17503
17518
|
${q.threadUrl}` : ""}`).join("\n");
|
|
17504
17519
|
const full = [
|
|
17505
17520
|
`# Reddit Trending: "${d.topic || input.topic}"${subreddit ? ` in r/${subreddit}` : ""}`,
|
|
17506
|
-
`**${totals.threads} threads \xB7 ${totals.upvotes} upvotes \xB7 ${totals.comments} comments** \xB7 last ${d.window === "7d" ? "week" : "month"} \xB7 ${d.threadsScraped ?? 0} of ${d.candidatesFound ?? threads.length} discovered scraped${d.partial ? " (partial
|
|
17521
|
+
`**${totals.threads} threads \xB7 ${totals.upvotes} upvotes \xB7 ${totals.comments} comments** \xB7 last ${d.window === "7d" ? "week" : "month"} \xB7 ${d.threadsScraped ?? 0} of ${d.candidatesFound ?? threads.length} discovered scraped${d.partial ? " (partial)" : ""}`,
|
|
17522
|
+
d.degradedResult ? `**Discovery degraded:** ${(d.degradationReasons ?? []).join(", ") || "no usable discovery surface"}${d.retryRecommended ? " \xB7 retry recommended" : ""}${d.billingRefunded ? " \xB7 discovery charge refunded" : ""}` : d.discoverySource === "reddit_search_fallback" ? "**Discovery fallback:** direct Reddit search was used after the primary SERP returned no usable threads." : "",
|
|
17507
17523
|
`
|
|
17508
17524
|
## Ranked threads
|
|
17509
17525
|
${threadBlocks || "_No threads found._"}`,
|
|
@@ -17535,7 +17551,13 @@ ${questionList || "_No questions extracted._"}`,
|
|
|
17535
17551
|
threadsScraped: Number(d.threadsScraped ?? 0),
|
|
17536
17552
|
candidatesFound: Number(d.candidatesFound ?? threads.length),
|
|
17537
17553
|
partial: Boolean(d.partial),
|
|
17538
|
-
searchQuery: d.searchQuery ?? ""
|
|
17554
|
+
searchQuery: d.searchQuery ?? "",
|
|
17555
|
+
discoverySource: d.discoverySource ?? "google_serp",
|
|
17556
|
+
resultQuality: d.resultQuality ?? "complete",
|
|
17557
|
+
degradedResult: Boolean(d.degradedResult),
|
|
17558
|
+
degradationReasons: d.degradationReasons ?? [],
|
|
17559
|
+
retryRecommended: Boolean(d.retryRecommended),
|
|
17560
|
+
billingRefunded: Boolean(d.billingRefunded)
|
|
17539
17561
|
}
|
|
17540
17562
|
};
|
|
17541
17563
|
}
|
|
@@ -23288,40 +23310,15 @@ function safeMetadata(metadata) {
|
|
|
23288
23310
|
return null;
|
|
23289
23311
|
}
|
|
23290
23312
|
}
|
|
23291
|
-
async function ensureConcurrencyGateSchema() {
|
|
23292
|
-
if (!schemaReady) {
|
|
23293
|
-
schemaReady = (async () => {
|
|
23294
|
-
const db = getDb();
|
|
23295
|
-
await db.execute(`
|
|
23296
|
-
CREATE TABLE IF NOT EXISTS concurrency_locks (
|
|
23297
|
-
id TEXT PRIMARY KEY,
|
|
23298
|
-
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
23299
|
-
operation TEXT NOT NULL,
|
|
23300
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
23301
|
-
acquired_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
23302
|
-
expires_at TEXT NOT NULL,
|
|
23303
|
-
released_at TEXT,
|
|
23304
|
-
metadata TEXT
|
|
23305
|
-
)
|
|
23306
|
-
`);
|
|
23307
|
-
await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_user_status ON concurrency_locks(user_id, status, expires_at)`);
|
|
23308
|
-
await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_operation ON concurrency_locks(operation, status, expires_at)`);
|
|
23309
|
-
})();
|
|
23310
|
-
}
|
|
23311
|
-
return schemaReady;
|
|
23312
|
-
}
|
|
23313
23313
|
async function expireConcurrencyLocksForUser(userId) {
|
|
23314
|
-
await ensureConcurrencyGateSchema();
|
|
23315
23314
|
await getDb().execute({
|
|
23316
|
-
sql: `
|
|
23317
|
-
SET status = 'expired', released_at = datetime('now')
|
|
23315
|
+
sql: `DELETE FROM concurrency_locks
|
|
23318
23316
|
WHERE user_id = ? AND status = 'active' AND expires_at <= datetime('now')`,
|
|
23319
23317
|
args: [userId]
|
|
23320
23318
|
});
|
|
23321
23319
|
}
|
|
23322
|
-
async function countActiveUsageForUser(userId) {
|
|
23323
|
-
await
|
|
23324
|
-
await expireConcurrencyLocksForUser(userId);
|
|
23320
|
+
async function countActiveUsageForUser(userId, alreadyExpired = false) {
|
|
23321
|
+
if (!alreadyExpired) await expireConcurrencyLocksForUser(userId);
|
|
23325
23322
|
const res = await getDb().execute({
|
|
23326
23323
|
sql: `SELECT
|
|
23327
23324
|
(
|
|
@@ -23341,7 +23338,6 @@ async function countActiveUsageForUser(userId) {
|
|
|
23341
23338
|
}
|
|
23342
23339
|
async function reuseExistingConcurrencyGate(userId, lockId, ttlSeconds) {
|
|
23343
23340
|
if (!/^cl_[a-f0-9]{24,32}$/.test(lockId)) return false;
|
|
23344
|
-
await ensureConcurrencyGateSchema();
|
|
23345
23341
|
await expireConcurrencyLocksForUser(userId);
|
|
23346
23342
|
const res = await getDb().execute({
|
|
23347
23343
|
sql: `UPDATE concurrency_locks
|
|
@@ -23355,13 +23351,12 @@ async function reuseExistingConcurrencyGate(userId, lockId, ttlSeconds) {
|
|
|
23355
23351
|
return Number(res.rowsAffected ?? 0) > 0;
|
|
23356
23352
|
}
|
|
23357
23353
|
async function acquireConcurrencyGate(user, operation, options = {}) {
|
|
23358
|
-
await ensureConcurrencyGateSchema();
|
|
23359
23354
|
const limit = concurrencyLimitForUser(user);
|
|
23360
23355
|
if (isConcurrencyLimitBypassed(user)) {
|
|
23361
23356
|
return { ok: true, lockId: null, active: 0, limit, operation, bypassed: true };
|
|
23362
23357
|
}
|
|
23363
23358
|
if (options.reuseLockId && await reuseExistingConcurrencyGate(user.id, options.reuseLockId, options.ttlSeconds)) {
|
|
23364
|
-
return { ok: true, lockId: null, active: await countActiveUsageForUser(user.id), limit, operation, reused: true };
|
|
23359
|
+
return { ok: true, lockId: null, active: await countActiveUsageForUser(user.id, true), limit, operation, reused: true };
|
|
23365
23360
|
}
|
|
23366
23361
|
await expireConcurrencyLocksForUser(user.id);
|
|
23367
23362
|
const lockId = `cl_${(0, import_node_crypto10.randomUUID)().replace(/-/g, "").slice(0, 24)}`;
|
|
@@ -23392,7 +23387,7 @@ async function acquireConcurrencyGate(user, operation, options = {}) {
|
|
|
23392
23387
|
limit
|
|
23393
23388
|
]
|
|
23394
23389
|
});
|
|
23395
|
-
const active = await countActiveUsageForUser(user.id);
|
|
23390
|
+
const active = await countActiveUsageForUser(user.id, true);
|
|
23396
23391
|
if (Number(res.rowsAffected ?? 0) > 0) {
|
|
23397
23392
|
return { ok: true, lockId, active, limit, operation };
|
|
23398
23393
|
}
|
|
@@ -23400,17 +23395,13 @@ async function acquireConcurrencyGate(user, operation, options = {}) {
|
|
|
23400
23395
|
}
|
|
23401
23396
|
async function releaseConcurrencyGate(lockId) {
|
|
23402
23397
|
if (!lockId) return;
|
|
23403
|
-
await ensureConcurrencyGateSchema();
|
|
23404
23398
|
await getDb().execute({
|
|
23405
|
-
sql: `
|
|
23406
|
-
SET status = 'released', released_at = datetime('now')
|
|
23407
|
-
WHERE id = ? AND status = 'active'`,
|
|
23399
|
+
sql: `DELETE FROM concurrency_locks WHERE id = ?`,
|
|
23408
23400
|
args: [lockId]
|
|
23409
23401
|
});
|
|
23410
23402
|
}
|
|
23411
23403
|
async function extendConcurrencyGate(lockId, ttlSeconds = DEFAULT_LOCK_TTL_SECONDS) {
|
|
23412
23404
|
if (!lockId) return;
|
|
23413
|
-
await ensureConcurrencyGateSchema();
|
|
23414
23405
|
await getDb().execute({
|
|
23415
23406
|
sql: `UPDATE concurrency_locks
|
|
23416
23407
|
SET expires_at = datetime('now', ?)
|
|
@@ -23418,7 +23409,7 @@ async function extendConcurrencyGate(lockId, ttlSeconds = DEFAULT_LOCK_TTL_SECON
|
|
|
23418
23409
|
args: [lockTtlModifier(ttlSeconds), lockId]
|
|
23419
23410
|
});
|
|
23420
23411
|
}
|
|
23421
|
-
var import_node_crypto10, DEFAULT_LOCK_TTL_SECONDS, DEFAULT_RETRY_AFTER_SECONDS, MAX_LOCK_TTL_SECONDS
|
|
23412
|
+
var import_node_crypto10, DEFAULT_LOCK_TTL_SECONDS, DEFAULT_RETRY_AFTER_SECONDS, MAX_LOCK_TTL_SECONDS;
|
|
23422
23413
|
var init_concurrency_gates = __esm({
|
|
23423
23414
|
"src/api/concurrency-gates.ts"() {
|
|
23424
23415
|
"use strict";
|
|
@@ -23428,7 +23419,6 @@ var init_concurrency_gates = __esm({
|
|
|
23428
23419
|
DEFAULT_LOCK_TTL_SECONDS = 15 * 60;
|
|
23429
23420
|
DEFAULT_RETRY_AFTER_SECONDS = 30;
|
|
23430
23421
|
MAX_LOCK_TTL_SECONDS = 24 * 60 * 60;
|
|
23431
|
-
schemaReady = null;
|
|
23432
23422
|
}
|
|
23433
23423
|
});
|
|
23434
23424
|
|
|
@@ -27530,7 +27520,7 @@ async function recoverSettlement(row, user, authorization, duplicate) {
|
|
|
27530
27520
|
}
|
|
27531
27521
|
}
|
|
27532
27522
|
await getDb().execute({
|
|
27533
|
-
sql: "UPDATE billing_events SET status = 'settled', settled_at = datetime('now') WHERE id = ? AND status
|
|
27523
|
+
sql: "UPDATE billing_events SET status = 'settled', settled_at = datetime('now') WHERE id = ? AND status IN ('settling', 'cost_pending')",
|
|
27534
27524
|
args: [row.id]
|
|
27535
27525
|
});
|
|
27536
27526
|
await getDb().execute({
|
|
@@ -27575,11 +27565,13 @@ async function settleScheduledRun(args) {
|
|
|
27575
27565
|
const rawCostNanos = rawCostUsd == null ? null : Math.round(rawCostUsd * USD_NANOS);
|
|
27576
27566
|
const modelMc = rawCostUsd == null ? 0 : vendorCostUsdToMc(rawCostUsd, SCHEDULED_AGENT_LLM_MARKUP_BPS);
|
|
27577
27567
|
const maximumModelMc = Math.max(0, Number(authorization.maximum_amount_mc) - SCHEDULED_RUN_BASE_MC);
|
|
27578
|
-
const
|
|
27568
|
+
const modelCostUnreported = args.modelCostStatus === "missing";
|
|
27569
|
+
const pendingReason = modelMc > maximumModelMc ? "max_credits_per_run_exceeded" : null;
|
|
27579
27570
|
const metadata = safeMetadata2({
|
|
27580
27571
|
...args.metadata,
|
|
27581
27572
|
billingPhase: "settlement",
|
|
27582
27573
|
chargeMode: "model_delta",
|
|
27574
|
+
...modelCostUnreported ? { modelCostUnreported: true } : {},
|
|
27583
27575
|
...pendingReason ? { reason: pendingReason } : {}
|
|
27584
27576
|
});
|
|
27585
27577
|
const eventId = existing?.id ?? (0, import_node_crypto14.randomUUID)();
|
|
@@ -27623,6 +27615,72 @@ async function settleScheduledRun(args) {
|
|
|
27623
27615
|
}
|
|
27624
27616
|
return recoverSettlement(existing, user, authorization, duplicateClaim);
|
|
27625
27617
|
}
|
|
27618
|
+
async function reconcileScheduledRunSettlements(limit = 25, minimumAgeHours = 24) {
|
|
27619
|
+
const stale = await getDb().execute({
|
|
27620
|
+
sql: `SELECT id, authorization_id, status, amount_mc, raw_cost_usd_nanos, multiplier_bps, metadata
|
|
27621
|
+
FROM billing_events
|
|
27622
|
+
WHERE billing_class = ? AND status = 'cost_pending'
|
|
27623
|
+
AND created_at < datetime('now', ?)
|
|
27624
|
+
ORDER BY created_at
|
|
27625
|
+
LIMIT ?`,
|
|
27626
|
+
args: [SCHEDULED_RUN_BILLING_CLASS, `-${Math.max(1, Math.trunc(minimumAgeHours))} hours`, Math.max(1, Math.trunc(limit))]
|
|
27627
|
+
});
|
|
27628
|
+
const result = { checked: stale.rows.length, settled: 0, chargedMc: 0, failed: 0 };
|
|
27629
|
+
for (const raw of stale.rows) {
|
|
27630
|
+
const row = raw;
|
|
27631
|
+
try {
|
|
27632
|
+
const authorization = await authorizationById(row.authorization_id);
|
|
27633
|
+
if (!authorization) {
|
|
27634
|
+
result.failed += 1;
|
|
27635
|
+
continue;
|
|
27636
|
+
}
|
|
27637
|
+
const cap = Math.max(0, Number(authorization.maximum_amount_mc) - SCHEDULED_RUN_BASE_MC);
|
|
27638
|
+
const chargeMc = Math.min(Math.max(0, Math.round(Number(row.amount_mc))), cap);
|
|
27639
|
+
if (chargeMc > 0) {
|
|
27640
|
+
const debit = await debitMcIdempotent(
|
|
27641
|
+
Number(authorization.user_id),
|
|
27642
|
+
chargeMc,
|
|
27643
|
+
LedgerOperation.SCHEDULED_RUN,
|
|
27644
|
+
"scheduled-run:" + authorization.id + ":model:reconciled",
|
|
27645
|
+
authorization.id + ":model"
|
|
27646
|
+
);
|
|
27647
|
+
if (!debit.ok) {
|
|
27648
|
+
result.failed += 1;
|
|
27649
|
+
continue;
|
|
27650
|
+
}
|
|
27651
|
+
}
|
|
27652
|
+
await getDb().execute({
|
|
27653
|
+
sql: `UPDATE billing_events
|
|
27654
|
+
SET status = 'settled', amount_mc = ?, settled_at = datetime('now'), metadata = ?
|
|
27655
|
+
WHERE id = ? AND status = 'cost_pending'`,
|
|
27656
|
+
args: [
|
|
27657
|
+
chargeMc,
|
|
27658
|
+
safeMetadata2({ ...metadataReason(row) ? { reason: metadataReason(row) } : {}, autoReconciled: true, cappedAtAuthorizedMaximum: chargeMc < Number(row.amount_mc) }),
|
|
27659
|
+
row.id
|
|
27660
|
+
]
|
|
27661
|
+
});
|
|
27662
|
+
await getDb().execute({
|
|
27663
|
+
sql: "UPDATE billing_authorizations SET status = 'settled', settled_at = datetime('now') WHERE id = ? AND status IN ('started', 'cost_pending')",
|
|
27664
|
+
args: [authorization.id]
|
|
27665
|
+
});
|
|
27666
|
+
result.settled += 1;
|
|
27667
|
+
result.chargedMc += chargeMc;
|
|
27668
|
+
console.error(JSON.stringify({
|
|
27669
|
+
event: "scheduled_run_settlement_reconciled",
|
|
27670
|
+
authorization_id: authorization.id,
|
|
27671
|
+
charged_mc: chargeMc,
|
|
27672
|
+
reason: metadataReason(row) ?? "unreported_cost"
|
|
27673
|
+
}));
|
|
27674
|
+
} catch (err) {
|
|
27675
|
+
result.failed += 1;
|
|
27676
|
+
console.error(JSON.stringify({
|
|
27677
|
+
event: "scheduled_run_reconciliation_failed",
|
|
27678
|
+
message: err instanceof Error ? err.message : String(err)
|
|
27679
|
+
}));
|
|
27680
|
+
}
|
|
27681
|
+
}
|
|
27682
|
+
return result;
|
|
27683
|
+
}
|
|
27626
27684
|
async function voidScheduledRunAuthorization(args) {
|
|
27627
27685
|
const user = await resolveSchedulingUser(args.identity);
|
|
27628
27686
|
const authorization = await authorizationById(args.authorizationId);
|
|
@@ -28609,28 +28667,6 @@ var init_inbox_routes = __esm({
|
|
|
28609
28667
|
});
|
|
28610
28668
|
|
|
28611
28669
|
// src/api/connected-account-billing.ts
|
|
28612
|
-
async function ensureConnectedAccountBillingSchema() {
|
|
28613
|
-
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
28614
|
-
user_id INTEGER PRIMARY KEY,
|
|
28615
|
-
stripe_subscription_id TEXT,
|
|
28616
|
-
stripe_subscription_item_id TEXT,
|
|
28617
|
-
price_id TEXT,
|
|
28618
|
-
quantity INTEGER NOT NULL DEFAULT 0,
|
|
28619
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
28620
|
-
last_error_code TEXT,
|
|
28621
|
-
checked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
28622
|
-
synced_at TEXT,
|
|
28623
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
28624
|
-
)`);
|
|
28625
|
-
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_charges (
|
|
28626
|
-
user_id INTEGER NOT NULL,
|
|
28627
|
-
connection_id TEXT NOT NULL,
|
|
28628
|
-
period_start TEXT NOT NULL,
|
|
28629
|
-
amount_mc INTEGER NOT NULL,
|
|
28630
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
28631
|
-
PRIMARY KEY (user_id, connection_id, period_start)
|
|
28632
|
-
)`);
|
|
28633
|
-
}
|
|
28634
28670
|
function connectedBillingPeriodStart(now = /* @__PURE__ */ new Date()) {
|
|
28635
28671
|
return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
28636
28672
|
}
|
|
@@ -28638,7 +28674,6 @@ function isBillableConnection(connection) {
|
|
|
28638
28674
|
return BILLABLE_LIFECYCLE.has(String(connection.lifecycleStatus ?? ""));
|
|
28639
28675
|
}
|
|
28640
28676
|
async function chargedConnectionIds(userId, periodStart) {
|
|
28641
|
-
await ensureConnectedAccountBillingSchema();
|
|
28642
28677
|
const result = await getDb().execute({
|
|
28643
28678
|
sql: "SELECT connection_id FROM connected_account_charges WHERE user_id = ? AND period_start = ?",
|
|
28644
28679
|
args: [Number(userId), periodStart]
|
|
@@ -28646,7 +28681,6 @@ async function chargedConnectionIds(userId, periodStart) {
|
|
|
28646
28681
|
return new Set(result.rows.map((row) => String(row.connection_id)));
|
|
28647
28682
|
}
|
|
28648
28683
|
async function getConnectedAccountBillingState2(userId) {
|
|
28649
|
-
await ensureConnectedAccountBillingSchema();
|
|
28650
28684
|
const result = await getDb().execute({
|
|
28651
28685
|
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
28652
28686
|
args: [Number(userId)]
|
|
@@ -28654,7 +28688,6 @@ async function getConnectedAccountBillingState2(userId) {
|
|
|
28654
28688
|
return result.rows[0] ? result.rows[0] : null;
|
|
28655
28689
|
}
|
|
28656
28690
|
async function setConnectedAccountBillingState2(input) {
|
|
28657
|
-
await ensureConnectedAccountBillingSchema();
|
|
28658
28691
|
await getDb().execute({
|
|
28659
28692
|
sql: `INSERT INTO connected_account_billing
|
|
28660
28693
|
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, checked_at, synced_at, updated_at)
|
|
@@ -33186,6 +33219,15 @@ var init_instagram_routes = __esm({
|
|
|
33186
33219
|
});
|
|
33187
33220
|
|
|
33188
33221
|
// src/api/reddit-trending.ts
|
|
33222
|
+
function buildRedditSearchUrl(topic, subreddit, window2 = "month") {
|
|
33223
|
+
const base = subreddit ? `https://old.reddit.com/r/${encodeURIComponent(subreddit)}/search` : "https://old.reddit.com/search";
|
|
33224
|
+
const params = new URLSearchParams();
|
|
33225
|
+
params.set("q", topic);
|
|
33226
|
+
params.set("sort", "top");
|
|
33227
|
+
params.set("t", window2);
|
|
33228
|
+
if (subreddit) params.set("restrict_sr", "on");
|
|
33229
|
+
return `${base}?${params.toString()}`;
|
|
33230
|
+
}
|
|
33189
33231
|
function buildSerpQuery(topic, subreddit) {
|
|
33190
33232
|
const scope = subreddit ? `site:reddit.com/r/${subreddit}` : "site:reddit.com";
|
|
33191
33233
|
return `${scope} ${topic}`.trim();
|
|
@@ -33248,6 +33290,32 @@ function canonicalThreadUrl(href) {
|
|
|
33248
33290
|
function engagementScore(score, commentCount) {
|
|
33249
33291
|
return score + 2 * commentCount;
|
|
33250
33292
|
}
|
|
33293
|
+
function normalizeSearchRows(rows) {
|
|
33294
|
+
if (!Array.isArray(rows)) return [];
|
|
33295
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33296
|
+
const out = [];
|
|
33297
|
+
for (const raw of rows) {
|
|
33298
|
+
if (!raw || typeof raw !== "object") continue;
|
|
33299
|
+
const r = raw;
|
|
33300
|
+
const url = canonicalThreadUrl(r.url);
|
|
33301
|
+
const title = typeof r.title === "string" ? r.title.trim() : "";
|
|
33302
|
+
if (!url || !title || seen.has(url)) continue;
|
|
33303
|
+
seen.add(url);
|
|
33304
|
+
const score = parseCountText(r.scoreText);
|
|
33305
|
+
const commentCount = parseCountText(r.commentsText);
|
|
33306
|
+
out.push({
|
|
33307
|
+
title,
|
|
33308
|
+
url,
|
|
33309
|
+
subreddit: typeof r.subreddit === "string" ? r.subreddit.trim() : "",
|
|
33310
|
+
score,
|
|
33311
|
+
commentCount,
|
|
33312
|
+
engagementScore: engagementScore(score, commentCount),
|
|
33313
|
+
ageText: typeof r.ageText === "string" ? r.ageText.trim() : "",
|
|
33314
|
+
topQuestions: []
|
|
33315
|
+
});
|
|
33316
|
+
}
|
|
33317
|
+
return out;
|
|
33318
|
+
}
|
|
33251
33319
|
function normalizeQuestionKey(s) {
|
|
33252
33320
|
return s.toLowerCase().replace(/[?.!\s]+$/g, "").replace(/\s+/g, " ").trim();
|
|
33253
33321
|
}
|
|
@@ -33298,10 +33366,29 @@ function flattenQuestions(threads) {
|
|
|
33298
33366
|
}
|
|
33299
33367
|
return out;
|
|
33300
33368
|
}
|
|
33301
|
-
var INTERROGATIVE_STARTERS;
|
|
33369
|
+
var PARSE_REDDIT_SEARCH, INTERROGATIVE_STARTERS;
|
|
33302
33370
|
var init_reddit_trending = __esm({
|
|
33303
33371
|
"src/api/reddit-trending.ts"() {
|
|
33304
33372
|
"use strict";
|
|
33373
|
+
PARSE_REDDIT_SEARCH = `(() => {
|
|
33374
|
+
const txt = el => ((el && el.innerText) || '').trim();
|
|
33375
|
+
const bodyText = (document.body && document.body.innerText) || '';
|
|
33376
|
+
const blocked = /whoa there|blocked by network|you've been blocked|network (policy|security)|log in to your reddit account/i.test(bodyText);
|
|
33377
|
+
const listing = document.querySelector('.search-result-listing');
|
|
33378
|
+
const rows = [];
|
|
33379
|
+
document.querySelectorAll('.search-result.search-result-link').forEach(r => {
|
|
33380
|
+
const titleEl = r.querySelector('header.search-result-header a.search-title');
|
|
33381
|
+
rows.push({
|
|
33382
|
+
title: txt(titleEl),
|
|
33383
|
+
url: (titleEl && titleEl.getAttribute('href')) || '',
|
|
33384
|
+
subreddit: txt(r.querySelector('a.search-subreddit-link')),
|
|
33385
|
+
scoreText: txt(r.querySelector('.search-score')),
|
|
33386
|
+
commentsText: txt(r.querySelector('a.search-comments')),
|
|
33387
|
+
ageText: txt(r.querySelector('.search-time time')),
|
|
33388
|
+
});
|
|
33389
|
+
});
|
|
33390
|
+
return { blocked: blocked, listingFound: Boolean(listing), rowCount: rows.length, rows: rows };
|
|
33391
|
+
})()`;
|
|
33305
33392
|
INTERROGATIVE_STARTERS = /* @__PURE__ */ new Set([
|
|
33306
33393
|
"what",
|
|
33307
33394
|
"whats",
|
|
@@ -35499,8 +35586,8 @@ async function residentialProxyId(attemptIndex) {
|
|
|
35499
35586
|
return void 0;
|
|
35500
35587
|
}
|
|
35501
35588
|
}
|
|
35502
|
-
async function scrapeOldRedditWithRetries(url, script, accept) {
|
|
35503
|
-
for (let attempt = 0; attempt <
|
|
35589
|
+
async function scrapeOldRedditWithRetries(url, script, accept, maxAttempts = 4) {
|
|
35590
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
35504
35591
|
const backupAttempt = attempt === 3;
|
|
35505
35592
|
if (backupAttempt && !backupProxyAvailable()) break;
|
|
35506
35593
|
const proxyId = backupAttempt ? await createBackupProxyIdSafe(browserServiceApiKey(), attempt) : await residentialProxyId(attempt);
|
|
@@ -35669,6 +35756,9 @@ var init_reddit_routes = __esm({
|
|
|
35669
35756
|
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.reddit_thread), 402);
|
|
35670
35757
|
discoveryDebited = true;
|
|
35671
35758
|
let candidates = [];
|
|
35759
|
+
let discoverySource = "google_serp";
|
|
35760
|
+
let primaryDiscoveryDegraded = false;
|
|
35761
|
+
let primaryDegradationReasons = [];
|
|
35672
35762
|
try {
|
|
35673
35763
|
const serp = await harvest({
|
|
35674
35764
|
query: serpQuery,
|
|
@@ -35682,14 +35772,59 @@ var init_reddit_routes = __esm({
|
|
|
35682
35772
|
softDeadlineMs
|
|
35683
35773
|
});
|
|
35684
35774
|
candidates = extractRedditThreadUrls(serp.organicResults, body.maxThreads);
|
|
35775
|
+
primaryDiscoveryDegraded = serp.diagnostics?.degradedResult === true;
|
|
35776
|
+
primaryDegradationReasons = Array.isArray(serp.diagnostics?.degradationReasons) ? serp.diagnostics.degradationReasons.filter((reason) => typeof reason === "string") : [];
|
|
35685
35777
|
} catch {
|
|
35686
35778
|
candidates = [];
|
|
35779
|
+
primaryDiscoveryDegraded = true;
|
|
35780
|
+
primaryDegradationReasons = ["primary_serp_failed"];
|
|
35781
|
+
}
|
|
35782
|
+
if (candidates.length === 0) {
|
|
35783
|
+
const fallback = await scrapeOldRedditWithRetries(
|
|
35784
|
+
buildRedditSearchUrl(body.topic, subreddit, body.window),
|
|
35785
|
+
PARSE_REDDIT_SEARCH,
|
|
35786
|
+
(data) => !data.blocked && data.listingFound,
|
|
35787
|
+
2
|
|
35788
|
+
);
|
|
35789
|
+
if (fallback) {
|
|
35790
|
+
candidates = normalizeSearchRows(fallback.data.rows).slice(0, body.maxThreads).map((thread) => ({ title: thread.title, url: thread.url, subreddit: thread.subreddit.replace(/^r\//i, "") }));
|
|
35791
|
+
discoverySource = "reddit_search_fallback";
|
|
35792
|
+
} else {
|
|
35793
|
+
discoverySource = "none";
|
|
35794
|
+
}
|
|
35687
35795
|
}
|
|
35688
35796
|
if (candidates.length === 0) {
|
|
35689
35797
|
await creditMc(user.id, MC_COSTS.reddit_thread, LedgerOperation.REDDIT_THREAD_REFUND, "no reddit threads discovered");
|
|
35690
35798
|
discoveryRefunded = true;
|
|
35691
|
-
|
|
35692
|
-
|
|
35799
|
+
const degradedResult = discoverySource === "none";
|
|
35800
|
+
const degradationReasons = degradedResult ? [.../* @__PURE__ */ new Set([...primaryDegradationReasons, "reddit_fallback_empty_or_blocked"])] : [];
|
|
35801
|
+
const result2 = {
|
|
35802
|
+
topic: body.topic,
|
|
35803
|
+
subreddit: subreddit ?? null,
|
|
35804
|
+
window: body.window === "week" ? "7d" : "30d",
|
|
35805
|
+
totals: trendingTotals([]),
|
|
35806
|
+
rankedThreads: [],
|
|
35807
|
+
questions: [],
|
|
35808
|
+
threadsScraped: 0,
|
|
35809
|
+
candidatesFound: 0,
|
|
35810
|
+
partial: false,
|
|
35811
|
+
searchQuery: serpQuery,
|
|
35812
|
+
discoverySource,
|
|
35813
|
+
resultQuality: degradedResult ? "degraded" : "complete",
|
|
35814
|
+
degradedResult,
|
|
35815
|
+
degradationReasons,
|
|
35816
|
+
retryRecommended: degradedResult,
|
|
35817
|
+
billingRefunded: true
|
|
35818
|
+
};
|
|
35819
|
+
await logRequestEvent({
|
|
35820
|
+
userId: user.id,
|
|
35821
|
+
source: "reddit_trending",
|
|
35822
|
+
status: "done",
|
|
35823
|
+
query: body.topic,
|
|
35824
|
+
resultCount: 0,
|
|
35825
|
+
result: result2
|
|
35826
|
+
});
|
|
35827
|
+
return c.json(result2);
|
|
35693
35828
|
}
|
|
35694
35829
|
let ranked;
|
|
35695
35830
|
if (!body.includeComments) {
|
|
@@ -35765,7 +35900,13 @@ var init_reddit_routes = __esm({
|
|
|
35765
35900
|
threadsScraped: body.includeComments ? ranked.length : 0,
|
|
35766
35901
|
candidatesFound: candidates.length,
|
|
35767
35902
|
partial: body.includeComments && ranked.length < candidates.length,
|
|
35768
|
-
searchQuery: serpQuery
|
|
35903
|
+
searchQuery: serpQuery,
|
|
35904
|
+
discoverySource,
|
|
35905
|
+
resultQuality: primaryDiscoveryDegraded ? "partial" : "complete",
|
|
35906
|
+
degradedResult: false,
|
|
35907
|
+
degradationReasons: primaryDiscoveryDegraded ? primaryDegradationReasons : [],
|
|
35908
|
+
retryRecommended: false,
|
|
35909
|
+
billingRefunded: false
|
|
35769
35910
|
};
|
|
35770
35911
|
await logRequestEvent({ userId: user.id, source: "reddit_trending", status: "done", query: body.topic, resultCount: ranked.length, result });
|
|
35771
35912
|
return c.json(result);
|
|
@@ -37670,6 +37811,89 @@ var init_paa_harvest_reconciliation = __esm({
|
|
|
37670
37811
|
}
|
|
37671
37812
|
});
|
|
37672
37813
|
|
|
37814
|
+
// src/api/retention-sweeps.ts
|
|
37815
|
+
function cutoffFor(rule) {
|
|
37816
|
+
const cutoff = new Date(Date.now() - rule.retentionDays * 24 * 60 * 60 * 1e3);
|
|
37817
|
+
if (rule.timestampFormat === "iso8601") return cutoff.toISOString();
|
|
37818
|
+
return cutoff.toISOString().replace("T", " ").slice(0, 19);
|
|
37819
|
+
}
|
|
37820
|
+
async function tableExists(table) {
|
|
37821
|
+
const res = await getDb().execute({
|
|
37822
|
+
sql: "SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1",
|
|
37823
|
+
args: [table]
|
|
37824
|
+
});
|
|
37825
|
+
return res.rows.length > 0;
|
|
37826
|
+
}
|
|
37827
|
+
async function sweepRule(rule, maxDeletes) {
|
|
37828
|
+
if (!await tableExists(rule.table)) return 0;
|
|
37829
|
+
const filter = rule.where ? ` AND ${rule.where}` : "";
|
|
37830
|
+
const cutoff = cutoffFor(rule);
|
|
37831
|
+
let deleted = 0;
|
|
37832
|
+
while (deleted < maxDeletes) {
|
|
37833
|
+
const res = await getDb().execute({
|
|
37834
|
+
sql: `DELETE FROM ${rule.table} WHERE rowid IN (
|
|
37835
|
+
SELECT rowid FROM ${rule.table}
|
|
37836
|
+
WHERE ${rule.timestampColumn} < ?${filter}
|
|
37837
|
+
LIMIT ?)`,
|
|
37838
|
+
args: [cutoff, Math.min(DEFAULT_BATCH, maxDeletes - deleted)]
|
|
37839
|
+
});
|
|
37840
|
+
const n = Number(res.rowsAffected ?? 0);
|
|
37841
|
+
deleted += n;
|
|
37842
|
+
if (n === 0) break;
|
|
37843
|
+
}
|
|
37844
|
+
return deleted;
|
|
37845
|
+
}
|
|
37846
|
+
async function sweepRetention(options = {}) {
|
|
37847
|
+
const maxDeletes = options.maxDeletesPerRule ?? DEFAULT_MAX_DELETES_PER_RULE;
|
|
37848
|
+
const deadline = Date.now() + (options.deadlineMs ?? DEFAULT_SWEEP_DEADLINE_MS);
|
|
37849
|
+
const perTable = {};
|
|
37850
|
+
let deleted = 0;
|
|
37851
|
+
let truncated = false;
|
|
37852
|
+
for (const rule of RETENTION_RULES) {
|
|
37853
|
+
const key = rule.where ? `${rule.table}:${rule.where}` : rule.table;
|
|
37854
|
+
if (Date.now() >= deadline) {
|
|
37855
|
+
truncated = true;
|
|
37856
|
+
break;
|
|
37857
|
+
}
|
|
37858
|
+
try {
|
|
37859
|
+
const n = await sweepRule(rule, maxDeletes);
|
|
37860
|
+
perTable[key] = n;
|
|
37861
|
+
deleted += n;
|
|
37862
|
+
if (n >= maxDeletes) truncated = true;
|
|
37863
|
+
} catch (err) {
|
|
37864
|
+
perTable[key] = -1;
|
|
37865
|
+
console.error(JSON.stringify({
|
|
37866
|
+
event: "retention_sweep_failed",
|
|
37867
|
+
table: rule.table,
|
|
37868
|
+
message: err instanceof Error ? err.message : String(err)
|
|
37869
|
+
}));
|
|
37870
|
+
}
|
|
37871
|
+
}
|
|
37872
|
+
return { deleted, perTable, truncated };
|
|
37873
|
+
}
|
|
37874
|
+
var REPORTING_WINDOW_DAYS, TELEMETRY_RETENTION_DAYS, FAILURE_RETENTION_DAYS, RETENTION_RULES, DEFAULT_BATCH, DEFAULT_MAX_DELETES_PER_RULE, DEFAULT_SWEEP_DEADLINE_MS;
|
|
37875
|
+
var init_retention_sweeps = __esm({
|
|
37876
|
+
"src/api/retention-sweeps.ts"() {
|
|
37877
|
+
"use strict";
|
|
37878
|
+
init_db();
|
|
37879
|
+
REPORTING_WINDOW_DAYS = 30;
|
|
37880
|
+
TELEMETRY_RETENTION_DAYS = 45;
|
|
37881
|
+
FAILURE_RETENTION_DAYS = 90;
|
|
37882
|
+
RETENTION_RULES = [
|
|
37883
|
+
{ table: "request_events", timestampColumn: "created_at", retentionDays: TELEMETRY_RETENTION_DAYS },
|
|
37884
|
+
{ table: "kernel_session_log", timestampColumn: "created_at", retentionDays: TELEMETRY_RETENTION_DAYS },
|
|
37885
|
+
{ table: "vendor_usage_log", timestampColumn: "created_at", retentionDays: TELEMETRY_RETENTION_DAYS },
|
|
37886
|
+
{ table: "harvest_attempts", timestampColumn: "created_at", retentionDays: TELEMETRY_RETENTION_DAYS, timestampFormat: "iso8601" },
|
|
37887
|
+
{ table: "browser_agent_actions", timestampColumn: "created_at", retentionDays: REPORTING_WINDOW_DAYS },
|
|
37888
|
+
{ table: "jobs", timestampColumn: "created_at", retentionDays: TELEMETRY_RETENTION_DAYS, where: "status = 'done'" },
|
|
37889
|
+
{ table: "jobs", timestampColumn: "created_at", retentionDays: FAILURE_RETENTION_DAYS, where: "status = 'failed'" }
|
|
37890
|
+
];
|
|
37891
|
+
DEFAULT_BATCH = 500;
|
|
37892
|
+
DEFAULT_MAX_DELETES_PER_RULE = 1e3;
|
|
37893
|
+
DEFAULT_SWEEP_DEADLINE_MS = 15e3;
|
|
37894
|
+
}
|
|
37895
|
+
});
|
|
37896
|
+
|
|
37673
37897
|
// src/api/directory-workflow-reconciliation.ts
|
|
37674
37898
|
async function reconcileDirectoryWorkflowSettlements(limit = 25) {
|
|
37675
37899
|
const expiredStarts = await listExpiredDirectoryWorkflowStarts(limit);
|
|
@@ -41371,7 +41595,7 @@ var PACKAGE_VERSION;
|
|
|
41371
41595
|
var init_version = __esm({
|
|
41372
41596
|
"src/version.ts"() {
|
|
41373
41597
|
"use strict";
|
|
41374
|
-
PACKAGE_VERSION = "0.
|
|
41598
|
+
PACKAGE_VERSION = "0.51.0";
|
|
41375
41599
|
}
|
|
41376
41600
|
});
|
|
41377
41601
|
|
|
@@ -41520,7 +41744,10 @@ seam is noted so you can chain them.
|
|
|
41520
41744
|
handles Reddit's bot wall itself (no login needed). Find threads first with \`search_serp\` or \`reddit_trending\`.
|
|
41521
41745
|
- DISCOVER what a niche is talking about (topic, no known thread) -> **reddit_trending** (takes a topic, optional
|
|
41522
41746
|
subreddit; returns the last 30 days' top threads ranked by engagement plus the questions people asked \u2014
|
|
41523
|
-
feed winning \`rankedThreads[].url\` values into \`reddit_thread\` for the full comment tree).
|
|
41747
|
+
feed winning \`rankedThreads[].url\` values into \`reddit_thread\` for the full comment tree). It uses bounded
|
|
41748
|
+
direct Reddit discovery when the primary SERP is empty. Before interpreting an empty result, inspect
|
|
41749
|
+
\`resultQuality\`, \`discoverySource\`, \`degradationReasons\`, \`retryRecommended\`, and \`billingRefunded\`;
|
|
41750
|
+
a degraded empty result is not evidence that the topic has no Reddit discussion.
|
|
41524
41751
|
|
|
41525
41752
|
## Other sites & logins (browser agent)
|
|
41526
41753
|
For an arbitrary site or a logged-in dashboard with no dedicated tool, use the browser_* agent. **First
|
|
@@ -41570,6 +41797,12 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
41570
41797
|
versioning reusable website templates; use the saved-template tools above for those jobs.
|
|
41571
41798
|
- The creation tool is a renderer, not a research or writing model. Preserve source truth in each
|
|
41572
41799
|
\`sourceLabel\`; do not hand it raw source material and expect it to invent the editorial architecture.
|
|
41800
|
+
- One edition may contain up to 100 articles. Images may appear as structured article/card heroes or as
|
|
41801
|
+
Markdown body images. Use \`site.ogImage\` for the collection social preview and \`article.ogImage\` for an
|
|
41802
|
+
article override; otherwise the article hero, then collection image, is reused. Every image requires useful
|
|
41803
|
+
alt text. Preserve caption, credit, source URL, and rights context when known; a reachable URL does not prove
|
|
41804
|
+
reuse rights. Static artifacts include collection OG tags and update article tags in-browser, while crawler-
|
|
41805
|
+
perfect per-article unfurls require the publishing host to serve article-specific metadata.
|
|
41573
41806
|
- ${savesReportsLocally ? "This local stdio server writes one self-contained HTML file and returns its localPath so the user can open it." : "This hosted server creates a private seven-day HTML artifact and returns a signed download URL; use renew_editorial_reading_room_download after the URL expires."}
|
|
41574
41807
|
|
|
41575
41808
|
## Local Sourcebook listings
|
|
@@ -41661,6 +41894,17 @@ deliberate edits and migrations. Use
|
|
|
41661
41894
|
they'll filter/sort by exact value. **memory-search** is meaning-based over full content (embeds, slower);
|
|
41662
41895
|
**memory-list** filters one vault by kind/tags (fast, exact).
|
|
41663
41896
|
|
|
41897
|
+
Choose the read surface by completeness, not convenience. **list-vaults** is the account inventory and
|
|
41898
|
+
reports each vault's note count. **memory-list** returns the complete metadata inventory for one vault but
|
|
41899
|
+
no note bodies, plus a sorted complete folder inventory derived from those paths; use it to identify every
|
|
41900
|
+
stable \`vault + path\` address. **list-memory-tags** returns the
|
|
41901
|
+
complete account-wide canonical tag inventory, including aliases, usage counts, and per-vault distribution.
|
|
41902
|
+
**memory-search** returns ranked
|
|
41903
|
+
content chunks, not an exhaustive inventory or complete documents: deduplicate hits by vault/path and call
|
|
41904
|
+
**memory-get** for every note whose full meaning matters. When the user explicitly needs every full note for
|
|
41905
|
+
backup, migration, or corpus-wide audit, use **memory-export** one vault at a time; do not pull the entire
|
|
41906
|
+
corpus merely to edit one note.
|
|
41907
|
+
|
|
41664
41908
|
For People, Deals, Projects, Tasks, and Communications, use the returned contract rather than guessing:
|
|
41665
41909
|
People only holds a real person or organization hub. Its contact card uses \`phone\`, \`text_phone\`, and
|
|
41666
41910
|
\`email\` for Call/Text/Email; \`memories\` for durable person context; and linked Deals, Projects,
|
|
@@ -41719,19 +41963,35 @@ Tags are live vocabulary, not improvised labels: **list-memory-tags** shows what
|
|
|
41719
41963
|
reusable, and has no exact, alias, or near-equivalent. Use **memory-backlinks**,
|
|
41720
41964
|
**memory-graph-universe**, and **memory-graph-path** to trace the linked universe across vaults.
|
|
41721
41965
|
|
|
41722
|
-
**Always inspect the complete tag inventory and related notes first.**
|
|
41966
|
+
**Always inspect the complete tag inventory and related notes first.** When the user wants to find, recall,
|
|
41967
|
+
understand, or connect something and does not already provide an exact vault/path, start with hybrid Smart
|
|
41968
|
+
RAG through **memory-search**. The exceptions are exhaustive inventory (**memory-list**), title-only lookup
|
|
41969
|
+
(**memory-suggest**), an exact known note (**memory-get**), and an explicit full-vault export (**memory-export**).
|
|
41970
|
+
For hybrid retrieval,
|
|
41723
41971
|
form 3 focused queries (2\u20134 when useful), fuse exact tag/metadata/vault/date and semantic matches into 50
|
|
41724
41972
|
candidates, expand the top 8 seeds by one link/backlink hop with at most 5 neighbors each, then Jina-rerank
|
|
41725
41973
|
the combined pool to the best 30. Graph neighbors are candidates, never automatic links; add only links
|
|
41726
|
-
supported by the note contents.
|
|
41974
|
+
supported by the note contents. A search hit is a discovery excerpt, not the note: call **memory-get** and read
|
|
41975
|
+
the complete note before relying on it for an answer, summary, edit, relationship, or durable write. Read the
|
|
41976
|
+
strong candidates, not every low-ranked hit.
|
|
41727
41977
|
|
|
41728
41978
|
Scrape deposits are raw evidence and therefore go to **Library** through **library-ingest**, with the full
|
|
41729
41979
|
Library template and source metadata. If the source contains durable applicable guidance, create a separate
|
|
41730
41980
|
Knowledge companion through prepare-memory-write + memory-capture and link it to the Library source with
|
|
41731
41981
|
derived_from; do not replace the raw source with the guide.
|
|
41732
41982
|
|
|
41733
|
-
For an update, call **memory-get** first and pass
|
|
41734
|
-
|
|
41983
|
+
For an update, identify the existing note by its stable \`vault + path\`, call **memory-get** first, and pass
|
|
41984
|
+
its revision as \`baseRevision\`. **memory-put replaces the entire content body; it is not a text patch.**
|
|
41985
|
+
Merge the requested change into the full body returned by memory-get, then send that complete merged body.
|
|
41986
|
+
Its supplied \`props\` patch existing metadata, so omit unchanged props and use an empty array only when the
|
|
41987
|
+
user deliberately wants to clear a link list. Preserve the existing title, source, capture time, surrounding
|
|
41988
|
+
content, links, template sections, and unsupported uncertainty unless the request deliberately changes them.
|
|
41989
|
+
A title change does not create a new note identity; keep the path unless the user explicitly asks to move it.
|
|
41990
|
+
Never convert an edit into a new path just because search returned a similar title. If \`baseRevision\`
|
|
41991
|
+
conflicts, reconcile the returned current body with the requested change and retry against the new revision;
|
|
41992
|
+
do not resend the stale full body unchanged. Treat Agent Inbox, optimizer rollups, channel messages, and other
|
|
41993
|
+
system-managed notes through their purpose-built tools when available rather than rewriting their backing
|
|
41994
|
+
notes with memory-put.
|
|
41735
41995
|
If a legacy backend does not return props, do not overwrite an existing linked note through that surface;
|
|
41736
41996
|
report that link preservation cannot be proved. After the write, read it back and search a distinctive
|
|
41737
41997
|
phrase to verify persistence and indexing.
|
|
@@ -42567,7 +42827,7 @@ var init_contracts = __esm({
|
|
|
42567
42827
|
});
|
|
42568
42828
|
|
|
42569
42829
|
// src/mcp/mcp-tool-schemas.ts
|
|
42570
|
-
var import_zod42, WEBSITE_URL_OR_DOMAIN_ERROR, WebsiteUrlOrDomainSchema, HarvestPaaInputSchema, ExtractUrlBaseInputSchema, ExtractUrlInputSchema, ExtractUrlLocalInputSchema, DiffPageBaseInputSchema, DiffPageInputSchema, DiffPageLocalInputSchema, MapSiteUrlsInputSchema, MapWaybackSnapshotsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, ArchiveReadInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, RedditTrendingInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, LocationMarketsInputSchema, CommonsSearchEntitiesInputSchema, CommonsGetEntityInputSchema, CommonsGetEntityLinksetInputSchema, CommonsFeaturedImageInputSchema, CommonsMediaInputSchema, CommonsCitationInputSchema, CommonsSourceInputSchema, CommonsRelatedLinkInputSchema, CommonsClaimInputSchema, CommonsPrepareEntityInputSchema, CommonsSubmitEntityInputSchema, CommonsValidateEntityInputSchema, CommonsGetEntityLedgerInputSchema, CommonsSaveFilterInputSchema, CommonsListFiltersInputSchema, CommonsListNeedsLinksInputSchema, CommonsGenericOutputSchema, DirectoryWorkflowStatusInputSchema, LocalSourcebookSubmitInputSchema, LocalSourcebookCategorySchema, LocalSourcebookSchemaTypeInputSchema, LocalSourcebookTagCandidateObjectSchema, LocalSourcebookTagDecisionObjectSchema, LocalSourcebookIdentityObjectSchema, GetLocalSourcebookContractInputSchema, ListLocalSourcebookTagsInputSchema, ResolveLocalSourcebookTagsInputSchema, PrepareLocalSourcebookWriteInputSchema, ValidateLocalSourcebookWriteInputSchema, LocalSourcebookCaptureInputSchema, LocalSourcebookSubmissionStatusInputSchema, LocalSourcebookRefreshInputSchema, LocalSourcebookOutputSchema, ArtifactPointerOutputSchema, EditorialReadingRoomSiteSchema, EditorialReadingRoomArticleSchema, EditorialReadingRoomGuideInputSchema, EditorialReadingRoomGuideOutputSchema, CreateEditorialReadingRoomInputSchema, EditorialReadingRoomArtifactSchema, CreateEditorialReadingRoomOutputSchema, RenewEditorialReadingRoomDownloadInputSchema, RenewEditorialReadingRoomDownloadOutputSchema, CommonsPublicationSubdomainSchema, CommonsPreparePublicationInputSchema, CommonsValidatePublicationInputSchema, CommonsClaimPublicationInputSchema, CommonsPublishEditorialInputSchema, CommonsGetPublicationInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryCsvArtifactOutput, DirectoryWorkflowOutputSchema, LocationDatasetProvenanceOutput, LocationMarketsOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, ArchiveEntryOutputSchema, ArchiveReadOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, WaybackCaptureOutputSchema, MapWaybackSnapshotsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, RedditTrendingOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, TestServiceConnectionInputSchema, TestServiceConnectionOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, SearchConsoleTableColumnSchema, SearchConsoleTableFilterSchema, ExportSearchConsoleTableDataInputSchema, ExportSearchConsoleTableDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GmailSearchContactsInputSchema, GmailSearchContactsOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
|
|
42830
|
+
var import_zod42, WEBSITE_URL_OR_DOMAIN_ERROR, WebsiteUrlOrDomainSchema, HarvestPaaInputSchema, ExtractUrlBaseInputSchema, ExtractUrlInputSchema, ExtractUrlLocalInputSchema, DiffPageBaseInputSchema, DiffPageInputSchema, DiffPageLocalInputSchema, MapSiteUrlsInputSchema, MapWaybackSnapshotsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, ArchiveReadInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, RedditTrendingInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, LocationMarketsInputSchema, CommonsSearchEntitiesInputSchema, CommonsGetEntityInputSchema, CommonsGetEntityLinksetInputSchema, CommonsFeaturedImageInputSchema, CommonsMediaInputSchema, CommonsCitationInputSchema, CommonsSourceInputSchema, CommonsRelatedLinkInputSchema, CommonsClaimInputSchema, CommonsPrepareEntityInputSchema, CommonsSubmitEntityInputSchema, CommonsValidateEntityInputSchema, CommonsGetEntityLedgerInputSchema, CommonsSaveFilterInputSchema, CommonsListFiltersInputSchema, CommonsListNeedsLinksInputSchema, CommonsGenericOutputSchema, DirectoryWorkflowStatusInputSchema, LocalSourcebookSubmitInputSchema, LocalSourcebookCategorySchema, LocalSourcebookSchemaTypeInputSchema, LocalSourcebookTagCandidateObjectSchema, LocalSourcebookTagDecisionObjectSchema, LocalSourcebookIdentityObjectSchema, GetLocalSourcebookContractInputSchema, ListLocalSourcebookTagsInputSchema, ResolveLocalSourcebookTagsInputSchema, PrepareLocalSourcebookWriteInputSchema, ValidateLocalSourcebookWriteInputSchema, LocalSourcebookCaptureInputSchema, LocalSourcebookSubmissionStatusInputSchema, LocalSourcebookRefreshInputSchema, LocalSourcebookOutputSchema, ArtifactPointerOutputSchema, EditorialReadingRoomSiteSchema, EditorialReadingRoomImageSchema, EditorialReadingRoomArticleSchema, EditorialReadingRoomGuideInputSchema, EditorialReadingRoomGuideOutputSchema, CreateEditorialReadingRoomInputSchema, EditorialReadingRoomArtifactSchema, CreateEditorialReadingRoomOutputSchema, RenewEditorialReadingRoomDownloadInputSchema, RenewEditorialReadingRoomDownloadOutputSchema, CommonsPublicationSubdomainSchema, CommonsPreparePublicationInputSchema, CommonsValidatePublicationInputSchema, CommonsClaimPublicationInputSchema, CommonsPublishEditorialInputSchema, CommonsGetPublicationInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryCsvArtifactOutput, DirectoryWorkflowOutputSchema, LocationDatasetProvenanceOutput, LocationMarketsOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, ArchiveEntryOutputSchema, ArchiveReadOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, WaybackCaptureOutputSchema, MapWaybackSnapshotsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, RedditTrendingOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, TestServiceConnectionInputSchema, TestServiceConnectionOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, SearchConsoleTableColumnSchema, SearchConsoleTableFilterSchema, ExportSearchConsoleTableDataInputSchema, ExportSearchConsoleTableDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GmailSearchContactsInputSchema, GmailSearchContactsOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
|
|
42571
42831
|
var init_mcp_tool_schemas = __esm({
|
|
42572
42832
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
42573
42833
|
"use strict";
|
|
@@ -43148,7 +43408,22 @@ var init_mcp_tool_schemas = __esm({
|
|
|
43148
43408
|
issueLabel: import_zod42.z.string().trim().min(1).max(100).default("Current edition").describe("Issue, date, or collection label in the home-page issue line."),
|
|
43149
43409
|
eyebrow: import_zod42.z.string().trim().min(1).max(120).default("A guided collection").describe("Short editorial eyebrow above the home-page headline."),
|
|
43150
43410
|
heroTitle: import_zod42.z.string().trim().min(1).max(180).describe("Outcome-led home-page headline for the whole reading room."),
|
|
43151
|
-
startLabel: import_zod42.z.string().trim().min(1).max(60).default("Start reading").describe("Label for the primary start-reading button.")
|
|
43411
|
+
startLabel: import_zod42.z.string().trim().min(1).max(60).default("Start reading").describe("Label for the primary start-reading button."),
|
|
43412
|
+
ogImage: import_zod42.z.object({
|
|
43413
|
+
url: import_zod42.z.string().url().refine((value) => /^https?:\/\//i.test(value), "Image URL must use HTTP or HTTPS.").describe("Public HTTP(S) image URL used for the reading-room home page social preview."),
|
|
43414
|
+
alt: import_zod42.z.string().trim().min(1).max(500).describe("Accessible description and og:image:alt text."),
|
|
43415
|
+
width: import_zod42.z.number().int().positive().optional().describe("Optional intrinsic width in pixels."),
|
|
43416
|
+
height: import_zod42.z.number().int().positive().optional().describe("Optional intrinsic height in pixels.")
|
|
43417
|
+
}).strict().optional().describe("Optional collection-level Open Graph image. Individual articles may override it.")
|
|
43418
|
+
}).strict();
|
|
43419
|
+
EditorialReadingRoomImageSchema = import_zod42.z.object({
|
|
43420
|
+
url: import_zod42.z.string().url().refine((value) => /^https?:\/\//i.test(value), "Image URL must use HTTP or HTTPS.").describe("Public HTTP(S) image URL."),
|
|
43421
|
+
alt: import_zod42.z.string().trim().min(1).max(500).describe("Required accessible description of the image."),
|
|
43422
|
+
caption: import_zod42.z.string().trim().max(1e3).optional().describe("Optional visible caption."),
|
|
43423
|
+
credit: import_zod42.z.string().trim().max(500).optional().describe("Optional visible creator, publisher, or rights credit."),
|
|
43424
|
+
sourceUrl: import_zod42.z.string().url().refine((value) => /^https?:\/\//i.test(value), "Source URL must use HTTP or HTTPS.").optional().describe("Optional public HTTP(S) source page for provenance or rights context."),
|
|
43425
|
+
width: import_zod42.z.number().int().positive().optional().describe("Optional intrinsic width in pixels."),
|
|
43426
|
+
height: import_zod42.z.number().int().positive().optional().describe("Optional intrinsic height in pixels.")
|
|
43152
43427
|
}).strict();
|
|
43153
43428
|
EditorialReadingRoomArticleSchema = import_zod42.z.object({
|
|
43154
43429
|
slug: import_zod42.z.string().trim().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(80).describe("Unique kebab-case article identifier."),
|
|
@@ -43161,7 +43436,9 @@ var init_mcp_tool_schemas = __esm({
|
|
|
43161
43436
|
sourceLabel: import_zod42.z.string().trim().min(1).max(500).describe("Visible provenance label naming the material this article was derived from. Do not invent a source."),
|
|
43162
43437
|
revision: import_zod42.z.string().trim().min(1).max(80).optional().describe("Optional revision identifier or version label."),
|
|
43163
43438
|
updatedAt: import_zod42.z.string().trim().min(1).max(80).optional().describe("Optional human-readable source update date."),
|
|
43164
|
-
|
|
43439
|
+
image: EditorialReadingRoomImageSchema.optional().describe("Optional article image shown on cards and above the article body. Markdown images remain supported inside the body."),
|
|
43440
|
+
ogImage: EditorialReadingRoomImageSchema.optional().describe("Optional article-specific social preview image. Defaults to article.image, then site.ogImage."),
|
|
43441
|
+
markdown: import_zod42.z.string().min(1).max(1e5).describe("Complete article body in Markdown. Standard Markdown images are allowed with descriptive alt text. Use H2/H3 headings for jump links, short paragraphs, concrete examples, and tables only where they improve comparison.")
|
|
43165
43442
|
}).strict();
|
|
43166
43443
|
EditorialReadingRoomGuideInputSchema = {
|
|
43167
43444
|
focus: import_zod42.z.enum(["workflow", "content_contract", "example"]).default("workflow").describe("Which part of the reusable editorial-reading-room guide to return. Start with workflow; fetch the content contract or compact example only when needed.")
|
|
@@ -43174,7 +43451,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
43174
43451
|
CreateEditorialReadingRoomInputSchema = {
|
|
43175
43452
|
site: EditorialReadingRoomSiteSchema,
|
|
43176
43453
|
deck: import_zod42.z.string().trim().min(1).max(1e3).describe("Two or three sentences that explain the collection\u2019s value and scope without generic marketing language."),
|
|
43177
|
-
articles: import_zod42.z.array(EditorialReadingRoomArticleSchema).min(1).max(
|
|
43454
|
+
articles: import_zod42.z.array(EditorialReadingRoomArticleSchema).min(1).max(100).describe("One to one hundred fully authored articles, with no more than 2,000,000 Markdown bytes combined. Articles may include structured card/hero images, article-specific Open Graph images, and Markdown body images. Read all in-scope source material before composing them; preserve distinctions, uncertainty, image provenance, and rights context instead of flattening the corpus."),
|
|
43178
43455
|
filename: import_zod42.z.string().trim().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/).max(120).optional().describe("Optional download filename. The server always normalizes it to a safe .html filename.")
|
|
43179
43456
|
};
|
|
43180
43457
|
EditorialReadingRoomArtifactSchema = import_zod42.z.object({
|
|
@@ -44001,7 +44278,13 @@ var init_mcp_tool_schemas = __esm({
|
|
|
44001
44278
|
threadsScraped: import_zod42.z.number().int().min(0),
|
|
44002
44279
|
candidatesFound: import_zod42.z.number().int().min(0),
|
|
44003
44280
|
partial: import_zod42.z.boolean(),
|
|
44004
|
-
searchQuery: import_zod42.z.string()
|
|
44281
|
+
searchQuery: import_zod42.z.string(),
|
|
44282
|
+
discoverySource: import_zod42.z.enum(["google_serp", "reddit_search_fallback", "none"]),
|
|
44283
|
+
resultQuality: import_zod42.z.enum(["complete", "partial", "degraded"]),
|
|
44284
|
+
degradedResult: import_zod42.z.boolean(),
|
|
44285
|
+
degradationReasons: import_zod42.z.array(import_zod42.z.string()),
|
|
44286
|
+
retryRecommended: import_zod42.z.boolean(),
|
|
44287
|
+
billingRefunded: import_zod42.z.boolean()
|
|
44005
44288
|
};
|
|
44006
44289
|
FacebookPageIntelOutputSchema = {
|
|
44007
44290
|
advertiserName: NullableString,
|
|
@@ -45184,10 +45467,11 @@ var init_guide = __esm({
|
|
|
45184
45467
|
2. Read every in-scope source before designing the page. Build a source inventory with purpose, authority, overlap, and provenance.
|
|
45185
45468
|
3. Architect the corpus into a small editorial edition. Prefer one article per real question, decision, lesson, or reusable pattern. Merge duplicate material; preserve meaningful distinctions and uncertainty.
|
|
45186
45469
|
4. Write the collection-level promise: a specific hero title and deck that explain what the reader will understand or be able to do.
|
|
45187
|
-
5. Author complete articles in Markdown. Use H2/H3 headings as a useful table of contents, short paragraphs, concrete examples, and tables only when they materially improve comparison.
|
|
45188
|
-
6.
|
|
45189
|
-
7.
|
|
45190
|
-
8.
|
|
45470
|
+
5. Author complete articles in Markdown. Use H2/H3 headings as a useful table of contents, short paragraphs, concrete examples, and tables only when they materially improve comparison. Markdown images are supported inside the body.
|
|
45471
|
+
6. Choose images only when they help the reader identify, understand, compare, or remember the material. Use article.image for the card/article hero, article.ogImage for a distinct social preview, and site.ogImage for the collection default. Every image needs descriptive alt text; preserve caption, credit, source URL, and rights context when known. Never invent attribution or imply usage rights.
|
|
45472
|
+
7. Preserve provenance in sourceLabel. Never invent a source, fact, result, quote, or certainty that the source material does not support.
|
|
45473
|
+
8. Call create_editorial_reading_room with the finished site metadata, deck, and ordered articles. Do not ask the renderer to discover, research, or write the content for you.
|
|
45474
|
+
9. Inspect the returned page at both mobile and desktop sizes. Verify navigation, jump links, article order, images, alt text, typography, overflow, source labels, social metadata, and that the page opens without a build step.
|
|
45191
45475
|
|
|
45192
45476
|
The renderer owns the reusable New York Times-inspired reading surface, responsive hamburger navigation, search, article jump links, progress, text-size controls, evening mode, and portable single-file HTML delivery.`;
|
|
45193
45477
|
CONTENT_CONTRACT = `Editorial reading-room content contract
|
|
@@ -45200,13 +45484,16 @@ The renderer owns the reusable New York Times-inspired reading surface, responsi
|
|
|
45200
45484
|
- site.eyebrow: collection framing, not a duplicate headline.
|
|
45201
45485
|
- site.heroTitle: specific outcome or understanding promised by the collection.
|
|
45202
45486
|
- site.startLabel: concise reading CTA.
|
|
45487
|
+
- site.ogImage: optional collection-level Open Graph image with required alt text.
|
|
45203
45488
|
- deck: two or three concrete sentences covering value and scope.
|
|
45204
|
-
- articles: 1-
|
|
45489
|
+
- articles: 1-100 complete editorial pieces, each with a unique slug and order.
|
|
45205
45490
|
- article.category: a repeated grouping label when multiple pieces belong together.
|
|
45206
45491
|
- article.kicker: short framing line.
|
|
45207
45492
|
- article.title: the question, decision, lesson, or reusable pattern.
|
|
45208
45493
|
- article.summary: what the reader will understand.
|
|
45209
45494
|
- article.sourceLabel: visible, truthful provenance.
|
|
45495
|
+
- article.image: optional card/article hero image with alt text and optional caption, credit, source URL, and dimensions.
|
|
45496
|
+
- article.ogImage: optional article-specific social image; otherwise article.image, then site.ogImage is used.
|
|
45210
45497
|
- article.markdown: full body with useful H2/H3 headings.
|
|
45211
45498
|
|
|
45212
45499
|
Quality gates:
|
|
@@ -45215,6 +45502,7 @@ Quality gates:
|
|
|
45215
45502
|
- Do not use placeholder copy.
|
|
45216
45503
|
- Keep headings descriptive enough to work as jump links.
|
|
45217
45504
|
- Prefer a coherent reading sequence over source-file order.
|
|
45505
|
+
- Use only public HTTP(S) image URLs. Preserve provenance and rights context; an available URL is not proof of reuse rights.
|
|
45218
45506
|
- If the corpus is too thin for multiple articles, create one strong article instead of padding the edition.`;
|
|
45219
45507
|
EXAMPLE = `Compact example:
|
|
45220
45508
|
|
|
@@ -45228,7 +45516,11 @@ Quality gates:
|
|
|
45228
45516
|
"issueLabel": "Pattern library",
|
|
45229
45517
|
"eyebrow": "A practical field guide",
|
|
45230
45518
|
"heroTitle": "Make every saved note easier to find and reuse",
|
|
45231
|
-
"startLabel": "Begin with the pattern"
|
|
45519
|
+
"startLabel": "Begin with the pattern",
|
|
45520
|
+
"ogImage": {
|
|
45521
|
+
"url": "https://example.com/reading-room-social.jpg",
|
|
45522
|
+
"alt": "Connected notes arranged as a navigable editorial collection"
|
|
45523
|
+
}
|
|
45232
45524
|
},
|
|
45233
45525
|
"deck": "A concise guide to placing new material in the right vault, connecting it to natural neighbors, and preserving source evidence without turning memory into a generic notes bucket.",
|
|
45234
45526
|
"articles": [{
|
|
@@ -45240,6 +45532,12 @@ Quality gates:
|
|
|
45240
45532
|
"summary": "A useful note is not only stored; it is classified, connected, and made retrievable.",
|
|
45241
45533
|
"sourceType": "Workflow synthesis",
|
|
45242
45534
|
"sourceLabel": "Derived from the MCP Memory capture contract supplied by the user.",
|
|
45535
|
+
"image": {
|
|
45536
|
+
"url": "https://example.com/graph-operation.jpg",
|
|
45537
|
+
"alt": "A source note connected to related project and knowledge notes",
|
|
45538
|
+
"caption": "Useful captures become connected retrieval objects.",
|
|
45539
|
+
"sourceUrl": "https://example.com/source"
|
|
45540
|
+
},
|
|
45243
45541
|
"markdown": "## Start with the destination\\n\\nChoose the vault whose job matches the material...\\n\\n## Connect natural neighbors\\n\\nLink only relationships supported by the notes..."
|
|
45244
45542
|
}]
|
|
45245
45543
|
}`;
|
|
@@ -46131,7 +46429,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
46131
46429
|
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
46132
46430
|
server.registerTool("reddit_trending", {
|
|
46133
46431
|
title: "Reddit Trending",
|
|
46134
|
-
description: "Discover
|
|
46432
|
+
description: "Discover top Reddit conversations from the last week or month. It tries Google site:reddit.com discovery, falls back to a bounded direct Reddit search when that SERP is empty or unavailable, then optionally scrapes threads for real upvotes, comments, questions, and engagement ranking. Inspect resultQuality, discoverySource, degradationReasons, retryRecommended, and billingRefunded before treating an empty result as a genuine lack of discussion. Set includeComments:false for a cheap discovery-only sweep; use reddit_thread for one known URL.",
|
|
46135
46433
|
inputSchema: RedditTrendingInputSchema,
|
|
46136
46434
|
outputSchema: recordOutputSchema("reddit_trending", RedditTrendingOutputSchema),
|
|
46137
46435
|
annotations: liveWebToolAnnotations("Reddit Trending")
|
|
@@ -46370,7 +46668,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
46370
46668
|
}, async (input) => executor.commonsPreparePublication(input));
|
|
46371
46669
|
server.registerTool("commons_validate_publication", {
|
|
46372
46670
|
title: "Validate Transparent Commons Publication",
|
|
46373
|
-
description: "Validate a publication name claim or a complete source-grounded editorial edition without writing.
|
|
46671
|
+
description: "Validate a publication name claim or a complete source-grounded editorial edition without writing. Publish validation accepts up to 100 articles plus structured article/card images, Markdown body images, and collection/article Open Graph images under the editorial reading-room contract. Use operation claim before commons_claim_publication and operation publish before commons_publish_editorial; ownership and revision conflicts are checked.",
|
|
46374
46672
|
inputSchema: CommonsValidatePublicationInputSchema,
|
|
46375
46673
|
outputSchema: recordOutputSchema("commons_validate_publication", CommonsGenericOutputSchema),
|
|
46376
46674
|
annotations: { title: "Validate Transparent Commons Publication", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -46384,7 +46682,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
46384
46682
|
}, async (input) => executor.commonsClaimPublication(input));
|
|
46385
46683
|
server.registerTool("commons_publish_editorial", {
|
|
46386
46684
|
title: "Publish Transparent Commons Editorial Edition",
|
|
46387
|
-
description: "Publish
|
|
46685
|
+
description: "Publish up to 100 fully authored editorial pieces, including optional article/card images, Markdown body images, and collection/article Open Graph images, to the caller-owned Transparent Commons subdomain. The calling AI must research and author the source-grounded edition first; image URLs need alt text and preserved provenance/rights context. The tool validates, renders, persists, and returns permanent root, archive, and edition URLs. Existing editions require current baseRevision. Requires idempotencyKey; this is not the neutral wiki write tool.",
|
|
46388
46686
|
inputSchema: CommonsPublishEditorialInputSchema,
|
|
46389
46687
|
outputSchema: recordOutputSchema("commons_publish_editorial", CommonsGenericOutputSchema),
|
|
46390
46688
|
annotations: { title: "Publish Transparent Commons Editorial Edition", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
@@ -46521,7 +46819,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
46521
46819
|
}, async (input) => formatWorkflowArtifactRead(await executor.workflowArtifactRead(input), input));
|
|
46522
46820
|
server.registerTool("editorial_reading_room_guide", {
|
|
46523
46821
|
title: "Editorial Reading Room Guide",
|
|
46524
|
-
description: 'Read the reusable composition contract before creating an editorial reading room. It
|
|
46822
|
+
description: 'Read the reusable composition contract before creating an editorial reading room. It covers corpus inventory, source truth, coherent architecture, up to 100 articles, purposeful images, image provenance, collection/article Open Graph images, and finished-page verification. Start with focus "workflow"; fetch "content_contract" or "example" only when needed. This does not research, write, or create a page.',
|
|
46525
46823
|
inputSchema: EditorialReadingRoomGuideInputSchema,
|
|
46526
46824
|
outputSchema: recordOutputSchema("editorial_reading_room_guide", EditorialReadingRoomGuideOutputSchema),
|
|
46527
46825
|
annotations: localPlanningToolAnnotations("Editorial Reading Room Guide")
|
|
@@ -46537,7 +46835,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
46537
46835
|
}));
|
|
46538
46836
|
server.registerTool("create_editorial_reading_room", {
|
|
46539
46837
|
title: "Create Editorial Reading Room",
|
|
46540
|
-
description: `
|
|
46838
|
+
description: `Render up to 100 fully authored, source-grounded articles into one polished mobile-first editorial report with contents, search, navigation, jump links, progress, text sizing, evening mode, provenance, structured article/card images, Markdown body images, and collection/article Open Graph metadata. Public HTTP(S) image URLs require alt text; preserve caption, credit, source, and rights context when known. The static artifact carries collection OG tags and updates article tags in-browser; a publishing host must serve article-specific metadata for crawler-perfect per-article unfurls. This renderer does not research or invent copy. For reusable website templates use list_artifact_templates instead. ${fileBehavior("Local stdio clients save one self-contained HTML file under the MCP Scraper output directory and return localPath.", "Hosted/app clients receive an owner-scoped private HTML artifact retained for seven days with a renewable signed download URL.")}`,
|
|
46541
46839
|
inputSchema: CreateEditorialReadingRoomInputSchema,
|
|
46542
46840
|
outputSchema: recordOutputSchema("create_editorial_reading_room", CreateEditorialReadingRoomOutputSchema),
|
|
46543
46841
|
annotations: {
|
|
@@ -46762,6 +47060,29 @@ var init_paa_mcp_server = __esm({
|
|
|
46762
47060
|
});
|
|
46763
47061
|
|
|
46764
47062
|
// src/mcp/http-mcp-tool-executor.ts
|
|
47063
|
+
function unclassifiedTransportFailure(path6, err) {
|
|
47064
|
+
const name = err instanceof Error ? err.name : typeof err;
|
|
47065
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
47066
|
+
console.error(JSON.stringify({
|
|
47067
|
+
event: "mcp_executor_transport_failed",
|
|
47068
|
+
path: path6,
|
|
47069
|
+
error_name: name,
|
|
47070
|
+
error_message: sanitizeVendorName(detail).slice(0, 400)
|
|
47071
|
+
}));
|
|
47072
|
+
return {
|
|
47073
|
+
content: [{
|
|
47074
|
+
type: "text",
|
|
47075
|
+
text: JSON.stringify({
|
|
47076
|
+
error: "service_unavailable",
|
|
47077
|
+
error_type: name,
|
|
47078
|
+
retryable: true,
|
|
47079
|
+
path: path6,
|
|
47080
|
+
message: publicErrorMessage("service_unavailable")
|
|
47081
|
+
})
|
|
47082
|
+
}],
|
|
47083
|
+
isError: true
|
|
47084
|
+
};
|
|
47085
|
+
}
|
|
46765
47086
|
function youtubeVideoIdFromUrl(url) {
|
|
46766
47087
|
if (!url) return null;
|
|
46767
47088
|
try {
|
|
@@ -46871,7 +47192,7 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
46871
47192
|
const rawOverride = process.env.MCP_SCRAPER_HTTP_TIMEOUT_MS;
|
|
46872
47193
|
const parsedOverride = rawOverride === void 0 ? NaN : Number(rawOverride);
|
|
46873
47194
|
this.httpTimeoutOverrideMs = Number.isFinite(parsedOverride) && parsedOverride > 0 ? parsedOverride : null;
|
|
46874
|
-
this.timeoutMs = this.httpTimeoutOverrideMs ??
|
|
47195
|
+
this.timeoutMs = this.httpTimeoutOverrideMs ?? DEFAULT_TOOL_CLIENT_TIMEOUT_MS;
|
|
46875
47196
|
const configuredSerpIntelligenceTimeoutMs = Number(process.env.MCP_SCRAPER_SERP_INTELLIGENCE_HTTP_TIMEOUT_MS ?? this.timeoutMs);
|
|
46876
47197
|
this.serpIntelligenceTimeoutMs = Number.isFinite(configuredSerpIntelligenceTimeoutMs) && configuredSerpIntelligenceTimeoutMs > 0 ? configuredSerpIntelligenceTimeoutMs : this.timeoutMs;
|
|
46877
47198
|
}
|
|
@@ -46910,7 +47231,7 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
46910
47231
|
isError: true
|
|
46911
47232
|
};
|
|
46912
47233
|
}
|
|
46913
|
-
return
|
|
47234
|
+
return unclassifiedTransportFailure(path6, err);
|
|
46914
47235
|
}
|
|
46915
47236
|
}
|
|
46916
47237
|
async callConnectedMutation(path6, body, idempotencyKey3, timeoutMs = this.timeoutMs) {
|
|
@@ -46944,8 +47265,8 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
46944
47265
|
return { content: [{ type: "text", text: JSON.stringify(httpErrorPayload(path6, res, data)) }], isError: true };
|
|
46945
47266
|
}
|
|
46946
47267
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
46947
|
-
} catch {
|
|
46948
|
-
return
|
|
47268
|
+
} catch (err) {
|
|
47269
|
+
return unclassifiedTransportFailure(path6, err);
|
|
46949
47270
|
}
|
|
46950
47271
|
}
|
|
46951
47272
|
async getTextArtifact(path6, maxBytes, timeoutMs = this.timeoutMs) {
|
|
@@ -46982,7 +47303,7 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
46982
47303
|
}]
|
|
46983
47304
|
};
|
|
46984
47305
|
} catch (err) {
|
|
46985
|
-
return
|
|
47306
|
+
return unclassifiedTransportFailure(path6, err);
|
|
46986
47307
|
}
|
|
46987
47308
|
}
|
|
46988
47309
|
harvestPaa(input) {
|
|
@@ -50222,7 +50543,7 @@ var init_memory_tool_schemas = __esm({
|
|
|
50222
50543
|
ExportSchema = {
|
|
50223
50544
|
id: "memory-export",
|
|
50224
50545
|
upstreamName: "exportTool",
|
|
50225
|
-
description: "Export every note in
|
|
50546
|
+
description: "Export every full note in one vault for an explicitly requested backup, migration, or corpus-wide audit \u2014 path, title, content, kind, last-updated, and count. This can be large and is not the normal way to find or edit one note: use memory-list for complete metadata inventory, memory-search for ranked recall, and memory-get for exact content. Defaults to the active or first entitled vault. Requires export scope; the export is logged to provenance.",
|
|
50226
50547
|
input: {
|
|
50227
50548
|
vault: import_zod47.z.string().optional().describe(
|
|
50228
50549
|
"Vault to export. Optional; defaults to the session active vault, then the first vault the caller is entitled to."
|
|
@@ -50274,7 +50595,7 @@ var init_memory_tool_schemas = __esm({
|
|
|
50274
50595
|
GetSchema = {
|
|
50275
50596
|
id: "memory-get",
|
|
50276
50597
|
upstreamName: "getTool",
|
|
50277
|
-
description: "Read
|
|
50598
|
+
description: "Read one complete note by exact vault+path, or by accepted shareId. After memory-search identifies a strong candidate, use this before relying on it for an answer, summary, edit, link, or durable write: search results are excerpts, not complete notes. Owned notes include stored Obsidian props so edits preserve links and template metadata. Returns the revision required as baseRevision on later edits/deletes. Requires read scope.",
|
|
50278
50599
|
input: {
|
|
50279
50600
|
vault: import_zod47.z.string().optional().describe(
|
|
50280
50601
|
"Vault to read from. Optional; defaults to the session active vault, then the first vault the caller is entitled to. Ignored when shareId is given."
|
|
@@ -50311,7 +50632,7 @@ var init_memory_tool_schemas = __esm({
|
|
|
50311
50632
|
ListSchema = {
|
|
50312
50633
|
id: "memory-list",
|
|
50313
50634
|
upstreamName: "listTool",
|
|
50314
|
-
description: "
|
|
50635
|
+
description: "Return every note in one vault as a complete metadata inventory \u2014 stable path, title, kind, tags, and last-updated \u2014 plus a sorted list of every unique folder and nested folder represented by those paths. It contains no bodies. Use memory-get for exact full content, memory-search for ranked semantic recall, or memory-export only when explicitly asked for every full note. Defaults to the active or first entitled vault; also returns entitled vaults. Requires read scope.",
|
|
50315
50636
|
input: {
|
|
50316
50637
|
vault: import_zod47.z.string().optional().describe(
|
|
50317
50638
|
"Vault to list. Optional; defaults to the session active vault, then the first vault the caller is entitled to."
|
|
@@ -50331,6 +50652,7 @@ var init_memory_tool_schemas = __esm({
|
|
|
50331
50652
|
updatedAt: import_zod47.z.string().describe("ISO-8601 timestamp of the note last update.")
|
|
50332
50653
|
})
|
|
50333
50654
|
).optional().describe("The notes in the vault (metadata only, no content). Present when ok is true."),
|
|
50655
|
+
folders: import_zod47.z.array(import_zod47.z.string()).optional().describe("Sorted complete folder inventory derived from note paths, including represented nested parent folders. Present when ok is true."),
|
|
50334
50656
|
vaults: import_zod47.z.array(import_zod47.z.string()).optional().describe("All vaults the caller is entitled to, for choosing a different vault to list."),
|
|
50335
50657
|
error: import_zod47.z.string().optional().describe("Human-readable failure reason when ok is false.")
|
|
50336
50658
|
},
|
|
@@ -50365,7 +50687,7 @@ var init_memory_tool_schemas = __esm({
|
|
|
50365
50687
|
PutSchema = {
|
|
50366
50688
|
id: "memory-put",
|
|
50367
50689
|
upstreamName: "putTool",
|
|
50368
|
-
description: "Create or deliberately edit one note at a path
|
|
50690
|
+
description: "Create or deliberately edit one note at a stable vault+path. On an existing note, first call memory-get, merge the requested change into its full body, and pass that complete body plus baseRevision: content is replace-all, while supplied props patch the stored props. A conflict returns the current body for reconciliation; never retry the stale full body unchanged. Use purpose-built tools instead of rewriting system-managed Agent Inbox, optimizer, or channel backing notes. For normal new People, Organizations, Deals, Projects, Tasks, or Communication records, use prepare-memory-write then memory-capture. For row-shaped datasets use table tools. Ordinary vaults are indexed and shareable; store real secrets only in a secure vault. Requires write scope.",
|
|
50369
50691
|
input: {
|
|
50370
50692
|
vault: import_zod47.z.string().optional().describe(
|
|
50371
50693
|
"Vault to write to. Optional; defaults to the session active vault, then the first vault the caller is entitled to. On a default-provisioned account, pick the vault whose job matches the content (see the server instructions for the full 16-vault guide) rather than defaulting blindly \u2014 e.g. a lesson learned goes in Knowledge, the raw source it came from goes in Library, a broken feature goes in Issues, a named real-world initiative goes in Projects. Do not use this low-level tool to create ordinary People, Organizations, Deals, Projects, Tasks, or Communication records: first use prepare-memory-write then memory-capture so relationships and approval state are validated."
|
|
@@ -50373,9 +50695,9 @@ var init_memory_tool_schemas = __esm({
|
|
|
50373
50695
|
path: import_zod47.z.string().optional().describe("Vault-relative note path to create or overwrite, e.g. projects/q3-plan. Writing an existing path replaces it. Required unless shareId is given."),
|
|
50374
50696
|
shareId: import_zod47.z.string().optional().describe("Edit a note someone individually shared with you and you accepted (accept-share), by its shareId, instead of vault+path. Requires the share to grant edit permission, and baseRevision is mandatory (get the current revision first) since you are editing alongside the owner and possibly others."),
|
|
50375
50697
|
title: import_zod47.z.string().optional().describe("Optional human-readable title; defaults are derived from the path when omitted."),
|
|
50376
|
-
content: import_zod47.z.string().min(1).describe("The
|
|
50698
|
+
content: import_zod47.z.string().min(1).describe("The complete note body to store and index. On edit this replaces the prior body, so merge the requested change into the full memory-get content before calling; never pass only the changed fragment."),
|
|
50377
50699
|
props: putTool_notePropsSchema.optional().describe("Obsidian note primitives plus vault-specific template fields. On edits, supplied fields patch the stored props instead of replacing the whole object; pass an empty array to deliberately clear a link list. Type/domain/folder also steer routing when no vault is given."),
|
|
50378
|
-
baseRevision: import_zod47.z.number().optional().describe("Revision the edit is based on (from
|
|
50700
|
+
baseRevision: import_zod47.z.number().optional().describe("Revision the edit is based on (from memory-get/put). Always supply it when an AI edits an existing note; a mismatch rejects the write and returns current content for reconciliation. Omit only for an intentional new note or explicit last-write-wins migration."),
|
|
50379
50701
|
tagDescriptions: import_zod47.z.record(import_zod47.z.string(), import_zod47.z.string()).optional().describe("One-line meaning for any tag in props.tags that is new to the account, keyed by tag. Tags resolve against the account's existing vocabulary; new tags require a one-line description.")
|
|
50380
50702
|
},
|
|
50381
50703
|
output: {
|
|
@@ -50409,7 +50731,7 @@ var init_memory_tool_schemas = __esm({
|
|
|
50409
50731
|
SearchSchema = {
|
|
50410
50732
|
id: "memory-search",
|
|
50411
50733
|
upstreamName: "searchTool",
|
|
50412
|
-
description: "Default
|
|
50734
|
+
description: "Default first tool whenever the user wants to find, recall, understand, or connect Memory content without an exact vault+path. Hybrid Smart RAG combines 2-4 semantic query variants with exact vault/tag/date/kind/type/metadata matches, expands one bounded graph hop, then reranks. Results are ranked excerpts, never an exhaustive inventory or complete notes: deduplicate by vault/path and call memory-get on strong candidates before answering, summarizing, editing, linking, or writing. Use memory-list for every note, memory-suggest for title-only lookup, and memory-export only for explicit full-vault dumps. Before tagging or writing, also inspect list-memory-tags.",
|
|
50413
50735
|
input: {
|
|
50414
50736
|
vault: import_zod47.z.string().optional().describe("Exact logical vault handle to search. Omit to search every entitled vault."),
|
|
50415
50737
|
query: import_zod47.z.string().min(1).describe("A focused semantic reformulation of the request."),
|
|
@@ -51586,6 +51908,24 @@ var init_memory_mcp_server = __esm({
|
|
|
51586
51908
|
});
|
|
51587
51909
|
|
|
51588
51910
|
// src/mcp/memory-mcp-tool-executor.ts
|
|
51911
|
+
function folderInventory(result) {
|
|
51912
|
+
if (!Array.isArray(result.notes)) return [];
|
|
51913
|
+
const folders = /* @__PURE__ */ new Set();
|
|
51914
|
+
for (const note of result.notes) {
|
|
51915
|
+
if (!note || typeof note !== "object" || Array.isArray(note)) continue;
|
|
51916
|
+
const path6 = note.path;
|
|
51917
|
+
if (typeof path6 !== "string") continue;
|
|
51918
|
+
const segments = path6.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
51919
|
+
for (let depth = 1; depth < segments.length; depth += 1) {
|
|
51920
|
+
folders.add(segments.slice(0, depth).join("/"));
|
|
51921
|
+
}
|
|
51922
|
+
}
|
|
51923
|
+
return [...folders].sort((a, b) => a.localeCompare(b));
|
|
51924
|
+
}
|
|
51925
|
+
function enrichMemoryResult(toolName, result) {
|
|
51926
|
+
if (toolName !== "listTool" || result.ok !== true) return result;
|
|
51927
|
+
return { ...result, folders: folderInventory(result) };
|
|
51928
|
+
}
|
|
51589
51929
|
var MemoryMcpToolExecutor;
|
|
51590
51930
|
var init_memory_mcp_tool_executor = __esm({
|
|
51591
51931
|
"src/mcp/memory-mcp-tool-executor.ts"() {
|
|
@@ -51612,7 +51952,10 @@ var init_memory_mcp_tool_executor = __esm({
|
|
|
51612
51952
|
const message = data?.error ?? `memory ${toolName} failed (HTTP ${res.status})`;
|
|
51613
51953
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
51614
51954
|
}
|
|
51615
|
-
const result =
|
|
51955
|
+
const result = enrichMemoryResult(
|
|
51956
|
+
toolName,
|
|
51957
|
+
data ?? { ok: false, error: `memory ${toolName} returned no result` }
|
|
51958
|
+
);
|
|
51616
51959
|
return {
|
|
51617
51960
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
51618
51961
|
structuredContent: result,
|
|
@@ -53305,13 +53648,21 @@ async function setBilledMc(id, billedMc) {
|
|
|
53305
53648
|
args: [Math.round(billedMc), id]
|
|
53306
53649
|
});
|
|
53307
53650
|
}
|
|
53308
|
-
async function markSessionClosed(id) {
|
|
53651
|
+
async function markSessionClosed(id, runtimeClosed = true) {
|
|
53309
53652
|
const db = getDb();
|
|
53310
53653
|
await db.execute({
|
|
53311
|
-
sql: `UPDATE browser_agent_sessions SET status =
|
|
53312
|
-
args: [id]
|
|
53654
|
+
sql: `UPDATE browser_agent_sessions SET status = ?, closed_at = datetime('now') WHERE id = ?`,
|
|
53655
|
+
args: [runtimeClosed ? "closed" : ORPHANED_SESSION_STATUS, id]
|
|
53313
53656
|
});
|
|
53314
53657
|
}
|
|
53658
|
+
async function listOrphanedSessionRows(limit = 50) {
|
|
53659
|
+
const db = getDb();
|
|
53660
|
+
const res = await db.execute({
|
|
53661
|
+
sql: `SELECT * FROM browser_agent_sessions WHERE status = ? ORDER BY closed_at LIMIT ?`,
|
|
53662
|
+
args: [ORPHANED_SESSION_STATUS, Math.max(1, Math.min(200, Math.round(limit)))]
|
|
53663
|
+
});
|
|
53664
|
+
return res.rows;
|
|
53665
|
+
}
|
|
53315
53666
|
async function recordAction(input) {
|
|
53316
53667
|
const db = getDb();
|
|
53317
53668
|
await db.execute({
|
|
@@ -53358,7 +53709,7 @@ async function listReplayRows(sessionId) {
|
|
|
53358
53709
|
});
|
|
53359
53710
|
return res.rows;
|
|
53360
53711
|
}
|
|
53361
|
-
var import_node_crypto28, _ready2, _migrationPromise2;
|
|
53712
|
+
var import_node_crypto28, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
|
|
53362
53713
|
var init_browser_agent_db = __esm({
|
|
53363
53714
|
"src/api/browser-agent-db.ts"() {
|
|
53364
53715
|
"use strict";
|
|
@@ -53366,6 +53717,7 @@ var init_browser_agent_db = __esm({
|
|
|
53366
53717
|
init_db();
|
|
53367
53718
|
_ready2 = false;
|
|
53368
53719
|
_migrationPromise2 = null;
|
|
53720
|
+
ORPHANED_SESSION_STATUS = "runtime_close_pending";
|
|
53369
53721
|
}
|
|
53370
53722
|
});
|
|
53371
53723
|
|
|
@@ -54522,6 +54874,21 @@ async function charge(sessionId, userId, _startedAtMs) {
|
|
|
54522
54874
|
if (res.ok) await setBilledMc(sessionId, owed);
|
|
54523
54875
|
}
|
|
54524
54876
|
}
|
|
54877
|
+
async function closeRuntimeSession(runtimeSessionId, sessionId) {
|
|
54878
|
+
try {
|
|
54879
|
+
await closeSession(runtimeSessionId);
|
|
54880
|
+
return true;
|
|
54881
|
+
} catch (err) {
|
|
54882
|
+
console.error(JSON.stringify({
|
|
54883
|
+
event: "browser_runtime_close_failed",
|
|
54884
|
+
session_id: sessionId,
|
|
54885
|
+
runtime_session_id: runtimeSessionId,
|
|
54886
|
+
error_name: err instanceof Error ? err.name : typeof err,
|
|
54887
|
+
message: sanitizeVendorName(err instanceof Error ? err.message : String(err)).slice(0, 300)
|
|
54888
|
+
}));
|
|
54889
|
+
return false;
|
|
54890
|
+
}
|
|
54891
|
+
}
|
|
54525
54892
|
async function closeAndSettleSession(row) {
|
|
54526
54893
|
if (row.user_id != null) {
|
|
54527
54894
|
try {
|
|
@@ -54529,15 +54896,24 @@ async function closeAndSettleSession(row) {
|
|
|
54529
54896
|
} catch {
|
|
54530
54897
|
}
|
|
54531
54898
|
}
|
|
54532
|
-
|
|
54533
|
-
|
|
54534
|
-
} catch {
|
|
54535
|
-
}
|
|
54536
|
-
await markSessionClosed(row.id).catch(() => {
|
|
54899
|
+
const runtimeClosed = await closeRuntimeSession(row.runtime_session_id, row.id);
|
|
54900
|
+
await markSessionClosed(row.id, runtimeClosed).catch(() => {
|
|
54537
54901
|
});
|
|
54538
54902
|
await releaseConcurrencyGate(row.concurrency_lock_id).catch(() => {
|
|
54539
54903
|
});
|
|
54540
54904
|
}
|
|
54905
|
+
async function retryOrphanedBrowserSessions(limit = 50) {
|
|
54906
|
+
const rows = await listOrphanedSessionRows(limit);
|
|
54907
|
+
let recovered = 0;
|
|
54908
|
+
for (const row of rows) {
|
|
54909
|
+
if (await closeRuntimeSession(row.runtime_session_id, row.id)) {
|
|
54910
|
+
await markSessionClosed(row.id, true).catch(() => {
|
|
54911
|
+
});
|
|
54912
|
+
recovered++;
|
|
54913
|
+
}
|
|
54914
|
+
}
|
|
54915
|
+
return { recovered, stillOpen: rows.length - recovered };
|
|
54916
|
+
}
|
|
54541
54917
|
async function completeAuthConnection(connectionId) {
|
|
54542
54918
|
const connection = await getAuthConnectionRow(connectionId);
|
|
54543
54919
|
if (!connection) return { ok: false, error: "not found" };
|
|
@@ -54562,7 +54938,7 @@ async function reapIdleBrowserSessions(idleSeconds = 120) {
|
|
|
54562
54938
|
function publicSession(row) {
|
|
54563
54939
|
return {
|
|
54564
54940
|
session_id: row.id,
|
|
54565
|
-
status: row.status,
|
|
54941
|
+
status: row.status === ORPHANED_SESSION_STATUS ? "closed" : row.status,
|
|
54566
54942
|
label: row.label,
|
|
54567
54943
|
watch_url: `/console/${row.id}`,
|
|
54568
54944
|
created_at: row.created_at,
|
|
@@ -59174,6 +59550,24 @@ function safeFilename3(requested, slug2) {
|
|
|
59174
59550
|
function escapedJsonForHtml(value) {
|
|
59175
59551
|
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
59176
59552
|
}
|
|
59553
|
+
function socialMeta(input) {
|
|
59554
|
+
const image = input.site.ogImage;
|
|
59555
|
+
const tags = [
|
|
59556
|
+
'<meta property="og:type" content="website">',
|
|
59557
|
+
`<meta property="og:title" content="${escapeHtml6(input.site.title)}">`,
|
|
59558
|
+
`<meta property="og:description" content="${escapeHtml6(input.deck || input.site.heroTitle)}">`,
|
|
59559
|
+
'<meta name="twitter:card" content="summary_large_image">',
|
|
59560
|
+
`<meta name="twitter:title" content="${escapeHtml6(input.site.title)}">`,
|
|
59561
|
+
`<meta name="twitter:description" content="${escapeHtml6(input.deck || input.site.heroTitle)}">`,
|
|
59562
|
+
`<meta property="og:image" content="${image ? escapeHtml6(image.url) : ""}">`,
|
|
59563
|
+
`<meta property="og:image:alt" content="${image ? escapeHtml6(image.alt) : ""}">`,
|
|
59564
|
+
`<meta property="og:image:width" content="${image?.width ?? ""}">`,
|
|
59565
|
+
`<meta property="og:image:height" content="${image?.height ?? ""}">`,
|
|
59566
|
+
`<meta name="twitter:image" content="${image ? escapeHtml6(image.url) : ""}">`,
|
|
59567
|
+
`<meta name="twitter:image:alt" content="${image ? escapeHtml6(image.alt) : ""}">`
|
|
59568
|
+
];
|
|
59569
|
+
return tags.join("\n ");
|
|
59570
|
+
}
|
|
59177
59571
|
function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
|
|
59178
59572
|
const sourceBytes = input.articles.reduce((total, article) => total + Buffer.byteLength(article.markdown), 0);
|
|
59179
59573
|
if (sourceBytes > 2e6) throw new Error("Editorial reading-room Markdown must be 2,000,000 bytes or fewer in total.");
|
|
@@ -59206,7 +59600,7 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
|
|
|
59206
59600
|
const generatedAt = now.toISOString();
|
|
59207
59601
|
const data = { ...input, generatedAt, articles };
|
|
59208
59602
|
const template = readAsset("index.html");
|
|
59209
|
-
const html = template.replace("<title>Editorial Reading Room</title>", `<title>${escapeHtml6(input.site.title)}</title>`).replace('content="A mobile-first editorial reading room."', `content="${escapeHtml6(input.deck || input.site.heroTitle)}"`).replace("/*__READING_ROOM_STYLES__*/", readAsset("styles.css")).replace("/*__READING_ROOM_DATA__*/", escapedJsonForHtml(data)).replace("/*__READING_ROOM_SCRIPT__*/", readAsset("app.js"));
|
|
59603
|
+
const html = template.replace("<title>Editorial Reading Room</title>", `<title>${escapeHtml6(input.site.title)}</title>`).replace('content="A mobile-first editorial reading room."', `content="${escapeHtml6(input.deck || input.site.heroTitle)}"`).replace("<!--__READING_ROOM_SOCIAL_META__-->", socialMeta(input)).replace("/*__READING_ROOM_STYLES__*/", readAsset("styles.css")).replace("/*__READING_ROOM_DATA__*/", escapedJsonForHtml(data)).replace("/*__READING_ROOM_SCRIPT__*/", readAsset("app.js"));
|
|
59210
59604
|
const bytes = Buffer.byteLength(html);
|
|
59211
59605
|
return {
|
|
59212
59606
|
html,
|
|
@@ -59447,6 +59841,38 @@ var init_editorial_reading_room_routes = __esm({
|
|
|
59447
59841
|
}
|
|
59448
59842
|
});
|
|
59449
59843
|
|
|
59844
|
+
// src/api/schema-presence.ts
|
|
59845
|
+
async function loadSchemaObjects() {
|
|
59846
|
+
const res = await getDb().execute(
|
|
59847
|
+
"SELECT name FROM sqlite_master WHERE type IN ('table', 'index')"
|
|
59848
|
+
);
|
|
59849
|
+
return new Set(res.rows.map((row) => String(row.name)));
|
|
59850
|
+
}
|
|
59851
|
+
function invalidateSchemaPresence() {
|
|
59852
|
+
cache = null;
|
|
59853
|
+
}
|
|
59854
|
+
async function schemaObjectsPresent(names) {
|
|
59855
|
+
if (names.length === 0) return true;
|
|
59856
|
+
try {
|
|
59857
|
+
if (!cache) cache = loadSchemaObjects();
|
|
59858
|
+
const present = await cache;
|
|
59859
|
+
if (names.every((name) => present.has(name))) return true;
|
|
59860
|
+
invalidateSchemaPresence();
|
|
59861
|
+
return false;
|
|
59862
|
+
} catch {
|
|
59863
|
+
invalidateSchemaPresence();
|
|
59864
|
+
return false;
|
|
59865
|
+
}
|
|
59866
|
+
}
|
|
59867
|
+
var cache;
|
|
59868
|
+
var init_schema_presence = __esm({
|
|
59869
|
+
"src/api/schema-presence.ts"() {
|
|
59870
|
+
"use strict";
|
|
59871
|
+
init_db();
|
|
59872
|
+
cache = null;
|
|
59873
|
+
}
|
|
59874
|
+
});
|
|
59875
|
+
|
|
59450
59876
|
// src/api/commons-linksets.ts
|
|
59451
59877
|
function normalizeCommonsClaims(input) {
|
|
59452
59878
|
if (input === void 0) return void 0;
|
|
@@ -59632,7 +60058,11 @@ var init_commons_linksets = __esm({
|
|
|
59632
60058
|
|
|
59633
60059
|
// src/api/commons-repository.ts
|
|
59634
60060
|
async function ensureCommonsSchema() {
|
|
59635
|
-
if (
|
|
60061
|
+
if (schemaReady) return;
|
|
60062
|
+
if (await schemaObjectsPresent(COMMONS_SCHEMA_OBJECTS)) {
|
|
60063
|
+
schemaReady = true;
|
|
60064
|
+
return;
|
|
60065
|
+
}
|
|
59636
60066
|
const db = getDb();
|
|
59637
60067
|
await db.batch([
|
|
59638
60068
|
{
|
|
@@ -59853,7 +60283,7 @@ async function ensureCommonsSchema() {
|
|
|
59853
60283
|
args: [COMMONS_SCHEMA_VERSION]
|
|
59854
60284
|
}
|
|
59855
60285
|
], "write");
|
|
59856
|
-
|
|
60286
|
+
schemaReady = true;
|
|
59857
60287
|
}
|
|
59858
60288
|
async function searchCommonsEntities(filters = {}, userId) {
|
|
59859
60289
|
await ensureCommonsSchema();
|
|
@@ -61568,12 +61998,13 @@ function jsonLikeValue(value) {
|
|
|
61568
61998
|
function escapeLike(value) {
|
|
61569
61999
|
return value.replace(/[%_]/g, "");
|
|
61570
62000
|
}
|
|
61571
|
-
var import_node_crypto35, COMMONS_SCHEMA_VERSION, DEFAULT_COMMONS_BASE_URL, DEFAULT_ENTITY_TYPE, COMMONS_ENTITY_PROFILES,
|
|
62001
|
+
var import_node_crypto35, COMMONS_SCHEMA_VERSION, DEFAULT_COMMONS_BASE_URL, DEFAULT_ENTITY_TYPE, COMMONS_ENTITY_PROFILES, schemaReady, COMMONS_SCHEMA_OBJECTS, CommonsRepositoryError;
|
|
61572
62002
|
var init_commons_repository = __esm({
|
|
61573
62003
|
"src/api/commons-repository.ts"() {
|
|
61574
62004
|
"use strict";
|
|
61575
62005
|
import_node_crypto35 = require("crypto");
|
|
61576
62006
|
init_db();
|
|
62007
|
+
init_schema_presence();
|
|
61577
62008
|
init_rates();
|
|
61578
62009
|
init_commons_linksets();
|
|
61579
62010
|
COMMONS_SCHEMA_VERSION = "2026-08-05.1";
|
|
@@ -61724,7 +62155,32 @@ var init_commons_repository = __esm({
|
|
|
61724
62155
|
]
|
|
61725
62156
|
}
|
|
61726
62157
|
};
|
|
61727
|
-
|
|
62158
|
+
schemaReady = false;
|
|
62159
|
+
COMMONS_SCHEMA_OBJECTS = [
|
|
62160
|
+
"schema_migrations",
|
|
62161
|
+
"commons_entities",
|
|
62162
|
+
"commons_claims",
|
|
62163
|
+
"commons_proposals",
|
|
62164
|
+
"commons_ledger",
|
|
62165
|
+
"commons_saved_filters",
|
|
62166
|
+
"commons_index_documents",
|
|
62167
|
+
"commons_publications",
|
|
62168
|
+
"commons_publication_editions",
|
|
62169
|
+
"commons_entities_status_updated",
|
|
62170
|
+
"commons_entities_type_updated",
|
|
62171
|
+
"commons_entities_source_domain",
|
|
62172
|
+
"commons_claims_subject_predicate",
|
|
62173
|
+
"commons_claims_object_entity",
|
|
62174
|
+
"commons_proposals_user_created",
|
|
62175
|
+
"commons_proposals_entity_created",
|
|
62176
|
+
"commons_ledger_entity_created",
|
|
62177
|
+
"commons_ledger_user_created",
|
|
62178
|
+
"commons_saved_filters_user_updated",
|
|
62179
|
+
"commons_index_documents_status_updated",
|
|
62180
|
+
"commons_index_documents_entity_type",
|
|
62181
|
+
"commons_publications_updated",
|
|
62182
|
+
"commons_publication_editions_publication_published"
|
|
62183
|
+
];
|
|
61728
62184
|
CommonsRepositoryError = class extends Error {
|
|
61729
62185
|
constructor(code, message, httpStatus = 400) {
|
|
61730
62186
|
super(message);
|
|
@@ -62681,7 +63137,10 @@ var init_analytics_attribution = __esm({
|
|
|
62681
63137
|
// src/api/analytics-repository.ts
|
|
62682
63138
|
var analytics_repository_exports = {};
|
|
62683
63139
|
__export(analytics_repository_exports, {
|
|
63140
|
+
ANALYTICS_CONTENT_SORTS: () => ANALYTICS_CONTENT_SORTS,
|
|
62684
63141
|
AnalyticsRepositoryError: () => AnalyticsRepositoryError,
|
|
63142
|
+
ENGAGED_SESSION_MS: () => ENGAGED_SESSION_MS,
|
|
63143
|
+
MAX_ENGAGED_MS: () => MAX_ENGAGED_MS,
|
|
62685
63144
|
analyticsAcquisition: () => analyticsAcquisition,
|
|
62686
63145
|
analyticsBusinessMetrics: () => analyticsBusinessMetrics,
|
|
62687
63146
|
analyticsChannelBreakdown: () => analyticsChannelBreakdown,
|
|
@@ -62725,6 +63184,7 @@ __export(analytics_repository_exports, {
|
|
|
62725
63184
|
migrateAnalytics: () => migrateAnalytics,
|
|
62726
63185
|
normalizeAnalyticsPath: () => normalizeAnalyticsPath,
|
|
62727
63186
|
normalizeAnalyticsUrl: () => normalizeAnalyticsUrl,
|
|
63187
|
+
normalizeContentOptions: () => normalizeContentOptions,
|
|
62728
63188
|
normalizeObservedHostname: () => normalizeObservedHostname,
|
|
62729
63189
|
queueAnalyticsActivation: () => queueAnalyticsActivation,
|
|
62730
63190
|
queueAnalyticsFormDelivery: () => queueAnalyticsFormDelivery,
|
|
@@ -63075,6 +63535,12 @@ async function migrateAnalytics() {
|
|
|
63075
63535
|
await client2.query(
|
|
63076
63536
|
`ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS utm_content text`
|
|
63077
63537
|
);
|
|
63538
|
+
await client2.query(
|
|
63539
|
+
`ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS engaged_ms integer`
|
|
63540
|
+
);
|
|
63541
|
+
await client2.query(
|
|
63542
|
+
`ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS scroll_depth smallint`
|
|
63543
|
+
);
|
|
63078
63544
|
await client2.query(
|
|
63079
63545
|
`ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL`
|
|
63080
63546
|
);
|
|
@@ -63600,6 +64066,12 @@ async function setAnalyticsPixelDomainState(input) {
|
|
|
63600
64066
|
404
|
|
63601
64067
|
);
|
|
63602
64068
|
}
|
|
64069
|
+
function boundedEngagedMs(value) {
|
|
64070
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.min(Math.trunc(value), MAX_ENGAGED_MS) : null;
|
|
64071
|
+
}
|
|
64072
|
+
function boundedScrollDepth(value) {
|
|
64073
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.min(Math.trunc(value), 100) : null;
|
|
64074
|
+
}
|
|
63603
64075
|
function normalizeAnalyticsPath(value) {
|
|
63604
64076
|
if (!value) return "/";
|
|
63605
64077
|
try {
|
|
@@ -63787,10 +64259,11 @@ async function ingestAnalyticsEvents(input) {
|
|
|
63787
64259
|
id, event_id, site_id, pixel_id, hostname, visitor_id, session_id, event_name,
|
|
63788
64260
|
occurred_at, path, canonical_url, title, referrer, source, medium, campaign,
|
|
63789
64261
|
device_class, properties, country_code, region_code, click_ids, person_id,
|
|
63790
|
-
channel_family, platform, campaign_id, ad_set_id, ad_id, creative_id, placement, utm_term, utm_content
|
|
64262
|
+
channel_family, platform, campaign_id, ad_set_id, ad_id, creative_id, placement, utm_term, utm_content,
|
|
64263
|
+
engaged_ms, scroll_depth
|
|
63791
64264
|
) VALUES (
|
|
63792
64265
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18::jsonb, $19, $20, $21::jsonb, $22,
|
|
63793
|
-
$23, $24, $25, $26, $27, $28, $29, $30, $31
|
|
64266
|
+
$23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33
|
|
63794
64267
|
) ON CONFLICT(site_id, event_id) DO NOTHING`,
|
|
63795
64268
|
[
|
|
63796
64269
|
(0, import_node_crypto37.randomUUID)(),
|
|
@@ -63823,7 +64296,9 @@ async function ingestAnalyticsEvents(input) {
|
|
|
63823
64296
|
attribution.creativeId,
|
|
63824
64297
|
attribution.placement,
|
|
63825
64298
|
attribution.term,
|
|
63826
|
-
attribution.content
|
|
64299
|
+
attribution.content,
|
|
64300
|
+
boundedEngagedMs(event2.engagedMs),
|
|
64301
|
+
boundedScrollDepth(event2.scrollDepth)
|
|
63827
64302
|
]
|
|
63828
64303
|
);
|
|
63829
64304
|
if (inserted.rowCount) accepted += 1;
|
|
@@ -64334,10 +64809,21 @@ async function analyticsChannelBreakdown(siteId, userId, filters) {
|
|
|
64334
64809
|
dataFreshThrough: (/* @__PURE__ */ new Date()).toISOString()
|
|
64335
64810
|
};
|
|
64336
64811
|
}
|
|
64337
|
-
|
|
64812
|
+
function normalizeContentOptions(options) {
|
|
64813
|
+
const sort = ANALYTICS_CONTENT_SORTS.includes(options?.sort) ? options.sort : "pageviews";
|
|
64814
|
+
return {
|
|
64815
|
+
sort,
|
|
64816
|
+
direction: options?.direction === "asc" ? "asc" : "desc",
|
|
64817
|
+
search: options?.search?.trim().slice(0, 300) || null
|
|
64818
|
+
};
|
|
64819
|
+
}
|
|
64820
|
+
async function analyticsContent(siteId, userId, filters, page, options) {
|
|
64338
64821
|
const db = getAnalyticsPool();
|
|
64339
64822
|
await accessFor(db, siteId, userId);
|
|
64340
64823
|
const query = eventFilterSql(siteId, filters);
|
|
64824
|
+
const table = normalizeContentOptions(options);
|
|
64825
|
+
const searchParam = `$${query.values.length + 1}`;
|
|
64826
|
+
const order = table.sort === "path" ? `metrics.path ${table.direction === "asc" ? "ASC" : "DESC"}` : `${table.sort} ${table.direction === "asc" ? "ASC" : "DESC"} NULLS LAST, metrics.path ASC`;
|
|
64341
64827
|
const result = await db.query(
|
|
64342
64828
|
`WITH filtered AS (
|
|
64343
64829
|
SELECT e.* FROM analytics_events e WHERE ${query.where}
|
|
@@ -64345,7 +64831,7 @@ async function analyticsContent(siteId, userId, filters, page) {
|
|
|
64345
64831
|
SELECT path, count(DISTINCT visitor_id)::int AS visitors,
|
|
64346
64832
|
count(DISTINCT session_id)::int AS sessions,
|
|
64347
64833
|
count(*) FILTER (WHERE event_name = 'page_view')::int AS pageviews,
|
|
64348
|
-
count(*) FILTER (WHERE event_name
|
|
64834
|
+
count(*) FILTER (WHERE event_name NOT IN ('page_view', 'page_engagement'))::int AS engagement_events,
|
|
64349
64835
|
count(*)::int AS events
|
|
64350
64836
|
FROM filtered GROUP BY path
|
|
64351
64837
|
), session_bounds AS (
|
|
@@ -64353,35 +64839,98 @@ async function analyticsContent(siteId, userId, filters, page) {
|
|
|
64353
64839
|
(array_agg(path ORDER BY occurred_at ASC, id ASC))[1] AS entrance_path,
|
|
64354
64840
|
(array_agg(path ORDER BY occurred_at DESC, id DESC))[1] AS exit_path
|
|
64355
64841
|
FROM filtered WHERE session_id IS NOT NULL GROUP BY session_id
|
|
64842
|
+
), session_pageviews AS (
|
|
64843
|
+
SELECT session_id, count(*) FILTER (WHERE event_name = 'page_view')::int AS pageviews
|
|
64844
|
+
FROM filtered WHERE session_id IS NOT NULL GROUP BY session_id
|
|
64845
|
+
), page_dwell AS (
|
|
64846
|
+
SELECT session_id, path,
|
|
64847
|
+
sum(engaged_ms)::bigint AS engaged_ms,
|
|
64848
|
+
max(scroll_depth)::int AS scroll_depth
|
|
64849
|
+
FROM filtered
|
|
64850
|
+
WHERE event_name = 'page_engagement' AND session_id IS NOT NULL
|
|
64851
|
+
GROUP BY session_id, path
|
|
64852
|
+
), dwell_metrics AS (
|
|
64853
|
+
SELECT path,
|
|
64854
|
+
avg(engaged_ms) FILTER (WHERE engaged_ms IS NOT NULL) AS avg_engaged_ms,
|
|
64855
|
+
avg(scroll_depth) FILTER (WHERE scroll_depth IS NOT NULL) AS avg_scroll,
|
|
64856
|
+
count(*) FILTER (WHERE engaged_ms IS NOT NULL)::int AS measured_pageviews
|
|
64857
|
+
FROM page_dwell GROUP BY path
|
|
64356
64858
|
), entrances AS (
|
|
64357
64859
|
SELECT entrance_path AS path, count(*)::int AS entrances FROM session_bounds GROUP BY entrance_path
|
|
64358
64860
|
), exits AS (
|
|
64359
64861
|
SELECT exit_path AS path, count(*)::int AS exits FROM session_bounds GROUP BY exit_path
|
|
64862
|
+
), bounces AS (
|
|
64863
|
+
SELECT b.entrance_path AS path, count(*)::int AS bounces
|
|
64864
|
+
FROM session_bounds b JOIN session_pageviews p USING(session_id)
|
|
64865
|
+
WHERE p.pageviews <= 1
|
|
64866
|
+
GROUP BY b.entrance_path
|
|
64867
|
+
), converted_sessions AS (
|
|
64868
|
+
SELECT DISTINCT c.session_id
|
|
64869
|
+
FROM analytics_conversions c
|
|
64870
|
+
WHERE c.site_id = $1 AND c.occurred_at >= $2::timestamptz AND c.occurred_at < $3::timestamptz
|
|
64871
|
+
AND c.session_id IS NOT NULL
|
|
64872
|
+
), path_sessions AS (
|
|
64873
|
+
SELECT DISTINCT path, session_id FROM filtered WHERE session_id IS NOT NULL
|
|
64874
|
+
), engaged AS (
|
|
64875
|
+
SELECT ps.path,
|
|
64876
|
+
count(*) FILTER (
|
|
64877
|
+
WHERE sp.pageviews >= 2
|
|
64878
|
+
OR COALESCE(pd.engaged_ms, 0) >= ${ENGAGED_SESSION_MS}
|
|
64879
|
+
OR cs.session_id IS NOT NULL
|
|
64880
|
+
)::int AS engaged_sessions
|
|
64881
|
+
FROM path_sessions ps
|
|
64882
|
+
JOIN session_pageviews sp USING(session_id)
|
|
64883
|
+
LEFT JOIN page_dwell pd ON pd.session_id = ps.session_id AND pd.path = ps.path
|
|
64884
|
+
LEFT JOIN converted_sessions cs ON cs.session_id = ps.session_id
|
|
64885
|
+
GROUP BY ps.path
|
|
64360
64886
|
), assisted AS (
|
|
64361
64887
|
SELECT f.path, count(DISTINCT c.id)::int AS conversions
|
|
64362
64888
|
FROM analytics_conversions c JOIN filtered f ON f.session_id = c.session_id
|
|
64363
64889
|
WHERE c.site_id = $1 AND c.occurred_at >= $2::timestamptz AND c.occurred_at < $3::timestamptz
|
|
64364
64890
|
GROUP BY f.path
|
|
64365
64891
|
)
|
|
64366
|
-
SELECT metrics.*,
|
|
64892
|
+
SELECT metrics.*,
|
|
64893
|
+
COALESCE(entrances.entrances, 0)::int AS entrances,
|
|
64367
64894
|
COALESCE(exits.exits, 0)::int AS exits,
|
|
64368
|
-
COALESCE(assisted.conversions, 0)::int AS conversions
|
|
64895
|
+
COALESCE(assisted.conversions, 0)::int AS conversions,
|
|
64896
|
+
COALESCE(dwell.measured_pageviews, 0)::int AS measured_pageviews,
|
|
64897
|
+
CASE WHEN dwell.avg_engaged_ms IS NULL THEN NULL
|
|
64898
|
+
ELSE round((dwell.avg_engaged_ms / 1000.0)::numeric, 1) END AS avg_seconds_on_page,
|
|
64899
|
+
CASE WHEN dwell.avg_scroll IS NULL THEN NULL
|
|
64900
|
+
ELSE round(dwell.avg_scroll::numeric, 1) END AS avg_scroll_depth,
|
|
64901
|
+
CASE WHEN COALESCE(entrances.entrances, 0) = 0 THEN NULL
|
|
64902
|
+
ELSE round(COALESCE(bounces.bounces, 0)::numeric * 100 / entrances.entrances, 1) END AS bounce_rate,
|
|
64903
|
+
CASE WHEN metrics.pageviews = 0 THEN NULL
|
|
64904
|
+
ELSE round(COALESCE(exits.exits, 0)::numeric * 100 / metrics.pageviews, 1) END AS exit_rate,
|
|
64905
|
+
CASE WHEN metrics.sessions = 0 THEN NULL
|
|
64906
|
+
ELSE round(COALESCE(engaged.engaged_sessions, 0)::numeric * 100 / metrics.sessions, 1) END AS engagement_rate,
|
|
64907
|
+
CASE WHEN metrics.sessions = 0 THEN NULL
|
|
64908
|
+
ELSE round(metrics.pageviews::numeric / metrics.sessions, 2) END AS pageviews_per_session
|
|
64369
64909
|
FROM page_metrics metrics
|
|
64370
64910
|
LEFT JOIN entrances USING(path)
|
|
64371
64911
|
LEFT JOIN exits USING(path)
|
|
64372
64912
|
LEFT JOIN assisted USING(path)
|
|
64373
|
-
|
|
64913
|
+
LEFT JOIN bounces USING(path)
|
|
64914
|
+
LEFT JOIN engaged USING(path)
|
|
64915
|
+
LEFT JOIN dwell_metrics dwell USING(path)
|
|
64916
|
+
WHERE ${searchParam}::text IS NULL OR metrics.path ILIKE '%' || ${searchParam} || '%'
|
|
64917
|
+
ORDER BY ${order}
|
|
64374
64918
|
LIMIT 1000`,
|
|
64375
|
-
query.values
|
|
64919
|
+
[...query.values, table.search]
|
|
64376
64920
|
);
|
|
64377
64921
|
const paged = paginateRows(
|
|
64378
64922
|
result.rows,
|
|
64379
64923
|
page,
|
|
64380
|
-
pageFingerprint(
|
|
64924
|
+
pageFingerprint(
|
|
64925
|
+
`content:${table.sort}:${table.direction}:${table.search ?? ""}`,
|
|
64926
|
+
siteId,
|
|
64927
|
+
filters
|
|
64928
|
+
)
|
|
64381
64929
|
);
|
|
64382
64930
|
return {
|
|
64383
64931
|
content: paged.items,
|
|
64384
64932
|
pageInfo: paged.pageInfo,
|
|
64933
|
+
table,
|
|
64385
64934
|
dataFreshThrough: (/* @__PURE__ */ new Date()).toISOString()
|
|
64386
64935
|
};
|
|
64387
64936
|
}
|
|
@@ -65211,7 +65760,7 @@ async function refreshAnalyticsDailyRollups(input) {
|
|
|
65211
65760
|
try {
|
|
65212
65761
|
await client2.query("BEGIN");
|
|
65213
65762
|
await client2.query(
|
|
65214
|
-
`DELETE FROM analytics_rollups_daily WHERE bucket >= $1::date AND bucket
|
|
65763
|
+
`DELETE FROM analytics_rollups_daily WHERE bucket >= $1::date AND bucket <= $2::date`,
|
|
65215
65764
|
[input.start, input.end]
|
|
65216
65765
|
);
|
|
65217
65766
|
const inserted = await client2.query(
|
|
@@ -65228,7 +65777,14 @@ async function refreshAnalyticsDailyRollups(input) {
|
|
|
65228
65777
|
now()
|
|
65229
65778
|
FROM analytics_events
|
|
65230
65779
|
WHERE occurred_at >= $1::timestamptz AND occurred_at < $2::timestamptz
|
|
65231
|
-
GROUP BY 1, 2, 3, 4, 5, 6, 7
|
|
65780
|
+
GROUP BY 1, 2, 3, 4, 5, 6, 7
|
|
65781
|
+
ON CONFLICT (site_id, pixel_id, hostname, bucket, source, medium, campaign)
|
|
65782
|
+
DO UPDATE SET
|
|
65783
|
+
pageviews = EXCLUDED.pageviews,
|
|
65784
|
+
events = EXCLUDED.events,
|
|
65785
|
+
visitors = EXCLUDED.visitors,
|
|
65786
|
+
sessions = EXCLUDED.sessions,
|
|
65787
|
+
refreshed_at = EXCLUDED.refreshed_at`,
|
|
65232
65788
|
[input.start, input.end]
|
|
65233
65789
|
);
|
|
65234
65790
|
await client2.query("COMMIT");
|
|
@@ -65240,9 +65796,17 @@ async function refreshAnalyticsDailyRollups(input) {
|
|
|
65240
65796
|
return { runId, rows };
|
|
65241
65797
|
} catch (error) {
|
|
65242
65798
|
await client2.query("ROLLBACK");
|
|
65799
|
+
const pgCode = error?.code;
|
|
65800
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
65801
|
+
console.error(JSON.stringify({
|
|
65802
|
+
event: "analytics_rollup_failed",
|
|
65803
|
+
runId,
|
|
65804
|
+
pgCode: pgCode ?? null,
|
|
65805
|
+
message: detail.slice(0, 400)
|
|
65806
|
+
}));
|
|
65243
65807
|
await db.query(
|
|
65244
|
-
`UPDATE analytics_rollup_runs SET status = 'failed', completed_at = now(), error_code =
|
|
65245
|
-
[runId]
|
|
65808
|
+
`UPDATE analytics_rollup_runs SET status = 'failed', completed_at = now(), error_code = $2 WHERE id = $1`,
|
|
65809
|
+
[runId, (pgCode ? `pg_${pgCode}` : "rollup_failed").slice(0, 60)]
|
|
65246
65810
|
);
|
|
65247
65811
|
throw error;
|
|
65248
65812
|
} finally {
|
|
@@ -65295,6 +65859,24 @@ function rowsToCsv2(rows) {
|
|
|
65295
65859
|
)
|
|
65296
65860
|
].join("\n");
|
|
65297
65861
|
}
|
|
65862
|
+
function markdownHeading(column) {
|
|
65863
|
+
return column.split("_").map((word, index) => index ? word : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
65864
|
+
}
|
|
65865
|
+
function markdownCell(value) {
|
|
65866
|
+
if (value === null || value === void 0) return "\u2014";
|
|
65867
|
+
return String(value).replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
65868
|
+
}
|
|
65869
|
+
function rowsToMarkdown(rows) {
|
|
65870
|
+
const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
65871
|
+
if (!columns.length) return ["No rows matched this window and filter set."];
|
|
65872
|
+
return [
|
|
65873
|
+
`| ${columns.map(markdownHeading).join(" | ")} |`,
|
|
65874
|
+
`| ${columns.map(() => "---").join(" | ")} |`,
|
|
65875
|
+
...rows.map(
|
|
65876
|
+
(row) => `| ${columns.map((column) => markdownCell(row[column])).join(" | ")} |`
|
|
65877
|
+
)
|
|
65878
|
+
];
|
|
65879
|
+
}
|
|
65298
65880
|
async function createAnalyticsExport(input) {
|
|
65299
65881
|
const site = (await listAnalyticsSites(input.userId)).find(
|
|
65300
65882
|
(candidate) => candidate.id === input.siteId
|
|
@@ -65362,6 +65944,7 @@ async function createAnalyticsExport(input) {
|
|
|
65362
65944
|
`- Verified conversions: ${overview.conversions}`,
|
|
65363
65945
|
`- Verified revenue: ${(overview.revenueMinor / 100).toFixed(2)}`,
|
|
65364
65946
|
"",
|
|
65947
|
+
...input.report === "overview" ? [] : [`## ${markdownHeading(input.report)} rows`, "", ...rowsToMarkdown(rows), ""],
|
|
65365
65948
|
`Generated ${(/* @__PURE__ */ new Date()).toISOString()} from the governed ${input.report} report contract.`
|
|
65366
65949
|
].join("\n");
|
|
65367
65950
|
}
|
|
@@ -65398,7 +65981,7 @@ async function createAnalyticsExport(input) {
|
|
|
65398
65981
|
}
|
|
65399
65982
|
return describeArtifact(replay.rows[0]);
|
|
65400
65983
|
}
|
|
65401
|
-
var import_node_crypto37, import_pg, AnalyticsRepositoryError, pool2, blockedPropertyName, inferredFamilySql, clickIdKeys;
|
|
65984
|
+
var import_node_crypto37, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
|
|
65402
65985
|
var init_analytics_repository = __esm({
|
|
65403
65986
|
"src/api/analytics-repository.ts"() {
|
|
65404
65987
|
"use strict";
|
|
@@ -65417,6 +66000,8 @@ var init_analytics_repository = __esm({
|
|
|
65417
66000
|
status;
|
|
65418
66001
|
};
|
|
65419
66002
|
pool2 = null;
|
|
66003
|
+
MAX_ENGAGED_MS = 30 * 60 * 1e3;
|
|
66004
|
+
ENGAGED_SESSION_MS = 1e4;
|
|
65420
66005
|
blockedPropertyName = /(email|phone|password|token|secret|authorization|cookie|name|address|form|body)/i;
|
|
65421
66006
|
inferredFamilySql = `COALESCE(e.channel_family, CASE
|
|
65422
66007
|
WHEN lower(COALESCE(e.source,'') || ' ' || COALESCE(e.referrer,'')) ~ '(perplexity|chatgpt|openai|claude|anthropic|grok|xai|ai[_ -]?overview|google[_ -]?ai|gemini)' THEN 'llm'
|
|
@@ -65425,6 +66010,22 @@ var init_analytics_repository = __esm({
|
|
|
65425
66010
|
WHEN COALESCE(e.source,'') = '' THEN 'direct'
|
|
65426
66011
|
WHEN lower(COALESCE(e.medium,'')) ~ 'email|newsletter' THEN 'email'
|
|
65427
66012
|
ELSE 'other' END)`;
|
|
66013
|
+
ANALYTICS_CONTENT_SORTS = [
|
|
66014
|
+
"path",
|
|
66015
|
+
"visitors",
|
|
66016
|
+
"sessions",
|
|
66017
|
+
"pageviews",
|
|
66018
|
+
"entrances",
|
|
66019
|
+
"exits",
|
|
66020
|
+
"engagement_events",
|
|
66021
|
+
"conversions",
|
|
66022
|
+
"avg_seconds_on_page",
|
|
66023
|
+
"avg_scroll_depth",
|
|
66024
|
+
"bounce_rate",
|
|
66025
|
+
"exit_rate",
|
|
66026
|
+
"engagement_rate",
|
|
66027
|
+
"pageviews_per_session"
|
|
66028
|
+
];
|
|
65428
66029
|
clickIdKeys = /* @__PURE__ */ new Set([
|
|
65429
66030
|
"fbclid",
|
|
65430
66031
|
"gclid",
|
|
@@ -65744,7 +66345,10 @@ function dashboardOriginAllowed(origin) {
|
|
|
65744
66345
|
return (process.env.ALLOWED_ORIGINS ?? process.env.APP_ORIGIN ?? "").split(",").map((value) => value.trim()).includes(origin);
|
|
65745
66346
|
}
|
|
65746
66347
|
function analyticsPixelSdk() {
|
|
65747
|
-
return `(()=>{const s=document.currentScript;if(!s)return;const pixel=s.dataset.pixel;if(!pixel)return;const endpoint=new URL('/analytics/v1/events',s.src).toString(),ttl=18e5;const key='mcp_analytics_visitor',sessionKey='mcp_analytics_session',touchKey='mcp_analytics_touch';const id=()=>crypto.randomUUID?crypto.randomUUID():'e_'+Date.now().toString(36)+Math.random().toString(36).slice(2);let consent=s.dataset.consent==='granted'&&!navigator.globalPrivacyControl,visitor=null,session=null;const read=k=>{try{return localStorage.getItem(k)}catch{return null}},write=(k,v)=>{try{localStorage.setItem(k,v)}catch{}};const ensure=()=>{if(!consent)return;visitor=read(key)||id();const now=Date.now();let saved={};try{saved=JSON.parse(read(sessionKey)||'{}')}catch{}session=saved.id&&now-Number(saved.seen||0)<ttl?saved.id:id();write(key,visitor);write(sessionKey,JSON.stringify({id:session,seen:now}))};const attribution=()=>{const q=new URLSearchParams(location.search),clickIds={},properties={},map={utm_term:'utm_term',utm_content:'utm_content',utm_id:'campaign_id',campaign_id:'campaign_id',adset_id:'ad_set_id',ad_set_id:'ad_set_id',adgroup_id:'ad_group_id',ad_group_id:'ad_group_id',ad_id:'ad_id',creative_id:'creative_id',placement:'placement'};for(const k of ['fbclid','gclid','gbraid','wbraid','ttclid','rdt_cid','msclkid']){const v=q.get(k);if(v)clickIds[k]=v}for(const [from,to] of Object.entries(map)){const v=q.get(from);if(v)properties[to]=v}const current={source:q.get('utm_source')||undefined,medium:q.get('utm_medium')||undefined,campaign:q.get('utm_campaign')||undefined,clickIds,properties};if(current.source||Object.keys(clickIds).length)write(touchKey,JSON.stringify(current));else{try{return JSON.parse(read(touchKey)||'null')||current}catch{}}return current};const send=(name,properties={})=>{if(!consent||navigator.globalPrivacyControl)return;ensure();const a=attribution();const body=JSON.stringify({pixelId:pixel,events:[{eventId:id(),eventName:name,occurredAt:new Date().toISOString(),visitorId:visitor,sessionId:session,path:location.pathname,canonicalUrl:location.origin+location.pathname,title:document.title,referrer:document.referrer||undefined,...a,deviceClass:innerWidth<640?'mobile':innerWidth<1024?'tablet':'desktop',properties:{...a.properties,...properties}}]});if(navigator.sendBeacon&&navigator.sendBeacon(endpoint,new Blob([body],{type:'application/json'})))return;fetch(endpoint,{method:'POST',headers:{'content-type':'application/json'},body,keepalive:true,credentials:'omit'}).catch(()=>{})};const api={page:()=>send('page_view'),track:send,consent:state=>{consent=(state==='granted'||state===true)&&!navigator.globalPrivacyControl;if(consent){ensure();api.page()}},debug:()=>({pixel,endpoint,consent,gpc:Boolean(navigator.globalPrivacyControl),visitorId:visitor,sessionId:session,hostname:location.hostname,path:location.pathname,...attribution()})};window.mcpAnalytics=api;ensure();if(consent)api.page();let current=location.href;const changed=()=>{if(location.href!==current){current=location.href;api.page()}};for(const method of ['pushState','replaceState']){const original=history[method];history[method]=function(...args){const result=original.apply(this,args);queueMicrotask(changed);return result}}addEventListener('popstate',changed)})();`;
|
|
66348
|
+
return `(()=>{const s=document.currentScript;if(!s)return;const pixel=s.dataset.pixel;if(!pixel)return;const endpoint=new URL('/analytics/v1/events',s.src).toString(),ttl=18e5;const key='mcp_analytics_visitor',sessionKey='mcp_analytics_session',touchKey='mcp_analytics_touch';const id=()=>crypto.randomUUID?crypto.randomUUID():'e_'+Date.now().toString(36)+Math.random().toString(36).slice(2);let consent=s.dataset.consent==='granted'&&!navigator.globalPrivacyControl,visitor=null,session=null;const read=k=>{try{return localStorage.getItem(k)}catch{return null}},write=(k,v)=>{try{localStorage.setItem(k,v)}catch{}};const ensure=()=>{if(!consent)return;visitor=read(key)||id();const now=Date.now();let saved={};try{saved=JSON.parse(read(sessionKey)||'{}')}catch{}session=saved.id&&now-Number(saved.seen||0)<ttl?saved.id:id();write(key,visitor);write(sessionKey,JSON.stringify({id:session,seen:now}))};const attribution=()=>{const q=new URLSearchParams(location.search),clickIds={},properties={},map={utm_term:'utm_term',utm_content:'utm_content',utm_id:'campaign_id',campaign_id:'campaign_id',adset_id:'ad_set_id',ad_set_id:'ad_set_id',adgroup_id:'ad_group_id',ad_group_id:'ad_group_id',ad_id:'ad_id',creative_id:'creative_id',placement:'placement'};for(const k of ['fbclid','gclid','gbraid','wbraid','ttclid','rdt_cid','msclkid']){const v=q.get(k);if(v)clickIds[k]=v}for(const [from,to] of Object.entries(map)){const v=q.get(from);if(v)properties[to]=v}const current={source:q.get('utm_source')||undefined,medium:q.get('utm_medium')||undefined,campaign:q.get('utm_campaign')||undefined,clickIds,properties};if(current.source||Object.keys(clickIds).length)write(touchKey,JSON.stringify(current));else{try{return JSON.parse(read(touchKey)||'null')||current}catch{}}return current};const send=(name,properties={},over={})=>{if(!consent||navigator.globalPrivacyControl)return;ensure();const a=attribution();const body=JSON.stringify({pixelId:pixel,events:[{eventId:id(),eventName:name,occurredAt:new Date().toISOString(),visitorId:visitor,sessionId:session,path:location.pathname,canonicalUrl:location.origin+location.pathname,title:document.title,referrer:document.referrer||undefined,...a,deviceClass:innerWidth<640?'mobile':innerWidth<1024?'tablet':'desktop',properties:{...a.properties,...properties},...over}]});if(navigator.sendBeacon&&navigator.sendBeacon(endpoint,new Blob([body],{type:'application/json'})))return;fetch(endpoint,{method:'POST',headers:{'content-type':'application/json'},body,keepalive:true,credentials:'omit'}).catch(()=>{})};let visibleSince=document.visibilityState==='visible'?Date.now():0,dwellMs=0,maxScroll=0,dwellPath=location.pathname;const depth=()=>{const d=document.documentElement,h=d.scrollHeight||1;return Math.max(0,Math.min(100,Math.round(((d.scrollTop||document.body.scrollTop||0)+innerHeight)/h*100)))};const accrue=()=>{if(visibleSince){dwellMs+=Date.now()-visibleSince;visibleSince=0}};const resume=()=>{if(!visibleSince)visibleSince=Date.now()};const flush=()=>{accrue();const ms=Math.min(dwellMs,18e5),sd=Math.max(maxScroll,depth());dwellMs=0;maxScroll=0;if(ms<250)return;send('page_engagement',{},{path:dwellPath,canonicalUrl:location.origin+dwellPath,engagedMs:ms,scrollDepth:sd})};addEventListener('scroll',()=>{const d=depth();if(d>maxScroll)maxScroll=d},{passive:true});document.addEventListener('visibilitychange',()=>{if(document.visibilityState==='hidden')flush();else resume()});addEventListener('pagehide',flush);const api={page:()=>send('page_view'),track:send,consent:state=>{consent=(state==='granted'||state===true)&&!navigator.globalPrivacyControl;if(consent){ensure();resume();api.page()}},debug:()=>({pixel,endpoint,consent,gpc:Boolean(navigator.globalPrivacyControl),visitorId:visitor,sessionId:session,hostname:location.hostname,path:location.pathname,engagedMs:dwellMs+(visibleSince?Date.now()-visibleSince:0),scrollDepth:Math.max(maxScroll,depth()),...attribution()})};window.mcpAnalytics=api;ensure();if(consent)api.page();let current=location.href;const changed=()=>{if(location.href!==current){flush();current=location.href;dwellPath=location.pathname;resume();api.page()}};for(const method of ['pushState','replaceState']){const original=history[method];history[method]=function(...args){const result=original.apply(this,args);queueMicrotask(changed);return result}}addEventListener('popstate',changed)})();`;
|
|
66349
|
+
}
|
|
66350
|
+
function pagePathFilter(value) {
|
|
66351
|
+
return value && value.startsWith("/") ? value : void 0;
|
|
65748
66352
|
}
|
|
65749
66353
|
function reportFilters(url) {
|
|
65750
66354
|
const end = url.searchParams.get("end") || (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -65766,7 +66370,7 @@ function reportFilters(url) {
|
|
|
65766
66370
|
channelFamily: url.searchParams.get("channelFamily") || void 0,
|
|
65767
66371
|
platform: url.searchParams.get("platform") || void 0,
|
|
65768
66372
|
referrer: url.searchParams.get("referrer") || void 0,
|
|
65769
|
-
path: url.searchParams.get("path")
|
|
66373
|
+
path: pagePathFilter(url.searchParams.get("path")),
|
|
65770
66374
|
deviceClass: url.searchParams.get("deviceClass") || void 0,
|
|
65771
66375
|
countryCode: url.searchParams.get("countryCode") || void 0,
|
|
65772
66376
|
regionCode: url.searchParams.get("regionCode") || void 0,
|
|
@@ -65902,7 +66506,9 @@ var init_analytics_routes = __esm({
|
|
|
65902
66506
|
campaign: import_zod53.z.string().trim().max(240).optional(),
|
|
65903
66507
|
clickIds: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.string().trim().max(500)).optional(),
|
|
65904
66508
|
deviceClass: import_zod53.z.enum(["desktop", "tablet", "mobile", "unknown"]).optional(),
|
|
65905
|
-
properties: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.unknown()).optional()
|
|
66509
|
+
properties: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.unknown()).optional(),
|
|
66510
|
+
engagedMs: import_zod53.z.number().int().min(0).max(MAX_ENGAGED_MS).optional(),
|
|
66511
|
+
scrollDepth: import_zod53.z.number().int().min(0).max(100).optional()
|
|
65906
66512
|
}).strict();
|
|
65907
66513
|
IngestionSchema = import_zod53.z.object({
|
|
65908
66514
|
pixelId: import_zod53.z.string().trim().min(8).max(160),
|
|
@@ -66902,14 +67508,13 @@ Imported through the Site Analytics CRM workflow.
|
|
|
66902
67508
|
["/sites/:siteId/overview", analyticsOverview],
|
|
66903
67509
|
["/sites/:siteId/timeseries", analyticsTimeseries],
|
|
66904
67510
|
["/sites/:siteId/acquisition", analyticsAcquisition],
|
|
66905
|
-
["/sites/:siteId/content", analyticsContent],
|
|
66906
67511
|
["/sites/:siteId/events", analyticsEventCounts]
|
|
66907
67512
|
]) {
|
|
66908
67513
|
analyticsApp.get(path6, auth3, async (c) => {
|
|
66909
67514
|
const url = new URL(c.req.url);
|
|
66910
67515
|
const filters = reportFilters(url);
|
|
66911
67516
|
validateReportWindow(filters);
|
|
66912
|
-
const paginated = path6.endsWith("/acquisition") || path6.endsWith("/
|
|
67517
|
+
const paginated = path6.endsWith("/acquisition") || path6.endsWith("/events");
|
|
66913
67518
|
const data = await load(
|
|
66914
67519
|
c.req.param("siteId"),
|
|
66915
67520
|
Number(c.get("user").id),
|
|
@@ -66919,6 +67524,24 @@ Imported through the Site Analytics CRM workflow.
|
|
|
66919
67524
|
return c.json({ ok: true, data, filters });
|
|
66920
67525
|
});
|
|
66921
67526
|
}
|
|
67527
|
+
analyticsApp.get("/sites/:siteId/content", auth3, async (c) => {
|
|
67528
|
+
const url = new URL(c.req.url);
|
|
67529
|
+
const filters = reportFilters(url);
|
|
67530
|
+
validateReportWindow(filters);
|
|
67531
|
+
const requestedSort = url.searchParams.get("sort");
|
|
67532
|
+
const data = await analyticsContent(
|
|
67533
|
+
c.req.param("siteId"),
|
|
67534
|
+
Number(c.get("user").id),
|
|
67535
|
+
filters,
|
|
67536
|
+
reportPage(url),
|
|
67537
|
+
{
|
|
67538
|
+
sort: ANALYTICS_CONTENT_SORTS.includes(requestedSort) ? requestedSort : void 0,
|
|
67539
|
+
direction: url.searchParams.get("direction") === "asc" ? "asc" : "desc",
|
|
67540
|
+
search: url.searchParams.get("search") || void 0
|
|
67541
|
+
}
|
|
67542
|
+
);
|
|
67543
|
+
return c.json({ ok: true, data, filters });
|
|
67544
|
+
});
|
|
66922
67545
|
analyticsApp.get("/sites/:siteId/dashboard", auth3, async (c) => {
|
|
66923
67546
|
const url = new URL(c.req.url);
|
|
66924
67547
|
const filters = reportFilters(url);
|
|
@@ -67152,7 +67775,7 @@ var init_scheduled_artifact_owner = __esm({
|
|
|
67152
67775
|
|
|
67153
67776
|
// src/api/scheduled-run-view-links.ts
|
|
67154
67777
|
async function ensureScheduledRunViewLinksSchema() {
|
|
67155
|
-
if (
|
|
67778
|
+
if (schemaReady2) return;
|
|
67156
67779
|
const db = getDb();
|
|
67157
67780
|
await db.execute(`
|
|
67158
67781
|
CREATE TABLE IF NOT EXISTS scheduled_run_view_links (
|
|
@@ -67200,7 +67823,7 @@ async function ensureScheduledRunViewLinksSchema() {
|
|
|
67200
67823
|
CREATE INDEX IF NOT EXISTS scheduled_run_view_links_expiry
|
|
67201
67824
|
ON scheduled_run_view_links(expires_at, revoked_at)
|
|
67202
67825
|
`);
|
|
67203
|
-
|
|
67826
|
+
schemaReady2 = true;
|
|
67204
67827
|
}
|
|
67205
67828
|
function tokenHash2(token4) {
|
|
67206
67829
|
return (0, import_node_crypto41.createHash)("sha256").update(token4).digest("hex");
|
|
@@ -67288,13 +67911,13 @@ async function revokeScheduledRunViewLink(ownerId, runId, shareId, now = /* @__P
|
|
|
67288
67911
|
});
|
|
67289
67912
|
return result.rowsAffected > 0;
|
|
67290
67913
|
}
|
|
67291
|
-
var import_node_crypto41,
|
|
67914
|
+
var import_node_crypto41, schemaReady2;
|
|
67292
67915
|
var init_scheduled_run_view_links = __esm({
|
|
67293
67916
|
"src/api/scheduled-run-view-links.ts"() {
|
|
67294
67917
|
"use strict";
|
|
67295
67918
|
import_node_crypto41 = require("crypto");
|
|
67296
67919
|
init_db();
|
|
67297
|
-
|
|
67920
|
+
schemaReady2 = false;
|
|
67298
67921
|
}
|
|
67299
67922
|
});
|
|
67300
67923
|
|
|
@@ -67904,9 +68527,9 @@ var init_public_scheduled_result_routes = __esm({
|
|
|
67904
68527
|
// src/api/service-connections.ts
|
|
67905
68528
|
function ensureServiceConnectionsSchema() {
|
|
67906
68529
|
const currentDb = getDb();
|
|
67907
|
-
if (
|
|
68530
|
+
if (schemaReady3 && schemaDb4 === currentDb) return schemaReady3;
|
|
67908
68531
|
schemaDb4 = currentDb;
|
|
67909
|
-
|
|
68532
|
+
schemaReady3 = (async () => {
|
|
67910
68533
|
const db = currentDb;
|
|
67911
68534
|
await db.execute(`
|
|
67912
68535
|
CREATE TABLE IF NOT EXISTS service_connections (
|
|
@@ -67974,11 +68597,11 @@ function ensureServiceConnectionsSchema() {
|
|
|
67974
68597
|
} catch {
|
|
67975
68598
|
}
|
|
67976
68599
|
})().catch((error) => {
|
|
67977
|
-
|
|
68600
|
+
schemaReady3 = null;
|
|
67978
68601
|
schemaDb4 = null;
|
|
67979
68602
|
throw error;
|
|
67980
68603
|
});
|
|
67981
|
-
return
|
|
68604
|
+
return schemaReady3;
|
|
67982
68605
|
}
|
|
67983
68606
|
function parseTools(value) {
|
|
67984
68607
|
if (typeof value !== "string") return [];
|
|
@@ -68177,13 +68800,13 @@ async function claimServiceConnectionAction(args) {
|
|
|
68177
68800
|
...result !== void 0 ? { result } : {}
|
|
68178
68801
|
};
|
|
68179
68802
|
}
|
|
68180
|
-
var import_node_crypto45,
|
|
68803
|
+
var import_node_crypto45, schemaReady3, schemaDb4;
|
|
68181
68804
|
var init_service_connections = __esm({
|
|
68182
68805
|
"src/api/service-connections.ts"() {
|
|
68183
68806
|
"use strict";
|
|
68184
68807
|
import_node_crypto45 = require("crypto");
|
|
68185
68808
|
init_db();
|
|
68186
|
-
|
|
68809
|
+
schemaReady3 = null;
|
|
68187
68810
|
schemaDb4 = null;
|
|
68188
68811
|
}
|
|
68189
68812
|
});
|
|
@@ -73113,7 +73736,31 @@ async function chargeTierChangeNow(stripeClient, subscriptionId, customerId) {
|
|
|
73113
73736
|
return { ok: false, amountDue: 0, error: err instanceof Error ? err.message : "Unable to charge the plan change immediately." };
|
|
73114
73737
|
}
|
|
73115
73738
|
}
|
|
73116
|
-
|
|
73739
|
+
function settleWithinTickBudget(label, unfinished, work, onDeadlineOrError) {
|
|
73740
|
+
return new Promise((resolve2) => {
|
|
73741
|
+
const timer = setTimeout(() => {
|
|
73742
|
+
unfinished.push(`${label}:deadline`);
|
|
73743
|
+
resolve2(onDeadlineOrError);
|
|
73744
|
+
}, CRON_TICK_BUDGET_MS);
|
|
73745
|
+
work.then(
|
|
73746
|
+
(value) => {
|
|
73747
|
+
clearTimeout(timer);
|
|
73748
|
+
resolve2(value);
|
|
73749
|
+
},
|
|
73750
|
+
(err) => {
|
|
73751
|
+
clearTimeout(timer);
|
|
73752
|
+
unfinished.push(`${label}:error`);
|
|
73753
|
+
console.error(JSON.stringify({
|
|
73754
|
+
event: "cron_tick_job_failed",
|
|
73755
|
+
job: label,
|
|
73756
|
+
message: err instanceof Error ? err.message : String(err)
|
|
73757
|
+
}));
|
|
73758
|
+
resolve2(onDeadlineOrError);
|
|
73759
|
+
}
|
|
73760
|
+
);
|
|
73761
|
+
});
|
|
73762
|
+
}
|
|
73763
|
+
var import_resend3, import_node_crypto55, import_hono36, import_hono37, import_factory8, import_cookie2, import_stripe2, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth4, sessionAuth, requireIntegrationsTier, requirePaidSchedulingTier, app, deploymentProfile, STRIPE_API_VERSION, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS, CRON_TICK_BUDGET_MS, CRON_TICK_DRAIN_BUDGET_MS;
|
|
73117
73764
|
var init_server = __esm({
|
|
73118
73765
|
"src/api/server.ts"() {
|
|
73119
73766
|
"use strict";
|
|
@@ -73167,6 +73814,8 @@ var init_server = __esm({
|
|
|
73167
73814
|
init_directory_workflow_dispatch();
|
|
73168
73815
|
init_paa_harvest_dispatch();
|
|
73169
73816
|
init_paa_harvest_reconciliation();
|
|
73817
|
+
init_unified_billing();
|
|
73818
|
+
init_retention_sweeps();
|
|
73170
73819
|
init_directory_workflow_reconciliation();
|
|
73171
73820
|
init_directory_artifacts();
|
|
73172
73821
|
init_workflow_routes();
|
|
@@ -75858,35 +76507,49 @@ var init_server = __esm({
|
|
|
75858
76507
|
ledger
|
|
75859
76508
|
});
|
|
75860
76509
|
});
|
|
76510
|
+
CRON_TICK_BUDGET_MS = 5e4;
|
|
76511
|
+
CRON_TICK_DRAIN_BUDGET_MS = 4e4;
|
|
75861
76512
|
app.get("/cron/tick", async (c) => {
|
|
75862
76513
|
const secret2 = c.req.header("authorization");
|
|
75863
76514
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
75864
76515
|
return c.json({ error: "Unauthorized" }, 401);
|
|
75865
76516
|
}
|
|
76517
|
+
const startedAt = Date.now();
|
|
75866
76518
|
const { drainQueue: drainQueue2 } = await Promise.resolve().then(() => (init_worker(), worker_exports));
|
|
75867
|
-
const budget = { maxJobs: 10, deadlineMs:
|
|
75868
|
-
const
|
|
75869
|
-
const
|
|
75870
|
-
|
|
75871
|
-
|
|
75872
|
-
|
|
75873
|
-
|
|
75874
|
-
|
|
75875
|
-
|
|
75876
|
-
|
|
75877
|
-
|
|
75878
|
-
|
|
75879
|
-
|
|
75880
|
-
|
|
75881
|
-
|
|
75882
|
-
|
|
75883
|
-
|
|
75884
|
-
|
|
75885
|
-
|
|
75886
|
-
|
|
75887
|
-
|
|
76519
|
+
const budget = { maxJobs: 10, deadlineMs: startedAt + CRON_TICK_DRAIN_BUDGET_MS };
|
|
76520
|
+
const origin = `${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`;
|
|
76521
|
+
const unfinished = [];
|
|
76522
|
+
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation, localSourcebookRedispatch, analyticsRollup, analyticsFormDelivery, workflowDispatchResult, scheduledRunReconciliation, retentionSweep] = await Promise.all([
|
|
76523
|
+
settleWithinTickBudget("drainQueue", unfinished, drainQueue2(budget), []),
|
|
76524
|
+
settleWithinTickBudget("monthlyRefreshSweep", unfinished, runMonthlyRefreshSweep(), { status: "skipped_deadline" }),
|
|
76525
|
+
settleWithinTickBudget("reapBrowserSessions", unfinished, reapIdleBrowserSessions(120), { reaped: 0 }),
|
|
76526
|
+
settleWithinTickBudget("retryOrphanedBrowserSessions", unfinished, retryOrphanedBrowserSessions(50), { recovered: 0, stillOpen: 0 }),
|
|
76527
|
+
settleWithinTickBudget("expireLots", unfinished, expireOldLots(), { expired_lots: 0, expired_mc: 0 }),
|
|
76528
|
+
settleWithinTickBudget("scrapeBlobCleanup", unfinished, cleanupExpiredScrapeBlobs(), { deleted: 0, store: "none" }),
|
|
76529
|
+
settleWithinTickBudget("connectedDataCleanup", unfinished, cleanupExpiredConnectedDataArtifacts(), { deleted: 0, store: "none" }),
|
|
76530
|
+
settleWithinTickBudget("connectedBillingSweep", unfinished, reconcileConnectedAccountBillingSweep(), { checked: 0, synced: 0, failed: 0 }),
|
|
76531
|
+
settleWithinTickBudget("directoryDispatch", unfinished, dispatchPendingDirectoryWorkflows(5), { claimed: 0, dispatched: 0, failed: 0 }),
|
|
76532
|
+
settleWithinTickBudget("directoryReconcile", unfinished, reconcileDirectoryWorkflowSettlements(5), { checked: 0, settled: 0, failed: 0 }),
|
|
76533
|
+
settleWithinTickBudget("directoryArtifactCleanup", unfinished, cleanupExpiredDirectoryArtifacts(), { deleted: 0, store: "none" }),
|
|
76534
|
+
settleWithinTickBudget("siteExtractArtifactCleanup", unfinished, cleanupExpiredSiteExtractArtifacts(), { deleted: 0, store: "none" }),
|
|
76535
|
+
settleWithinTickBudget("siteExtractRedispatch", unfinished, redispatchFundedSiteExtractJobs(10), { checked: 0, dispatched: 0, failed: 0 }),
|
|
76536
|
+
settleWithinTickBudget("siteExtractReconcile", unfinished, reconcileSiteExtractSettlements(10), { checked: 0, settled: 0, failed: 0 }),
|
|
76537
|
+
settleWithinTickBudget("paaRedispatch", unfinished, redispatchPendingPaaHarvests(10), { checked: 0, dispatched: 0, failed: 0 }),
|
|
76538
|
+
settleWithinTickBudget("paaReconcile", unfinished, reconcilePaaHarvestSettlements(10), { checked: 0, staleFailed: 0, settled: 0, failed: 0 }),
|
|
76539
|
+
settleWithinTickBudget("localSourcebookRedispatch", unfinished, redispatchQueuedLocalSourcebookAcquisitions(5), { checked: 0, dispatched: 0, failed: 0 }),
|
|
76540
|
+
settleWithinTickBudget("analyticsRollup", unfinished, refreshAnalyticsDailyRollupsIfDue(), { status: "failed", rows: 0 }),
|
|
76541
|
+
settleWithinTickBudget("analyticsForms", unfinished, deliverPendingAnalyticsForms(10), { claimed: 0, delivered: 0, deferred: 0 }),
|
|
76542
|
+
settleWithinTickBudget("workflowDispatch", unfinished, dispatchDueWorkflowSchedules(origin), { dispatched: 0, status: "skipped_deadline" }),
|
|
76543
|
+
settleWithinTickBudget("scheduledRunReconcile", unfinished, reconcileScheduledRunSettlements(25), { checked: 0, settled: 0, chargedMc: 0, failed: 0 }),
|
|
76544
|
+
settleWithinTickBudget("retentionSweep", unfinished, sweepRetention(), { deleted: 0, perTable: {}, truncated: false })
|
|
75888
76545
|
]);
|
|
75889
76546
|
return c.json({
|
|
76547
|
+
durationMs: Date.now() - startedAt,
|
|
76548
|
+
tickBudgetMs: CRON_TICK_BUDGET_MS,
|
|
76549
|
+
unfinished,
|
|
76550
|
+
complete: unfinished.length === 0,
|
|
76551
|
+
scheduledRunReconciliation,
|
|
76552
|
+
retentionSweep,
|
|
75890
76553
|
drained: results.length,
|
|
75891
76554
|
results,
|
|
75892
76555
|
sweepResult,
|