mcp-scraper 0.41.0 → 0.42.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 +2 -2
- package/dist/bin/api-server.cjs +4331 -3081
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- 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 +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +2821 -2309
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +9 -6
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-6J57U6HA.js → chunk-4MFUKI4X.js} +2 -2
- package/dist/{chunk-SIE5LZ2V.js → chunk-6O6DPKLX.js} +2983 -2450
- package/dist/chunk-6O6DPKLX.js.map +1 -0
- package/dist/{chunk-QZXKQB7Y.js → chunk-6Q7V4IQV.js} +2 -2
- package/dist/{chunk-EHES33KB.js → chunk-DAS7L4SF.js} +2 -2
- package/dist/{chunk-JVIW4GK2.js → chunk-DH42LIR3.js} +2 -2
- package/dist/{chunk-XELSA2MS.js → chunk-ENJQ2NSX.js} +2 -2
- package/dist/{chunk-XELSA2MS.js.map → chunk-ENJQ2NSX.js.map} +1 -1
- package/dist/{chunk-IQTT7CAB.js → chunk-FCFQ634B.js} +28 -2
- package/dist/chunk-FCFQ634B.js.map +1 -0
- package/dist/{chunk-345BQXZH.js → chunk-MJKVDLVR.js} +18 -13
- package/dist/chunk-MJKVDLVR.js.map +1 -0
- package/dist/{chunk-27DUCRAZ.js → chunk-QOWBJ4YY.js} +2 -2
- package/dist/chunk-S5OVG2MK.js +7 -0
- package/dist/chunk-S5OVG2MK.js.map +1 -0
- package/dist/{chunk-CS4HE6QY.js → chunk-ZBQ6EXZW.js} +2 -2
- package/dist/{db-LVVU6NOK.js → db-C5ELI55I.js} +2 -2
- package/dist/{extract-bundle-SKXEDG4Z.js → extract-bundle-DMZ63HZU.js} +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{location-data-repository-NBTCOA7B.js → location-data-repository-SHEOIBTL.js} +3 -3
- package/dist/{server-2JJPCZH4.js → server-MVCGZ4OK.js} +1160 -520
- package/dist/server-MVCGZ4OK.js.map +1 -0
- package/dist/{site-extract-repository-6GX72U4N.js → site-extract-repository-B42SIGAZ.js} +3 -3
- package/dist/{worker-5RWLFSAJ.js → worker-4R3UWQZS.js} +5 -5
- package/package.json +1 -1
- package/dist/chunk-345BQXZH.js.map +0 -1
- package/dist/chunk-IQTT7CAB.js.map +0 -1
- package/dist/chunk-OOB35KFT.js +0 -7
- package/dist/chunk-OOB35KFT.js.map +0 -1
- package/dist/chunk-SIE5LZ2V.js.map +0 -1
- package/dist/server-2JJPCZH4.js.map +0 -1
- /package/dist/{chunk-6J57U6HA.js.map → chunk-4MFUKI4X.js.map} +0 -0
- /package/dist/{chunk-QZXKQB7Y.js.map → chunk-6Q7V4IQV.js.map} +0 -0
- /package/dist/{chunk-EHES33KB.js.map → chunk-DAS7L4SF.js.map} +0 -0
- /package/dist/{chunk-JVIW4GK2.js.map → chunk-DH42LIR3.js.map} +0 -0
- /package/dist/{chunk-27DUCRAZ.js.map → chunk-QOWBJ4YY.js.map} +0 -0
- /package/dist/{chunk-CS4HE6QY.js.map → chunk-ZBQ6EXZW.js.map} +0 -0
- /package/dist/{db-LVVU6NOK.js.map → db-C5ELI55I.js.map} +0 -0
- /package/dist/{extract-bundle-SKXEDG4Z.js.map → extract-bundle-DMZ63HZU.js.map} +0 -0
- /package/dist/{location-data-repository-NBTCOA7B.js.map → location-data-repository-SHEOIBTL.js.map} +0 -0
- /package/dist/{site-extract-repository-6GX72U4N.js.map → site-extract-repository-B42SIGAZ.js.map} +0 -0
- /package/dist/{worker-5RWLFSAJ.js.map → worker-4R3UWQZS.js.map} +0 -0
|
@@ -3,20 +3,20 @@ import {
|
|
|
3
3
|
cleanupExpiredSiteExtractArtifacts,
|
|
4
4
|
harvestPageMedia,
|
|
5
5
|
renewSiteExtractArtifactDownload
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-6Q7V4IQV.js";
|
|
7
7
|
import {
|
|
8
8
|
REQUIRED_LOCATION_STATE_CODES,
|
|
9
9
|
importHostedCensusPlacesCsv,
|
|
10
10
|
importHostedZipGroupsCsv,
|
|
11
11
|
listActiveHostedLocationDatasets
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QOWBJ4YY.js";
|
|
13
13
|
import {
|
|
14
14
|
classifyHarvestProblem,
|
|
15
15
|
createHarvestAttemptRecorder,
|
|
16
16
|
deliverWebhook,
|
|
17
17
|
harvestProblemResponse,
|
|
18
18
|
serializeHarvestProblem
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ZBQ6EXZW.js";
|
|
20
20
|
import {
|
|
21
21
|
AIModeSelectors,
|
|
22
22
|
AIOverviewSelectors,
|
|
@@ -40,11 +40,23 @@ import {
|
|
|
40
40
|
resolveBackupKernelProxyId,
|
|
41
41
|
resolveKernelProxyId,
|
|
42
42
|
serpArrivalUrl
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-DH42LIR3.js";
|
|
44
44
|
import {
|
|
45
|
+
ApiErrorResponseSchema,
|
|
46
|
+
ArtifactTemplateConfigSchema,
|
|
47
|
+
CreateArtifactTemplateInputSchema,
|
|
45
48
|
CreateEditorialReadingRoomInputSchema,
|
|
49
|
+
CreateScheduledRunViewLinkInputSchema,
|
|
50
|
+
EditorialReadingRoomV1InputSchema,
|
|
51
|
+
GetScheduledRunResponseSchema,
|
|
46
52
|
HttpMcpToolExecutor,
|
|
53
|
+
InternalScheduledArtifactRenderRequestSchema,
|
|
54
|
+
ListScheduledRunsQuerySchema,
|
|
55
|
+
PublicScheduledRunTokenSchema,
|
|
47
56
|
RenewEditorialReadingRoomDownloadInputSchema,
|
|
57
|
+
SCHEDULED_ARTIFACT_TEMPLATE_PRESET,
|
|
58
|
+
ScheduledResultsMcpExecutor,
|
|
59
|
+
UpdateArtifactTemplateInputSchema,
|
|
48
60
|
WIZPER_LANGUAGES,
|
|
49
61
|
WaybackInventoryOptionsSchema,
|
|
50
62
|
WaybackTimelineSchema,
|
|
@@ -63,11 +75,12 @@ import {
|
|
|
63
75
|
outputBaseDir,
|
|
64
76
|
registerBrowserAgentMcpTools,
|
|
65
77
|
registerMemoryMcpTools,
|
|
78
|
+
registerScheduledResultsMcpTools,
|
|
66
79
|
registerSerpIntelligenceCaptureTools,
|
|
67
80
|
renewConnectedDataArtifactDownload,
|
|
68
81
|
renewDirectoryArtifactDownload,
|
|
69
82
|
transcribeMediaUrl
|
|
70
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-6O6DPKLX.js";
|
|
71
84
|
import {
|
|
72
85
|
auditImageUrls,
|
|
73
86
|
auditImages,
|
|
@@ -75,9 +88,10 @@ import {
|
|
|
75
88
|
computeIssues,
|
|
76
89
|
createPrivateArtifact,
|
|
77
90
|
isPrivateIpAddress,
|
|
91
|
+
readPrivateArtifactWindow,
|
|
78
92
|
renewPrivateArtifactDownload,
|
|
79
93
|
validatePublicHttpUrl
|
|
80
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-MJKVDLVR.js";
|
|
81
95
|
import {
|
|
82
96
|
sanitizeAttempts,
|
|
83
97
|
sanitizeHarvestResult,
|
|
@@ -92,7 +106,7 @@ import {
|
|
|
92
106
|
recordKernelSession,
|
|
93
107
|
recordVendorUsage,
|
|
94
108
|
runWithCostContext
|
|
95
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-DAS7L4SF.js";
|
|
96
110
|
import {
|
|
97
111
|
listWorkflowDefinitions,
|
|
98
112
|
runWorkflow,
|
|
@@ -119,7 +133,7 @@ import {
|
|
|
119
133
|
RawMapsOverviewSchema,
|
|
120
134
|
RawMapsReviewStatsSchema
|
|
121
135
|
} from "./chunk-CB5C3BPB.js";
|
|
122
|
-
import "./chunk-
|
|
136
|
+
import "./chunk-S5OVG2MK.js";
|
|
123
137
|
import {
|
|
124
138
|
abandonExtractSettlement,
|
|
125
139
|
countSuccessfulPages,
|
|
@@ -142,7 +156,7 @@ import {
|
|
|
142
156
|
setExtractJobTotal,
|
|
143
157
|
settleExtractJob,
|
|
144
158
|
terminalExtractJobStatus
|
|
145
|
-
} from "./chunk-
|
|
159
|
+
} from "./chunk-4MFUKI4X.js";
|
|
146
160
|
import {
|
|
147
161
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
148
162
|
CONCURRENCY_PRICE_ID,
|
|
@@ -298,7 +312,7 @@ import {
|
|
|
298
312
|
startHarvestAttempt,
|
|
299
313
|
upsertPageSnapshot,
|
|
300
314
|
verifyPassword
|
|
301
|
-
} from "./chunk-
|
|
315
|
+
} from "./chunk-FCFQ634B.js";
|
|
302
316
|
|
|
303
317
|
// src/blog/registry.ts
|
|
304
318
|
var posts = [
|
|
@@ -3615,7 +3629,7 @@ async function generateOgImage(post) {
|
|
|
3615
3629
|
|
|
3616
3630
|
// src/api/server.ts
|
|
3617
3631
|
import { Resend as Resend3 } from "resend";
|
|
3618
|
-
import { randomUUID as
|
|
3632
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
3619
3633
|
|
|
3620
3634
|
// src/api/kpo-extractor.ts
|
|
3621
3635
|
import TurndownService from "turndown";
|
|
@@ -6236,7 +6250,7 @@ async function extractSite(opts) {
|
|
|
6236
6250
|
}
|
|
6237
6251
|
|
|
6238
6252
|
// src/api/server.ts
|
|
6239
|
-
import { Hono as
|
|
6253
|
+
import { Hono as Hono33 } from "hono";
|
|
6240
6254
|
import { serve as serveInngest } from "inngest/hono";
|
|
6241
6255
|
|
|
6242
6256
|
// src/inngest/client.ts
|
|
@@ -8644,7 +8658,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
8644
8658
|
});
|
|
8645
8659
|
}) : null;
|
|
8646
8660
|
const finalized = await step.run("finalize", async () => {
|
|
8647
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
8661
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-DMZ63HZU.js");
|
|
8648
8662
|
const current = await getExtractJob(jobId2);
|
|
8649
8663
|
if (!current) throw new Error("extract job disappeared before finalization");
|
|
8650
8664
|
const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
|
|
@@ -9586,7 +9600,7 @@ async function resolveUsZipsPath(requestedPath) {
|
|
|
9586
9600
|
async function loadZipGroups(stateAbbr, requestedPath, warnings, sourceMode) {
|
|
9587
9601
|
const mode = resolvedLocationDataMode({ zipSource: sourceMode });
|
|
9588
9602
|
if (mode === "hosted") {
|
|
9589
|
-
const { getHostedZipGroups } = await import("./location-data-repository-
|
|
9603
|
+
const { getHostedZipGroups } = await import("./location-data-repository-SHEOIBTL.js");
|
|
9590
9604
|
const hosted = await getHostedZipGroups(stateAbbr);
|
|
9591
9605
|
if (!hosted.dataset) {
|
|
9592
9606
|
throw new DirectoryLocationError(
|
|
@@ -9649,7 +9663,7 @@ async function resolveDirectoryMarkets(options, config = {}) {
|
|
|
9649
9663
|
const sourceUrl = censusStateUrl(state.fips);
|
|
9650
9664
|
const warnings = [];
|
|
9651
9665
|
if (resolvedLocationDataMode(config) === "hosted") {
|
|
9652
|
-
const { queryHostedLocationMarkets } = await import("./location-data-repository-
|
|
9666
|
+
const { queryHostedLocationMarkets } = await import("./location-data-repository-SHEOIBTL.js");
|
|
9653
9667
|
const hosted = await queryHostedLocationMarkets({
|
|
9654
9668
|
state: state.abbr,
|
|
9655
9669
|
minPopulation: options.minPopulation,
|
|
@@ -12635,12 +12649,12 @@ function normalizeIdempotencyKey(value) {
|
|
|
12635
12649
|
function tokenFor(id, idempotencyKey2) {
|
|
12636
12650
|
return createHmac("sha256", billingSecret()).update(id).update(":").update(idempotencyKey2).digest("base64url");
|
|
12637
12651
|
}
|
|
12638
|
-
function tokenHash(
|
|
12639
|
-
return createHash3("sha256").update(
|
|
12652
|
+
function tokenHash(token3) {
|
|
12653
|
+
return createHash3("sha256").update(token3).digest("hex");
|
|
12640
12654
|
}
|
|
12641
|
-
function verifyToken(row,
|
|
12655
|
+
function verifyToken(row, token3) {
|
|
12642
12656
|
const expected = Buffer.from(row.token_hash, "hex");
|
|
12643
|
-
const actual = Buffer.from(tokenHash(
|
|
12657
|
+
const actual = Buffer.from(tokenHash(token3), "hex");
|
|
12644
12658
|
if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
|
|
12645
12659
|
throw new UnifiedBillingError("unauthorized", "invalid billing authorization token", 401);
|
|
12646
12660
|
}
|
|
@@ -12757,11 +12771,11 @@ async function authorizeScheduledRun(args) {
|
|
|
12757
12771
|
);
|
|
12758
12772
|
}
|
|
12759
12773
|
const id = randomUUID3();
|
|
12760
|
-
const
|
|
12774
|
+
const token3 = tokenFor(id, idempotencyKey2);
|
|
12761
12775
|
const expiresAt = new Date(Date.now() + AUTHORIZATION_TTL_MS).toISOString();
|
|
12762
12776
|
const inserted = await getDb().execute({
|
|
12763
12777
|
sql: "INSERT OR IGNORE INTO billing_authorizations (id, user_id, idempotency_key, billing_class, status, minimum_amount_mc, maximum_amount_mc, token_hash, metadata, expires_at) VALUES (?, ?, ?, ?, 'authorized', ?, ?, ?, ?, ?)",
|
|
12764
|
-
args: [id, user.id, idempotencyKey2, SCHEDULED_RUN_BILLING_CLASS, SCHEDULED_RUN_BASE_MC, maximumMc, tokenHash(
|
|
12778
|
+
args: [id, user.id, idempotencyKey2, SCHEDULED_RUN_BILLING_CLASS, SCHEDULED_RUN_BASE_MC, maximumMc, tokenHash(token3), safeMetadata2(args.metadata), expiresAt]
|
|
12765
12779
|
});
|
|
12766
12780
|
if (inserted.rowsAffected === 0) {
|
|
12767
12781
|
const raced = await getDb().execute({
|
|
@@ -12777,7 +12791,7 @@ async function authorizeScheduledRun(args) {
|
|
|
12777
12791
|
}
|
|
12778
12792
|
return {
|
|
12779
12793
|
id,
|
|
12780
|
-
token:
|
|
12794
|
+
token: token3,
|
|
12781
12795
|
status: "authorized",
|
|
12782
12796
|
minimumMc: SCHEDULED_RUN_BASE_MC,
|
|
12783
12797
|
maximumMc,
|
|
@@ -13047,11 +13061,11 @@ function signSession(userId) {
|
|
|
13047
13061
|
const sig = createHmac2("sha256", secret()).update(payload).digest("hex");
|
|
13048
13062
|
return `${payload}.${sig}`;
|
|
13049
13063
|
}
|
|
13050
|
-
function verifySession(
|
|
13051
|
-
const dot =
|
|
13064
|
+
function verifySession(token3) {
|
|
13065
|
+
const dot = token3.lastIndexOf(".");
|
|
13052
13066
|
if (dot === -1) return null;
|
|
13053
|
-
const payload =
|
|
13054
|
-
const sig =
|
|
13067
|
+
const payload = token3.slice(0, dot);
|
|
13068
|
+
const sig = token3.slice(dot + 1);
|
|
13055
13069
|
const expected = createHmac2("sha256", secret()).update(payload).digest("hex");
|
|
13056
13070
|
if (!safeEqualHex(sig, expected)) return null;
|
|
13057
13071
|
const id = parseInt(payload);
|
|
@@ -14380,9 +14394,9 @@ var YouTubeExtractor = class {
|
|
|
14380
14394
|
}
|
|
14381
14395
|
async fetchContinuation(page, endpoint, contToken, context) {
|
|
14382
14396
|
const result = await page.evaluate(
|
|
14383
|
-
async ({ ep, token:
|
|
14397
|
+
async ({ ep, token: token3, ctx }) => {
|
|
14384
14398
|
const body = {
|
|
14385
|
-
continuation:
|
|
14399
|
+
continuation: token3,
|
|
14386
14400
|
context: {
|
|
14387
14401
|
client: {
|
|
14388
14402
|
clientName: ctx.clientName,
|
|
@@ -16261,7 +16275,7 @@ function advertiserTokenOverlap(query, candidate) {
|
|
|
16261
16275
|
const candidateTokens = new Set(normalizeAdvertiserName(candidate).split(" ").filter(Boolean));
|
|
16262
16276
|
if (queryTokens.size === 0 || candidateTokens.size === 0) return 0;
|
|
16263
16277
|
let overlap = 0;
|
|
16264
|
-
for (const
|
|
16278
|
+
for (const token3 of queryTokens) if (candidateTokens.has(token3)) overlap++;
|
|
16265
16279
|
return overlap / queryTokens.size;
|
|
16266
16280
|
}
|
|
16267
16281
|
function assessPageIntelMatch(body, result) {
|
|
@@ -22638,8 +22652,8 @@ function protectedResourceMetadata() {
|
|
|
22638
22652
|
function wwwAuthenticateValue() {
|
|
22639
22653
|
return `Bearer resource_metadata="${oauthResourceBaseUrl()}/.well-known/oauth-protected-resource"`;
|
|
22640
22654
|
}
|
|
22641
|
-
function isBearerJwt(
|
|
22642
|
-
return !
|
|
22655
|
+
function isBearerJwt(token3) {
|
|
22656
|
+
return !token3.startsWith("sk_") && token3.split(".").length === 3;
|
|
22643
22657
|
}
|
|
22644
22658
|
var jwksSingleton = null;
|
|
22645
22659
|
function getJwks() {
|
|
@@ -22647,8 +22661,8 @@ function getJwks() {
|
|
|
22647
22661
|
jwksSingleton = createRemoteJWKSet(new URL(`${oauthIssuer()}/.well-known/jwks.json`));
|
|
22648
22662
|
return jwksSingleton;
|
|
22649
22663
|
}
|
|
22650
|
-
async function verifyScraperOAuthJwt(
|
|
22651
|
-
const { payload } = await jwtVerify(
|
|
22664
|
+
async function verifyScraperOAuthJwt(token3) {
|
|
22665
|
+
const { payload } = await jwtVerify(token3, getJwks(), {
|
|
22652
22666
|
issuer: oauthIssuer(),
|
|
22653
22667
|
audience: oauthResourceUrl()
|
|
22654
22668
|
});
|
|
@@ -22718,6 +22732,7 @@ mcpApp.all("/", async (c) => {
|
|
|
22718
22732
|
const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false, ownerId: hashOwnerId(callerKey) });
|
|
22719
22733
|
registerSerpIntelligenceCaptureTools(server, executor);
|
|
22720
22734
|
registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl, savesReportsLocally: false });
|
|
22735
|
+
registerScheduledResultsMcpTools(server, new ScheduledResultsMcpExecutor(baseUrl, callerKey));
|
|
22721
22736
|
const memoryExecutor = new HybridMemoryToolExecutor(new MemoryMcpToolExecutor(baseUrl, callerKey), user);
|
|
22722
22737
|
registerMemoryMcpTools(server, memoryExecutor);
|
|
22723
22738
|
await server.connect(transport);
|
|
@@ -25351,9 +25366,9 @@ function negotiateScope(requested, user, resource) {
|
|
|
25351
25366
|
if (operator && !unique2.includes("memory.admin")) unique2.push("memory.admin");
|
|
25352
25367
|
return unique2.join(" ");
|
|
25353
25368
|
}
|
|
25354
|
-
async function sessionUser(
|
|
25355
|
-
if (!
|
|
25356
|
-
const userId = verifySession(
|
|
25369
|
+
async function sessionUser(token3) {
|
|
25370
|
+
if (!token3) return null;
|
|
25371
|
+
const userId = verifySession(token3);
|
|
25357
25372
|
if (!userId) return null;
|
|
25358
25373
|
const user = await getUserById(userId);
|
|
25359
25374
|
return user ?? null;
|
|
@@ -25700,35 +25715,35 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
25700
25715
|
// src/api/chat-routes.ts
|
|
25701
25716
|
import { Hono as Hono24 } from "hono";
|
|
25702
25717
|
var chatApp = new Hono24();
|
|
25703
|
-
function tokenOk(
|
|
25704
|
-
return !!
|
|
25718
|
+
function tokenOk(token3) {
|
|
25719
|
+
return !!token3 && token3.startsWith("mk_") && token3.length > 10;
|
|
25705
25720
|
}
|
|
25706
25721
|
function jsonError(message, status = 400) {
|
|
25707
25722
|
return new Response(JSON.stringify({ ok: false, error: message }), { status, headers: { "content-type": "application/json" } });
|
|
25708
25723
|
}
|
|
25709
25724
|
chatApp.get("/api/channels", async (c) => {
|
|
25710
|
-
const
|
|
25711
|
-
if (!tokenOk(
|
|
25712
|
-
const r = await memoryCall("listVaultsTool", {},
|
|
25725
|
+
const token3 = c.req.query("token");
|
|
25726
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25727
|
+
const r = await memoryCall("listVaultsTool", {}, token3);
|
|
25713
25728
|
if (!r.ok) return c.json(r);
|
|
25714
25729
|
const vaults = (r.vaults ?? []).filter((v) => v.kind === "channel");
|
|
25715
25730
|
return c.json({ ok: true, channels: vaults });
|
|
25716
25731
|
});
|
|
25717
25732
|
chatApp.get("/api/messages", async (c) => {
|
|
25718
|
-
const
|
|
25719
|
-
if (!tokenOk(
|
|
25733
|
+
const token3 = c.req.query("token");
|
|
25734
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25720
25735
|
const vault = c.req.query("vault");
|
|
25721
25736
|
if (!vault) return jsonError("vault is required");
|
|
25722
25737
|
const parentMessageId = c.req.query("parentMessageId") || void 0;
|
|
25723
|
-
const r = await memoryCall("listChannelMessagesTool", { vault, parentMessageId },
|
|
25738
|
+
const r = await memoryCall("listChannelMessagesTool", { vault, parentMessageId }, token3);
|
|
25724
25739
|
return c.json(r);
|
|
25725
25740
|
});
|
|
25726
25741
|
chatApp.get("/api/poll", async (c) => {
|
|
25727
|
-
const
|
|
25728
|
-
if (!tokenOk(
|
|
25742
|
+
const token3 = c.req.query("token");
|
|
25743
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25729
25744
|
const vault = c.req.query("vault");
|
|
25730
25745
|
if (!vault) return jsonError("vault is required");
|
|
25731
|
-
const r = await memoryCall("pollChannelTool", { vault },
|
|
25746
|
+
const r = await memoryCall("pollChannelTool", { vault }, token3);
|
|
25732
25747
|
return c.json(r);
|
|
25733
25748
|
});
|
|
25734
25749
|
function parseAttachNote(v) {
|
|
@@ -25753,12 +25768,12 @@ chatApp.post("/api/reply", async (c) => {
|
|
|
25753
25768
|
return c.json(r);
|
|
25754
25769
|
});
|
|
25755
25770
|
chatApp.get("/api/notes-own", async (c) => {
|
|
25756
|
-
const
|
|
25757
|
-
if (!tokenOk(
|
|
25758
|
-
const vaultsRes = await memoryCall("listVaultsTool", {},
|
|
25771
|
+
const token3 = c.req.query("token");
|
|
25772
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25773
|
+
const vaultsRes = await memoryCall("listVaultsTool", {}, token3);
|
|
25759
25774
|
if (!vaultsRes.ok) return c.json(vaultsRes);
|
|
25760
25775
|
const ownVaults = (vaultsRes.vaults ?? []).filter((v) => v.role === "owner" && v.kind === "notes");
|
|
25761
|
-
const lists = await Promise.all(ownVaults.map((v) => memoryCall("listTool", { vault: v.handle },
|
|
25776
|
+
const lists = await Promise.all(ownVaults.map((v) => memoryCall("listTool", { vault: v.handle }, token3)));
|
|
25762
25777
|
const notes = ownVaults.flatMap((v, i) => {
|
|
25763
25778
|
const r = lists[i];
|
|
25764
25779
|
return r.ok ? (r.notes ?? []).map((n) => ({ vault: v.handle, path: n.path, title: n.title })) : [];
|
|
@@ -25766,12 +25781,12 @@ chatApp.get("/api/notes-own", async (c) => {
|
|
|
25766
25781
|
return c.json({ ok: true, notes });
|
|
25767
25782
|
});
|
|
25768
25783
|
chatApp.get("/api/message-note", async (c) => {
|
|
25769
|
-
const
|
|
25770
|
-
if (!tokenOk(
|
|
25784
|
+
const token3 = c.req.query("token");
|
|
25785
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25771
25786
|
const vault = c.req.query("vault");
|
|
25772
25787
|
const messageId = c.req.query("messageId");
|
|
25773
25788
|
if (!vault || !messageId) return jsonError("vault and messageId are required");
|
|
25774
|
-
const r = await memoryCall("getMessageNoteTool", { vault, messageId },
|
|
25789
|
+
const r = await memoryCall("getMessageNoteTool", { vault, messageId }, token3);
|
|
25775
25790
|
return c.json(r);
|
|
25776
25791
|
});
|
|
25777
25792
|
chatApp.post("/api/react", async (c) => {
|
|
@@ -25782,17 +25797,17 @@ chatApp.post("/api/react", async (c) => {
|
|
|
25782
25797
|
return c.json(r);
|
|
25783
25798
|
});
|
|
25784
25799
|
chatApp.get("/api/mentions", async (c) => {
|
|
25785
|
-
const
|
|
25786
|
-
if (!tokenOk(
|
|
25787
|
-
const r = await memoryCall("myMentionsTool", {},
|
|
25800
|
+
const token3 = c.req.query("token");
|
|
25801
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25802
|
+
const r = await memoryCall("myMentionsTool", {}, token3);
|
|
25788
25803
|
return c.json(r);
|
|
25789
25804
|
});
|
|
25790
25805
|
chatApp.get("/api/members", async (c) => {
|
|
25791
|
-
const
|
|
25792
|
-
if (!tokenOk(
|
|
25806
|
+
const token3 = c.req.query("token");
|
|
25807
|
+
if (!tokenOk(token3)) return jsonError("missing or malformed token", 401);
|
|
25793
25808
|
const vault = c.req.query("vault");
|
|
25794
25809
|
if (!vault) return jsonError("vault is required");
|
|
25795
|
-
const r = await memoryCall("listChannelMembersTool", { vault },
|
|
25810
|
+
const r = await memoryCall("listChannelMembersTool", { vault }, token3);
|
|
25796
25811
|
return c.json(r);
|
|
25797
25812
|
});
|
|
25798
25813
|
chatApp.post("/api/channel", async (c) => {
|
|
@@ -25828,13 +25843,13 @@ chatApp.post("/api/member-remove", async (c) => {
|
|
|
25828
25843
|
return c.json(r);
|
|
25829
25844
|
});
|
|
25830
25845
|
chatApp.get("/:token", (c) => {
|
|
25831
|
-
const
|
|
25832
|
-
if (!tokenOk(
|
|
25846
|
+
const token3 = c.req.param("token");
|
|
25847
|
+
if (!tokenOk(token3)) {
|
|
25833
25848
|
return c.html(`<!doctype html><html><body style="font-family:system-ui;padding:40px;color:#3D3548"><h2>This chat link isn't valid.</h2><p>Ask the AI to run get-chat-link again, or revoke-chat-link then get-chat-link if you think it was compromised.</p></body></html>`, 401);
|
|
25834
25849
|
}
|
|
25835
|
-
return c.html(renderChatPage(
|
|
25850
|
+
return c.html(renderChatPage(token3));
|
|
25836
25851
|
});
|
|
25837
|
-
function renderChatPage(
|
|
25852
|
+
function renderChatPage(token3) {
|
|
25838
25853
|
return `<!doctype html>
|
|
25839
25854
|
<html>
|
|
25840
25855
|
<head>
|
|
@@ -26019,7 +26034,7 @@ function renderChatPage(token2) {
|
|
|
26019
26034
|
</div>
|
|
26020
26035
|
|
|
26021
26036
|
<script>
|
|
26022
|
-
const TOKEN = ${JSON.stringify(
|
|
26037
|
+
const TOKEN = ${JSON.stringify(token3)};
|
|
26023
26038
|
const state = { channels: [], activeVault: null, activeName: null, members: [], threadParent: null, pollTimer: null, pendingAttach: null, threadPendingAttach: null, ownNotes: null, pickerTarget: 'main' };
|
|
26024
26039
|
|
|
26025
26040
|
function api(path, opts) {
|
|
@@ -26389,8 +26404,8 @@ var MAX_VAULT_RECORDS = 200;
|
|
|
26389
26404
|
var MAX_MEMORY_READ_CONCURRENCY = 4;
|
|
26390
26405
|
var MAX_MEMORY_READ_ATTEMPTS = 3;
|
|
26391
26406
|
var vaultApp = new Hono25();
|
|
26392
|
-
function validToken(
|
|
26393
|
-
return !!
|
|
26407
|
+
function validToken(token3) {
|
|
26408
|
+
return !!token3 && token3.startsWith("mk_") && token3.length > 20 && token3.length < 256;
|
|
26394
26409
|
}
|
|
26395
26410
|
function isVault(value) {
|
|
26396
26411
|
return typeof value === "string" && VAULTS.includes(value);
|
|
@@ -26516,11 +26531,11 @@ function shouldRetryMemoryRead(error) {
|
|
|
26516
26531
|
function sleep(ms) {
|
|
26517
26532
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
26518
26533
|
}
|
|
26519
|
-
async function readMemory(tool, args,
|
|
26520
|
-
let result = await memoryCall(tool, args,
|
|
26534
|
+
async function readMemory(tool, args, token3) {
|
|
26535
|
+
let result = await memoryCall(tool, args, token3);
|
|
26521
26536
|
for (let attempt = 1; !result.ok && attempt < MAX_MEMORY_READ_ATTEMPTS && shouldRetryMemoryRead(result.error); attempt += 1) {
|
|
26522
26537
|
await sleep(attempt * 150);
|
|
26523
|
-
result = await memoryCall(tool, args,
|
|
26538
|
+
result = await memoryCall(tool, args, token3);
|
|
26524
26539
|
}
|
|
26525
26540
|
return result;
|
|
26526
26541
|
}
|
|
@@ -26537,27 +26552,27 @@ async function mapWithConcurrency3(items, limit, map) {
|
|
|
26537
26552
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
26538
26553
|
return values;
|
|
26539
26554
|
}
|
|
26540
|
-
async function readVault(vault,
|
|
26541
|
-
const list = await readMemory("listTool", { vault },
|
|
26555
|
+
async function readVault(vault, token3) {
|
|
26556
|
+
const list = await readMemory("listTool", { vault }, token3);
|
|
26542
26557
|
if (!list.ok) throw new Error(list.error ?? `unable to read ${vault}`);
|
|
26543
26558
|
const noteRefs = (list.notes ?? []).slice(0, MAX_VAULT_RECORDS);
|
|
26544
26559
|
const notes = await mapWithConcurrency3(
|
|
26545
26560
|
noteRefs,
|
|
26546
26561
|
MAX_MEMORY_READ_CONCURRENCY,
|
|
26547
|
-
({ path: path5 }) => readMemory("getTool", { vault, path: path5 },
|
|
26562
|
+
({ path: path5 }) => readMemory("getTool", { vault, path: path5 }, token3)
|
|
26548
26563
|
);
|
|
26549
26564
|
const failed = notes.find((result) => !result.ok);
|
|
26550
26565
|
if (failed && !failed.ok) throw new Error(failed.error ?? `unable to read ${vault} record`);
|
|
26551
26566
|
return notes.filter((result) => result.ok && !!result.note).map((result) => workspaceRecord(vault, result.note));
|
|
26552
26567
|
}
|
|
26553
26568
|
vaultApp.get("/api/workspace", async (c) => {
|
|
26554
|
-
const
|
|
26555
|
-
if (!validToken(
|
|
26569
|
+
const token3 = c.req.query("token");
|
|
26570
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26556
26571
|
try {
|
|
26557
26572
|
const requested = c.req.query("vault");
|
|
26558
26573
|
const vaults = isVault(requested) ? [requested] : VAULTS;
|
|
26559
26574
|
const results = [];
|
|
26560
|
-
for (const vault of vaults) results.push(await readVault(vault,
|
|
26575
|
+
for (const vault of vaults) results.push(await readVault(vault, token3));
|
|
26561
26576
|
const records = results.flat().sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
26562
26577
|
return c.json({ ok: true, records, limited: results.some((result) => result.length >= 200) });
|
|
26563
26578
|
} catch (error) {
|
|
@@ -26566,8 +26581,8 @@ vaultApp.get("/api/workspace", async (c) => {
|
|
|
26566
26581
|
});
|
|
26567
26582
|
vaultApp.post("/api/records", async (c) => {
|
|
26568
26583
|
const body = await c.req.json().catch(() => ({}));
|
|
26569
|
-
const
|
|
26570
|
-
if (!validToken(
|
|
26584
|
+
const token3 = typeof body.token === "string" ? body.token : void 0;
|
|
26585
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26571
26586
|
const vault = body.vault;
|
|
26572
26587
|
const path5 = stringValue(body.path, 320);
|
|
26573
26588
|
const title = stringValue(body.title, 320);
|
|
@@ -26578,19 +26593,19 @@ vaultApp.post("/api/records", async (c) => {
|
|
|
26578
26593
|
const validationError = validateNewRecord(vault, normalizedProps);
|
|
26579
26594
|
if (validationError) return c.json({ ok: false, error: validationError }, 400);
|
|
26580
26595
|
const baseRevision = typeof body.baseRevision === "number" && Number.isInteger(body.baseRevision) ? body.baseRevision : void 0;
|
|
26581
|
-
const result = await memoryCall("putTool", { vault, path: path5, title, content: content || title, props: normalizedProps, ...baseRevision === void 0 ? {} : { baseRevision } },
|
|
26596
|
+
const result = await memoryCall("putTool", { vault, path: path5, title, content: content || title, props: normalizedProps, ...baseRevision === void 0 ? {} : { baseRevision } }, token3);
|
|
26582
26597
|
return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
|
|
26583
26598
|
});
|
|
26584
26599
|
vaultApp.post("/api/records/transition", async (c) => {
|
|
26585
26600
|
const body = await c.req.json().catch(() => ({}));
|
|
26586
|
-
const
|
|
26587
|
-
if (!validToken(
|
|
26601
|
+
const token3 = typeof body.token === "string" ? body.token : void 0;
|
|
26602
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26588
26603
|
const vault = typeof body.vault === "string" ? body.vault : "";
|
|
26589
26604
|
const path5 = typeof body.path === "string" ? body.path : "";
|
|
26590
26605
|
const status = stringValue(body.status, 80);
|
|
26591
26606
|
const baseRevision = typeof body.baseRevision === "number" && Number.isInteger(body.baseRevision) ? body.baseRevision : void 0;
|
|
26592
26607
|
if (!isVault(vault) || !path5 || !status) return c.json({ ok: false, error: "valid vault, path, and status are required" }, 400);
|
|
26593
|
-
const current = await readMemory("getTool", { vault, path: path5 },
|
|
26608
|
+
const current = await readMemory("getTool", { vault, path: path5 }, token3);
|
|
26594
26609
|
if (!current.ok || !current.note) return c.json(current, current.ok ? 404 : 502);
|
|
26595
26610
|
const result = await memoryCall("putTool", {
|
|
26596
26611
|
vault,
|
|
@@ -26599,13 +26614,13 @@ vaultApp.post("/api/records/transition", async (c) => {
|
|
|
26599
26614
|
content: current.note.content,
|
|
26600
26615
|
props: { ...current.note.props ?? {}, status },
|
|
26601
26616
|
baseRevision: baseRevision ?? current.note.revision
|
|
26602
|
-
},
|
|
26617
|
+
}, token3);
|
|
26603
26618
|
return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
|
|
26604
26619
|
});
|
|
26605
26620
|
vaultApp.post("/api/records/update", async (c) => {
|
|
26606
26621
|
const body = await c.req.json().catch(() => ({}));
|
|
26607
|
-
const
|
|
26608
|
-
if (!validToken(
|
|
26622
|
+
const token3 = typeof body.token === "string" ? body.token : void 0;
|
|
26623
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26609
26624
|
const vault = body.vault;
|
|
26610
26625
|
const path5 = stringValue(body.path, 320);
|
|
26611
26626
|
const title = typeof body.title === "string" ? stringValue(body.title, 320) : void 0;
|
|
@@ -26615,7 +26630,7 @@ vaultApp.post("/api/records/update", async (c) => {
|
|
|
26615
26630
|
if (!isVault(vault) || !path5 || body.title !== void 0 && !title || body.props !== void 0 && !props) {
|
|
26616
26631
|
return c.json({ ok: false, error: "valid vault, path, and optional record fields are required" }, 400);
|
|
26617
26632
|
}
|
|
26618
|
-
const current = await readMemory("getTool", { vault, path: path5 },
|
|
26633
|
+
const current = await readMemory("getTool", { vault, path: path5 }, token3);
|
|
26619
26634
|
if (!current.ok || !current.note) return c.json(current, current.ok ? 404 : 502);
|
|
26620
26635
|
const nextProps = normalizeRelationshipProps(vault, { ...current.note.props ?? {}, ...props ?? {} });
|
|
26621
26636
|
const validationError = validateNewRecord(vault, nextProps);
|
|
@@ -26627,18 +26642,18 @@ vaultApp.post("/api/records/update", async (c) => {
|
|
|
26627
26642
|
content: content ?? current.note.content,
|
|
26628
26643
|
props: nextProps,
|
|
26629
26644
|
baseRevision: baseRevision ?? current.note.revision
|
|
26630
|
-
},
|
|
26645
|
+
}, token3);
|
|
26631
26646
|
return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
|
|
26632
26647
|
});
|
|
26633
26648
|
vaultApp.delete("/api/records", async (c) => {
|
|
26634
26649
|
const body = await c.req.json().catch(() => ({}));
|
|
26635
|
-
const
|
|
26636
|
-
if (!validToken(
|
|
26650
|
+
const token3 = typeof body.token === "string" ? body.token : void 0;
|
|
26651
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26637
26652
|
const vault = body.vault;
|
|
26638
26653
|
const path5 = stringValue(body.path, 320);
|
|
26639
26654
|
const baseRevision = typeof body.baseRevision === "number" && Number.isInteger(body.baseRevision) ? body.baseRevision : void 0;
|
|
26640
26655
|
if (vault !== "Tasks" || !path5) return c.json({ ok: false, error: "only a valid task record can be deleted" }, 400);
|
|
26641
|
-
const current = await readMemory("getTool", { vault, path: path5 },
|
|
26656
|
+
const current = await readMemory("getTool", { vault, path: path5 }, token3);
|
|
26642
26657
|
if (!current.ok || !current.note) return c.json(current, current.ok ? 404 : 502);
|
|
26643
26658
|
if (!["task", "milestone"].includes(typeof current.note.props?.type === "string" ? current.note.props.type : defaultType("Tasks"))) {
|
|
26644
26659
|
return c.json({ ok: false, error: "only task records can be deleted" }, 400);
|
|
@@ -26647,13 +26662,13 @@ vaultApp.delete("/api/records", async (c) => {
|
|
|
26647
26662
|
vault,
|
|
26648
26663
|
path: path5,
|
|
26649
26664
|
baseRevision: baseRevision ?? current.note.revision
|
|
26650
|
-
},
|
|
26665
|
+
}, token3);
|
|
26651
26666
|
return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
|
|
26652
26667
|
});
|
|
26653
26668
|
vaultApp.post("/api/communications/review", async (c) => {
|
|
26654
26669
|
const body = await c.req.json().catch(() => ({}));
|
|
26655
|
-
const
|
|
26656
|
-
if (!validToken(
|
|
26670
|
+
const token3 = typeof body.token === "string" ? body.token : void 0;
|
|
26671
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26657
26672
|
const path5 = stringValue(body.path, 320);
|
|
26658
26673
|
const action = stringValue(body.action, 24);
|
|
26659
26674
|
const title = typeof body.title === "string" ? stringValue(body.title, 320) : void 0;
|
|
@@ -26664,7 +26679,7 @@ vaultApp.post("/api/communications/review", async (c) => {
|
|
|
26664
26679
|
if (!path5 || !normalizedAction || !["approve", "deny", "edit"].includes(normalizedAction) || body.title !== void 0 && !title) {
|
|
26665
26680
|
return c.json({ ok: false, error: "valid path, action, and optional draft fields are required" }, 400);
|
|
26666
26681
|
}
|
|
26667
|
-
const current = await readMemory("getTool", { vault: "Communications", path: path5 },
|
|
26682
|
+
const current = await readMemory("getTool", { vault: "Communications", path: path5 }, token3);
|
|
26668
26683
|
if (!current.ok || !current.note) return c.json(current, current.ok ? 404 : 502);
|
|
26669
26684
|
if (current.note.props?.type !== "email") return c.json({ ok: false, error: "only email communications can be reviewed" }, 400);
|
|
26670
26685
|
const reviewed = normalizedAction === "approve" || normalizedAction === "deny";
|
|
@@ -26688,13 +26703,13 @@ vaultApp.post("/api/communications/review", async (c) => {
|
|
|
26688
26703
|
content: content ?? current.note.content,
|
|
26689
26704
|
props,
|
|
26690
26705
|
baseRevision: baseRevision ?? current.note.revision
|
|
26691
|
-
},
|
|
26706
|
+
}, token3);
|
|
26692
26707
|
return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
|
|
26693
26708
|
});
|
|
26694
26709
|
vaultApp.post("/api/activity", async (c) => {
|
|
26695
26710
|
const body = await c.req.json().catch(() => ({}));
|
|
26696
|
-
const
|
|
26697
|
-
if (!validToken(
|
|
26711
|
+
const token3 = typeof body.token === "string" ? body.token : void 0;
|
|
26712
|
+
if (!validToken(token3)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
|
|
26698
26713
|
const title = stringValue(body.title, 320);
|
|
26699
26714
|
const content = typeof body.content === "string" && body.content.length <= 2e4 ? body.content : "";
|
|
26700
26715
|
const props = propsValue(body.props) ?? {};
|
|
@@ -26704,7 +26719,7 @@ vaultApp.post("/api/activity", async (c) => {
|
|
|
26704
26719
|
return c.json({ ok: false, error: "Activities require at least one canonical People reference in person_refs" }, 400);
|
|
26705
26720
|
}
|
|
26706
26721
|
for (const ref of personRefs) {
|
|
26707
|
-
const person = await readMemory("getTool", { vault: "People", path: ref.replace(/^People::/, "") },
|
|
26722
|
+
const person = await readMemory("getTool", { vault: "People", path: ref.replace(/^People::/, "") }, token3);
|
|
26708
26723
|
if (!person.ok || !person.note || !["person", "organization"].includes(String(person.note.props?.type ?? ""))) {
|
|
26709
26724
|
return c.json({ ok: false, error: `Activity person reference is not a known People record: ${ref}` }, 400);
|
|
26710
26725
|
}
|
|
@@ -26732,37 +26747,37 @@ vaultApp.post("/api/activity", async (c) => {
|
|
|
26732
26747
|
...props,
|
|
26733
26748
|
person_refs: personRefs
|
|
26734
26749
|
}
|
|
26735
|
-
},
|
|
26750
|
+
}, token3);
|
|
26736
26751
|
return c.json(result, result.ok ? 200 : 502);
|
|
26737
26752
|
});
|
|
26738
26753
|
|
|
26739
26754
|
// src/api/schedule-routes.ts
|
|
26740
26755
|
import { Hono as Hono26 } from "hono";
|
|
26741
26756
|
var scheduleApp = new Hono26();
|
|
26742
|
-
function tokenOk2(
|
|
26743
|
-
return !!
|
|
26757
|
+
function tokenOk2(token3) {
|
|
26758
|
+
return !!token3 && token3.startsWith("mk_") && token3.length > 10;
|
|
26744
26759
|
}
|
|
26745
26760
|
function jsonError2(message, status = 400) {
|
|
26746
26761
|
return new Response(JSON.stringify({ ok: false, error: message }), { status, headers: { "content-type": "application/json" } });
|
|
26747
26762
|
}
|
|
26748
26763
|
scheduleApp.get("/api/status", async (c) => {
|
|
26749
|
-
const
|
|
26750
|
-
if (!tokenOk2(
|
|
26751
|
-
const r = await memoryCall("getScheduleStatusTool", {},
|
|
26764
|
+
const token3 = c.req.query("token");
|
|
26765
|
+
if (!tokenOk2(token3)) return jsonError2("missing or malformed token", 401);
|
|
26766
|
+
const r = await memoryCall("getScheduleStatusTool", {}, token3);
|
|
26752
26767
|
return c.json(r);
|
|
26753
26768
|
});
|
|
26754
26769
|
scheduleApp.get("/api/vaults", async (c) => {
|
|
26755
|
-
const
|
|
26756
|
-
if (!tokenOk2(
|
|
26757
|
-
const r = await memoryCall("listVaultsTool", {},
|
|
26770
|
+
const token3 = c.req.query("token");
|
|
26771
|
+
if (!tokenOk2(token3)) return jsonError2("missing or malformed token", 401);
|
|
26772
|
+
const r = await memoryCall("listVaultsTool", {}, token3);
|
|
26758
26773
|
if (!r.ok) return c.json(r);
|
|
26759
26774
|
const vaults = (r.vaults ?? []).filter((v) => v.kind !== "channel");
|
|
26760
26775
|
return c.json({ ok: true, vaults });
|
|
26761
26776
|
});
|
|
26762
26777
|
scheduleApp.get("/api/actions", async (c) => {
|
|
26763
|
-
const
|
|
26764
|
-
if (!tokenOk2(
|
|
26765
|
-
const r = await memoryCall("listScheduledActionsTool", {},
|
|
26778
|
+
const token3 = c.req.query("token");
|
|
26779
|
+
if (!tokenOk2(token3)) return jsonError2("missing or malformed token", 401);
|
|
26780
|
+
const r = await memoryCall("listScheduledActionsTool", {}, token3);
|
|
26766
26781
|
return c.json(r);
|
|
26767
26782
|
});
|
|
26768
26783
|
scheduleApp.post("/api/propose", async (c) => {
|
|
@@ -26798,13 +26813,13 @@ scheduleApp.post("/api/actions/:id/delete", async (c) => {
|
|
|
26798
26813
|
return c.json(r);
|
|
26799
26814
|
});
|
|
26800
26815
|
scheduleApp.get("/:token", (c) => {
|
|
26801
|
-
const
|
|
26802
|
-
if (!tokenOk2(
|
|
26816
|
+
const token3 = c.req.param("token");
|
|
26817
|
+
if (!tokenOk2(token3)) {
|
|
26803
26818
|
return c.html(`<!doctype html><html><body style="font-family:system-ui;padding:40px;color:#3D3548"><h2>This automations link isn't valid.</h2><p>Ask the AI to run get-schedule-link again, or revoke-schedule-link then get-schedule-link if you think it was compromised.</p></body></html>`, 401);
|
|
26804
26819
|
}
|
|
26805
|
-
return c.html(renderSchedulePage(
|
|
26820
|
+
return c.html(renderSchedulePage(token3));
|
|
26806
26821
|
});
|
|
26807
|
-
function renderSchedulePage(
|
|
26822
|
+
function renderSchedulePage(token3) {
|
|
26808
26823
|
return `<!doctype html>
|
|
26809
26824
|
<html>
|
|
26810
26825
|
<head>
|
|
@@ -26902,7 +26917,7 @@ function renderSchedulePage(token2) {
|
|
|
26902
26917
|
</div>
|
|
26903
26918
|
|
|
26904
26919
|
<script>
|
|
26905
|
-
const TOKEN = ${JSON.stringify(
|
|
26920
|
+
const TOKEN = ${JSON.stringify(token3)};
|
|
26906
26921
|
const state = { actions: [], vaults: [] };
|
|
26907
26922
|
|
|
26908
26923
|
function api(path, opts) { return fetch('/schedule' + path, opts).then(r => r.json()); }
|
|
@@ -27145,8 +27160,8 @@ function supportThreadToken(customerEmail, normalizedSubject) {
|
|
|
27145
27160
|
return shortHash(`${customerEmail.trim().toLowerCase()}
|
|
27146
27161
|
${normalizeSupportSubject(normalizedSubject).toLowerCase()}`);
|
|
27147
27162
|
}
|
|
27148
|
-
function supportThreadAddress(
|
|
27149
|
-
const safeToken =
|
|
27163
|
+
function supportThreadAddress(token3, receivingDomain) {
|
|
27164
|
+
const safeToken = token3.toLowerCase().replace(/[^a-f0-9]/g, "").slice(0, 24);
|
|
27150
27165
|
const domain = receivingDomain.trim().toLowerCase();
|
|
27151
27166
|
if (!safeToken || !/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(domain)) throw new Error("invalid support thread address");
|
|
27152
27167
|
return `support+${safeToken}@${domain}`;
|
|
@@ -27173,8 +27188,8 @@ function personPath(identity) {
|
|
|
27173
27188
|
const local = identity.email.split("@")[0];
|
|
27174
27189
|
return `contacts/${safePathPart(identity.name === identity.email ? local : identity.name)}-${shortHash(identity.email, 12)}`;
|
|
27175
27190
|
}
|
|
27176
|
-
function issuePath(
|
|
27177
|
-
return `support/threads/${
|
|
27191
|
+
function issuePath(token3) {
|
|
27192
|
+
return `support/threads/${token3}`;
|
|
27178
27193
|
}
|
|
27179
27194
|
function communicationPath(email) {
|
|
27180
27195
|
const date = cleanLine(email.created_at, (/* @__PURE__ */ new Date()).toISOString()).slice(0, 10).replace(/[^0-9-]/g, "");
|
|
@@ -27358,8 +27373,8 @@ function buildIssueContent(email, customer, personPathValue, threadAddressValue)
|
|
|
27358
27373
|
`- Person: ${noteRef("People", personPathValue)}`
|
|
27359
27374
|
].join("\n");
|
|
27360
27375
|
}
|
|
27361
|
-
async function findOrCreateIssue(memoryKey, email, customer, personPathValue,
|
|
27362
|
-
const path5 = issuePath(
|
|
27376
|
+
async function findOrCreateIssue(memoryKey, email, customer, personPathValue, token3, threadAddressValue) {
|
|
27377
|
+
const path5 = issuePath(token3);
|
|
27363
27378
|
const existing = await getNote(memoryKey, "Issues", path5);
|
|
27364
27379
|
if (existing) return existing;
|
|
27365
27380
|
const subject = normalizeSupportSubject(email.subject);
|
|
@@ -27369,7 +27384,7 @@ async function findOrCreateIssue(memoryKey, email, customer, personPathValue, to
|
|
|
27369
27384
|
path: path5,
|
|
27370
27385
|
title: `Support thread \u2014 ${subject} \u2014 ${customer.email}`.slice(0, 240),
|
|
27371
27386
|
content: buildIssueContent(email, customer, personPathValue, threadAddressValue),
|
|
27372
|
-
source: `resend:thread:${
|
|
27387
|
+
source: `resend:thread:${token3}`,
|
|
27373
27388
|
type: "user_friction",
|
|
27374
27389
|
tagCandidates: ISSUE_TAGS,
|
|
27375
27390
|
props: {
|
|
@@ -27378,7 +27393,7 @@ async function findOrCreateIssue(memoryKey, email, customer, personPathValue, to
|
|
|
27378
27393
|
summary,
|
|
27379
27394
|
pinned: false,
|
|
27380
27395
|
source_type: "thread",
|
|
27381
|
-
source_ref: `resend:thread:${
|
|
27396
|
+
source_ref: `resend:thread:${token3}`,
|
|
27382
27397
|
related: [],
|
|
27383
27398
|
related_vault_notes: [noteRef("People", personPathValue)],
|
|
27384
27399
|
embed: true,
|
|
@@ -27393,7 +27408,7 @@ async function findOrCreateIssue(memoryKey, email, customer, personPathValue, to
|
|
|
27393
27408
|
customer_email: customer.email,
|
|
27394
27409
|
customer_name: customer.name,
|
|
27395
27410
|
normalized_subject: subject,
|
|
27396
|
-
thread_token:
|
|
27411
|
+
thread_token: token3,
|
|
27397
27412
|
thread_address: threadAddressValue,
|
|
27398
27413
|
communication_refs: [],
|
|
27399
27414
|
message_count: 0,
|
|
@@ -27436,7 +27451,7 @@ function buildCommunicationContent(email, customer, direction, personPathValue,
|
|
|
27436
27451
|
...parentPath ? [`- Previous message: [[${parentPath}]]`] : []
|
|
27437
27452
|
].join("\n");
|
|
27438
27453
|
}
|
|
27439
|
-
async function captureCommunication(memoryKey, email, customer, direction, personPathValue, issuePathValue,
|
|
27454
|
+
async function captureCommunication(memoryKey, email, customer, direction, personPathValue, issuePathValue, token3, parentPath) {
|
|
27440
27455
|
const path5 = communicationPath(email);
|
|
27441
27456
|
const existing = await getNote(memoryKey, "Communication", path5);
|
|
27442
27457
|
if (existing) return existing;
|
|
@@ -27475,8 +27490,8 @@ async function captureCommunication(memoryKey, email, customer, direction, perso
|
|
|
27475
27490
|
provider: "resend",
|
|
27476
27491
|
provider_connection_ref: "resend:updates.mcpscraper.dev",
|
|
27477
27492
|
provider_message_ref: providerMessageRef,
|
|
27478
|
-
provider_thread_ref:
|
|
27479
|
-
thread_ref:
|
|
27493
|
+
provider_thread_ref: token3,
|
|
27494
|
+
thread_ref: token3,
|
|
27480
27495
|
issue_ref: issuePathValue,
|
|
27481
27496
|
sender_email: supportSender(email)?.email ?? email.from,
|
|
27482
27497
|
recipient_emails: email.to ?? [],
|
|
@@ -27536,8 +27551,8 @@ async function updateIssueThread(memoryKey, issuePathValue, email, communication
|
|
|
27536
27551
|
if (!result.conflict || attempt === 1) throw new Error(result.error ?? "support thread issue update failed");
|
|
27537
27552
|
}
|
|
27538
27553
|
}
|
|
27539
|
-
async function getSupportThreadByToken(memoryKey,
|
|
27540
|
-
const safeToken =
|
|
27554
|
+
async function getSupportThreadByToken(memoryKey, token3) {
|
|
27555
|
+
const safeToken = token3.toLowerCase().replace(/[^a-f0-9]/g, "").slice(0, 24);
|
|
27541
27556
|
if (safeToken.length !== 24) return null;
|
|
27542
27557
|
const path5 = issuePath(safeToken);
|
|
27543
27558
|
const issue = await getNote(memoryKey, "Issues", path5);
|
|
@@ -27565,8 +27580,8 @@ async function captureSupportMessage(memoryKey, email, options) {
|
|
|
27565
27580
|
const customer = options.customer ?? supportSender(email);
|
|
27566
27581
|
if (!customer) throw new Error("support sender does not contain a usable email address");
|
|
27567
27582
|
const normalizedSubject = options.existingThread?.normalizedSubject ?? normalizeSupportSubject(email.subject);
|
|
27568
|
-
const
|
|
27569
|
-
const threadAddressValue = options.existingThread?.threadAddress ?? supportThreadAddress(
|
|
27583
|
+
const token3 = options.existingThread?.threadToken ?? supportThreadToken(customer.email, normalizedSubject);
|
|
27584
|
+
const threadAddressValue = options.existingThread?.threadAddress ?? supportThreadAddress(token3, options.receivingDomain);
|
|
27570
27585
|
const person = options.existingThread ? await getNote(memoryKey, "People", options.existingThread.personPath) : await findOrCreatePerson(memoryKey, customer, email.created_at);
|
|
27571
27586
|
if (!person) throw new Error("support person could not be resolved");
|
|
27572
27587
|
const issue = options.existingThread?.issue ?? await findOrCreateIssue(
|
|
@@ -27574,7 +27589,7 @@ async function captureSupportMessage(memoryKey, email, options) {
|
|
|
27574
27589
|
email,
|
|
27575
27590
|
customer,
|
|
27576
27591
|
person.path,
|
|
27577
|
-
|
|
27592
|
+
token3,
|
|
27578
27593
|
threadAddressValue
|
|
27579
27594
|
);
|
|
27580
27595
|
const parentPath = typeof issue.props?.last_communication_ref === "string" ? issue.props.last_communication_ref : void 0;
|
|
@@ -27585,7 +27600,7 @@ async function captureSupportMessage(memoryKey, email, options) {
|
|
|
27585
27600
|
options.direction,
|
|
27586
27601
|
person.path,
|
|
27587
27602
|
issue.path,
|
|
27588
|
-
|
|
27603
|
+
token3,
|
|
27589
27604
|
parentPath
|
|
27590
27605
|
);
|
|
27591
27606
|
await updateIssueThread(memoryKey, issue.path, email, communication.path, options.direction, person.path);
|
|
@@ -27594,7 +27609,7 @@ async function captureSupportMessage(memoryKey, email, options) {
|
|
|
27594
27609
|
communicationPath: communication.path,
|
|
27595
27610
|
personPath: person.path,
|
|
27596
27611
|
parentCommunicationPath: parentPath,
|
|
27597
|
-
threadToken:
|
|
27612
|
+
threadToken: token3,
|
|
27598
27613
|
threadAddress: threadAddressValue,
|
|
27599
27614
|
customerEmail: customer.email,
|
|
27600
27615
|
normalizedSubject,
|
|
@@ -27767,8 +27782,8 @@ resendInboundApp.post("/webhooks", async (c) => {
|
|
|
27767
27782
|
threadAddress: captured.threadAddress
|
|
27768
27783
|
});
|
|
27769
27784
|
}
|
|
27770
|
-
const
|
|
27771
|
-
const thread =
|
|
27785
|
+
const token3 = extractSupportThreadToken(receivedEvent.data.to ?? [], ...domains);
|
|
27786
|
+
const thread = token3 ? await getSupportThreadByToken(memoryKey, token3) : null;
|
|
27772
27787
|
if (!thread) return c.json({ ok: true, route, ignored: true, reason: "unknown-support-thread" });
|
|
27773
27788
|
const operator = parseMailbox(destination);
|
|
27774
27789
|
if (operator && sender.email === operator.email) {
|
|
@@ -28749,6 +28764,88 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
|
|
|
28749
28764
|
warnings
|
|
28750
28765
|
};
|
|
28751
28766
|
}
|
|
28767
|
+
function markdownLinkLabel(value) {
|
|
28768
|
+
return value.replace(/[[\]\\]/g, "\\$&");
|
|
28769
|
+
}
|
|
28770
|
+
function scheduledSourceRef(value) {
|
|
28771
|
+
if (/(?:access_token|auth(?:orization)?|signature|sig|token|key)=/i.test(value)) {
|
|
28772
|
+
throw new Error("Editorial reading-room source references cannot contain credentials or authorization URLs.");
|
|
28773
|
+
}
|
|
28774
|
+
const safe = safeUrl(value);
|
|
28775
|
+
return safe ? `[${markdownLinkLabel(value)}](${safe})` : `\`${value.replace(/`/g, "\\`")}\``;
|
|
28776
|
+
}
|
|
28777
|
+
function scheduledThemeStyles(config) {
|
|
28778
|
+
const palette = config.theme === "ink" ? ":root{--paper:#151719;--ink:#f5f1e8;--muted:#bbb4a7;--rule:#3b3e42;--accent:#e8a85a}" : config.theme === "warm" ? ":root{--paper:#fff7e8;--ink:#30251c;--muted:#735f4d;--rule:#dfccb4;--accent:#a4492d}" : ":root{--paper:#f8f5ed;--ink:#20211f;--muted:#66645e;--rule:#d6d0c3;--accent:#2f5d50}";
|
|
28779
|
+
const density = config.density === "compact" ? "article p,article li{line-height:1.48}.article-shell{gap:1rem}" : "article p,article li{line-height:1.72}";
|
|
28780
|
+
const sourceRail = config.showSourceRail ? "" : ".article-source{display:none!important}";
|
|
28781
|
+
const generatedAt = config.showGeneratedAt ? "" : "#generated-at{display:none!important}";
|
|
28782
|
+
return `<style id="scheduled-reading-room-config">${palette}${density}${sourceRail}${generatedAt}</style>`;
|
|
28783
|
+
}
|
|
28784
|
+
function renderEditorialReadingRoomV1(input, config, now = /* @__PURE__ */ new Date()) {
|
|
28785
|
+
const filesMarkdown = (input.files ?? []).length === 0 ? "" : [
|
|
28786
|
+
"## Shared files",
|
|
28787
|
+
"",
|
|
28788
|
+
...(input.files ?? []).map((file) => {
|
|
28789
|
+
const detail = [
|
|
28790
|
+
file.sharedBy ? `shared by ${file.sharedBy}` : "",
|
|
28791
|
+
file.sharedAt ? `at ${file.sharedAt}` : "",
|
|
28792
|
+
file.mimeType ? `(${file.mimeType})` : ""
|
|
28793
|
+
].filter(Boolean).join(" ");
|
|
28794
|
+
return `- **${file.label.replace(/\*/g, "\\*")}**${detail ? ` \u2014 ${detail}` : ""}: ${scheduledSourceRef(file.sourceRef)}`;
|
|
28795
|
+
})
|
|
28796
|
+
].join("\n");
|
|
28797
|
+
const siteSlug = slugify2(`${config.brandName ?? input.generatedFor}-${input.title}`) || "scheduled-reading-room";
|
|
28798
|
+
const legacyInput = {
|
|
28799
|
+
site: {
|
|
28800
|
+
slug: siteSlug,
|
|
28801
|
+
title: input.title,
|
|
28802
|
+
product: config.brandName ?? input.generatedFor,
|
|
28803
|
+
edition: "Scheduled result",
|
|
28804
|
+
editionLabel: "Generated report",
|
|
28805
|
+
issueLabel: config.showGeneratedAt ? now.toISOString() : "Current result",
|
|
28806
|
+
eyebrow: "A source-labelled scheduled result",
|
|
28807
|
+
heroTitle: input.title,
|
|
28808
|
+
startLabel: "Start reading"
|
|
28809
|
+
},
|
|
28810
|
+
deck: input.dek ?? `Prepared for ${input.generatedFor}.`,
|
|
28811
|
+
articles: input.sections.map((section, index) => {
|
|
28812
|
+
const provenance = [
|
|
28813
|
+
"",
|
|
28814
|
+
"## Sources",
|
|
28815
|
+
"",
|
|
28816
|
+
...section.sourceLabels.map((label) => `- ${label}`),
|
|
28817
|
+
...section.sourceRefs.map((ref) => `- ${scheduledSourceRef(ref)}`)
|
|
28818
|
+
].join("\n");
|
|
28819
|
+
const withFiles = index === input.sections.length - 1 && filesMarkdown ? `${section.markdown}
|
|
28820
|
+
|
|
28821
|
+
${provenance}
|
|
28822
|
+
|
|
28823
|
+
${filesMarkdown}` : `${section.markdown}
|
|
28824
|
+
|
|
28825
|
+
${provenance}`;
|
|
28826
|
+
return {
|
|
28827
|
+
slug: section.id,
|
|
28828
|
+
category: "Report",
|
|
28829
|
+
kicker: section.summary ?? "Scheduled result",
|
|
28830
|
+
order: index + 1,
|
|
28831
|
+
title: section.title,
|
|
28832
|
+
summary: section.summary ?? section.title,
|
|
28833
|
+
sourceType: "Scheduled result",
|
|
28834
|
+
sourceLabel: section.sourceLabels.join(" \xB7 ").slice(0, 500),
|
|
28835
|
+
markdown: withFiles
|
|
28836
|
+
};
|
|
28837
|
+
}),
|
|
28838
|
+
filename: `${siteSlug}.html`
|
|
28839
|
+
};
|
|
28840
|
+
const rendered = renderEditorialReadingRoom(legacyInput, now);
|
|
28841
|
+
const html = rendered.html.replace("</head>", `${scheduledThemeStyles(config)}</head>`).replace("<body", `<body data-scheduled-renderer="editorial_reading_room_v1" data-theme="${config.theme}" data-density="${config.density}"`);
|
|
28842
|
+
return {
|
|
28843
|
+
...rendered,
|
|
28844
|
+
html,
|
|
28845
|
+
bytes: Buffer.byteLength(html),
|
|
28846
|
+
sha256: createHash10("sha256").update(html).digest("hex")
|
|
28847
|
+
};
|
|
28848
|
+
}
|
|
28752
28849
|
|
|
28753
28850
|
// src/editorial-reading-room/artifacts.ts
|
|
28754
28851
|
var EDITORIAL_READING_ROOM_ARTIFACT_PREFIX = "editorial-reading-rooms/";
|
|
@@ -28864,6 +28961,878 @@ editorialReadingRoomApp.post("/renew-download", async (c) => {
|
|
|
28864
28961
|
});
|
|
28865
28962
|
});
|
|
28866
28963
|
|
|
28964
|
+
// src/api/scheduled-result-routes.ts
|
|
28965
|
+
import { Hono as Hono30 } from "hono";
|
|
28966
|
+
import { z as z30 } from "zod";
|
|
28967
|
+
|
|
28968
|
+
// src/api/scheduled-artifact-owner.ts
|
|
28969
|
+
import { createHash as createHash11 } from "crypto";
|
|
28970
|
+
function scheduledArtifactOwnerIdForApiKey(apiKey) {
|
|
28971
|
+
return createHash11("sha256").update(apiKey).digest("hex").slice(0, 24);
|
|
28972
|
+
}
|
|
28973
|
+
|
|
28974
|
+
// src/api/scheduled-run-view-links.ts
|
|
28975
|
+
import { createHash as createHash12, randomBytes as randomBytes3, randomUUID as randomUUID13 } from "crypto";
|
|
28976
|
+
var schemaReady2 = false;
|
|
28977
|
+
async function ensureScheduledRunViewLinksSchema() {
|
|
28978
|
+
if (schemaReady2) return;
|
|
28979
|
+
const db = getDb();
|
|
28980
|
+
await db.execute(`
|
|
28981
|
+
CREATE TABLE IF NOT EXISTS scheduled_run_view_links (
|
|
28982
|
+
share_id TEXT PRIMARY KEY,
|
|
28983
|
+
owner_id TEXT NOT NULL,
|
|
28984
|
+
run_id TEXT NOT NULL,
|
|
28985
|
+
artifact_id TEXT NOT NULL,
|
|
28986
|
+
title TEXT NOT NULL,
|
|
28987
|
+
summary TEXT NOT NULL,
|
|
28988
|
+
renderer_key TEXT NOT NULL CHECK (renderer_key = 'editorial_reading_room_v1'),
|
|
28989
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
28990
|
+
expires_at TEXT NOT NULL,
|
|
28991
|
+
revoked_at TEXT,
|
|
28992
|
+
created_at TEXT NOT NULL
|
|
28993
|
+
)
|
|
28994
|
+
`);
|
|
28995
|
+
await db.execute(`
|
|
28996
|
+
CREATE INDEX IF NOT EXISTS scheduled_run_view_links_owner_run
|
|
28997
|
+
ON scheduled_run_view_links(owner_id, run_id, created_at DESC)
|
|
28998
|
+
`);
|
|
28999
|
+
await db.execute(`
|
|
29000
|
+
CREATE INDEX IF NOT EXISTS scheduled_run_view_links_expiry
|
|
29001
|
+
ON scheduled_run_view_links(expires_at, revoked_at)
|
|
29002
|
+
`);
|
|
29003
|
+
schemaReady2 = true;
|
|
29004
|
+
}
|
|
29005
|
+
function tokenHash2(token3) {
|
|
29006
|
+
return createHash12("sha256").update(token3).digest("hex");
|
|
29007
|
+
}
|
|
29008
|
+
function mapRow(row) {
|
|
29009
|
+
return {
|
|
29010
|
+
shareId: String(row.share_id),
|
|
29011
|
+
ownerId: String(row.owner_id),
|
|
29012
|
+
runId: String(row.run_id),
|
|
29013
|
+
artifactId: String(row.artifact_id),
|
|
29014
|
+
title: String(row.title),
|
|
29015
|
+
summary: String(row.summary),
|
|
29016
|
+
rendererKey: "editorial_reading_room_v1",
|
|
29017
|
+
tokenHash: String(row.token_hash),
|
|
29018
|
+
expiresAt: String(row.expires_at),
|
|
29019
|
+
revokedAt: row.revoked_at == null ? null : String(row.revoked_at),
|
|
29020
|
+
createdAt: String(row.created_at)
|
|
29021
|
+
};
|
|
29022
|
+
}
|
|
29023
|
+
async function createScheduledRunViewLink(input) {
|
|
29024
|
+
await ensureScheduledRunViewLinksSchema();
|
|
29025
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
29026
|
+
const token3 = randomBytes3(32).toString("base64url");
|
|
29027
|
+
const record = {
|
|
29028
|
+
shareId: randomUUID13(),
|
|
29029
|
+
ownerId: input.ownerId,
|
|
29030
|
+
runId: input.runId,
|
|
29031
|
+
artifactId: input.artifactId,
|
|
29032
|
+
title: input.title.slice(0, 300),
|
|
29033
|
+
summary: input.summary.slice(0, 2e3),
|
|
29034
|
+
rendererKey: input.rendererKey,
|
|
29035
|
+
tokenHash: tokenHash2(token3),
|
|
29036
|
+
expiresAt: new Date(now.getTime() + input.expiresInDays * 24 * 60 * 60 * 1e3).toISOString(),
|
|
29037
|
+
revokedAt: null,
|
|
29038
|
+
createdAt: now.toISOString()
|
|
29039
|
+
};
|
|
29040
|
+
await getDb().execute({
|
|
29041
|
+
sql: `
|
|
29042
|
+
INSERT INTO scheduled_run_view_links (
|
|
29043
|
+
share_id, owner_id, run_id, artifact_id, title, summary,
|
|
29044
|
+
renderer_key, token_hash, expires_at, revoked_at, created_at
|
|
29045
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)
|
|
29046
|
+
`,
|
|
29047
|
+
args: [
|
|
29048
|
+
record.shareId,
|
|
29049
|
+
record.ownerId,
|
|
29050
|
+
record.runId,
|
|
29051
|
+
record.artifactId,
|
|
29052
|
+
record.title,
|
|
29053
|
+
record.summary,
|
|
29054
|
+
record.rendererKey,
|
|
29055
|
+
record.tokenHash,
|
|
29056
|
+
record.expiresAt,
|
|
29057
|
+
record.createdAt
|
|
29058
|
+
]
|
|
29059
|
+
});
|
|
29060
|
+
return { record, token: token3 };
|
|
29061
|
+
}
|
|
29062
|
+
async function resolveScheduledRunViewLink(token3, now = /* @__PURE__ */ new Date()) {
|
|
29063
|
+
await ensureScheduledRunViewLinksSchema();
|
|
29064
|
+
const result = await getDb().execute({
|
|
29065
|
+
sql: `
|
|
29066
|
+
SELECT share_id, owner_id, run_id, artifact_id, title, summary,
|
|
29067
|
+
renderer_key, token_hash, expires_at, revoked_at, created_at
|
|
29068
|
+
FROM scheduled_run_view_links
|
|
29069
|
+
WHERE token_hash = ?
|
|
29070
|
+
AND revoked_at IS NULL
|
|
29071
|
+
AND expires_at > ?
|
|
29072
|
+
LIMIT 1
|
|
29073
|
+
`,
|
|
29074
|
+
args: [tokenHash2(token3), now.toISOString()]
|
|
29075
|
+
});
|
|
29076
|
+
const row = result.rows[0];
|
|
29077
|
+
return row ? mapRow(row) : null;
|
|
29078
|
+
}
|
|
29079
|
+
async function revokeScheduledRunViewLink(ownerId, runId, shareId, now = /* @__PURE__ */ new Date()) {
|
|
29080
|
+
await ensureScheduledRunViewLinksSchema();
|
|
29081
|
+
const result = await getDb().execute({
|
|
29082
|
+
sql: `
|
|
29083
|
+
UPDATE scheduled_run_view_links
|
|
29084
|
+
SET revoked_at = COALESCE(revoked_at, ?)
|
|
29085
|
+
WHERE share_id = ? AND owner_id = ? AND run_id = ?
|
|
29086
|
+
`,
|
|
29087
|
+
args: [now.toISOString(), shareId, ownerId, runId]
|
|
29088
|
+
});
|
|
29089
|
+
return result.rowsAffected > 0;
|
|
29090
|
+
}
|
|
29091
|
+
|
|
29092
|
+
// src/api/scheduled-result-routes.ts
|
|
29093
|
+
var TemplateParamsSchema = z30.object({ templateId: z30.string().uuid() }).strict();
|
|
29094
|
+
var RunParamsSchema = z30.object({ runId: z30.string().min(1).max(200) }).strict();
|
|
29095
|
+
var ViewLinkParamsSchema = z30.object({
|
|
29096
|
+
runId: z30.string().min(1).max(200),
|
|
29097
|
+
shareId: z30.string().uuid()
|
|
29098
|
+
}).strict();
|
|
29099
|
+
function apiError(error, errorCode = "invalid_request") {
|
|
29100
|
+
return { ok: false, error, errorCode };
|
|
29101
|
+
}
|
|
29102
|
+
async function memoryContext(c) {
|
|
29103
|
+
const user = c.get("user");
|
|
29104
|
+
const { key, error } = await getOrCreateUserMemoryKey(user);
|
|
29105
|
+
return { user, key, error };
|
|
29106
|
+
}
|
|
29107
|
+
function memoryStatus(result) {
|
|
29108
|
+
if (result.errorCode === "not_found") return 404;
|
|
29109
|
+
if (result.errorCode === "conflict" || result.errorCode === "template_archived") return 409;
|
|
29110
|
+
if (result.errorCode === "invalid_request") return 400;
|
|
29111
|
+
return 502;
|
|
29112
|
+
}
|
|
29113
|
+
function safeMemoryFailure(result) {
|
|
29114
|
+
const errorCode = typeof result.errorCode === "string" ? result.errorCode : "internal_error";
|
|
29115
|
+
const messages = {
|
|
29116
|
+
invalid_request: "The scheduled-results request is invalid.",
|
|
29117
|
+
not_found: "The requested scheduled-results record was not found.",
|
|
29118
|
+
conflict: "The scheduled-results request conflicts with the current record.",
|
|
29119
|
+
template_archived: "The selected artifact template is archived."
|
|
29120
|
+
};
|
|
29121
|
+
return apiError(
|
|
29122
|
+
messages[errorCode] ?? "Scheduled results are temporarily unavailable.",
|
|
29123
|
+
errorCode in messages ? errorCode : "internal_error"
|
|
29124
|
+
);
|
|
29125
|
+
}
|
|
29126
|
+
var scheduledResultApp = new Hono30();
|
|
29127
|
+
scheduledResultApp.use("/artifact-templates", createApiKeyAuth());
|
|
29128
|
+
scheduledResultApp.use("/artifact-templates/*", createApiKeyAuth());
|
|
29129
|
+
scheduledResultApp.use("/schedule-runs", createApiKeyAuth());
|
|
29130
|
+
scheduledResultApp.use("/schedule-runs/*", createApiKeyAuth());
|
|
29131
|
+
scheduledResultApp.get("/artifact-templates", async (c) => {
|
|
29132
|
+
const status = c.req.query("status") ?? "active";
|
|
29133
|
+
if (!["active", "archived", "all"].includes(status)) {
|
|
29134
|
+
return c.json(apiError("status must be active, archived, or all."), 400);
|
|
29135
|
+
}
|
|
29136
|
+
const { key, error } = await memoryContext(c);
|
|
29137
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29138
|
+
const result = await memoryCall("listArtifactTemplatesTool", { status }, key);
|
|
29139
|
+
if (!result.ok) return c.json(safeMemoryFailure(result), memoryStatus(result));
|
|
29140
|
+
c.header("Cache-Control", "private, no-store");
|
|
29141
|
+
return c.json({
|
|
29142
|
+
...result,
|
|
29143
|
+
presets: Array.isArray(result.presets) ? result.presets : [SCHEDULED_ARTIFACT_TEMPLATE_PRESET]
|
|
29144
|
+
});
|
|
29145
|
+
});
|
|
29146
|
+
scheduledResultApp.post("/artifact-templates", async (c) => {
|
|
29147
|
+
const parsed = CreateArtifactTemplateInputSchema.safeParse(await c.req.json().catch(() => null));
|
|
29148
|
+
if (!parsed.success) return c.json(apiError("The artifact template is invalid."), 400);
|
|
29149
|
+
const { key, error } = await memoryContext(c);
|
|
29150
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29151
|
+
const result = await memoryCall("createArtifactTemplateTool", parsed.data, key);
|
|
29152
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 201 : memoryStatus(result));
|
|
29153
|
+
});
|
|
29154
|
+
scheduledResultApp.get("/artifact-templates/:templateId", async (c) => {
|
|
29155
|
+
const params = TemplateParamsSchema.safeParse(c.req.param());
|
|
29156
|
+
if (!params.success) return c.json(apiError("The template id is invalid."), 400);
|
|
29157
|
+
const { key, error } = await memoryContext(c);
|
|
29158
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29159
|
+
const result = await memoryCall("getArtifactTemplateTool", params.data, key);
|
|
29160
|
+
c.header("Cache-Control", "private, no-store");
|
|
29161
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 200 : memoryStatus(result));
|
|
29162
|
+
});
|
|
29163
|
+
scheduledResultApp.put("/artifact-templates/:templateId", async (c) => {
|
|
29164
|
+
const params = TemplateParamsSchema.safeParse(c.req.param());
|
|
29165
|
+
const body = UpdateArtifactTemplateInputSchema.safeParse(await c.req.json().catch(() => null));
|
|
29166
|
+
if (!params.success || !body.success) return c.json(apiError("The template update is invalid."), 400);
|
|
29167
|
+
const { key, error } = await memoryContext(c);
|
|
29168
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29169
|
+
const result = await memoryCall("updateArtifactTemplateTool", { ...params.data, ...body.data }, key);
|
|
29170
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 200 : memoryStatus(result));
|
|
29171
|
+
});
|
|
29172
|
+
for (const [path5, archived] of [["archive", true], ["restore", false]]) {
|
|
29173
|
+
scheduledResultApp.post(`/artifact-templates/:templateId/${path5}`, async (c) => {
|
|
29174
|
+
const params = TemplateParamsSchema.safeParse(c.req.param());
|
|
29175
|
+
if (!params.success) return c.json(apiError("The template id is invalid."), 400);
|
|
29176
|
+
const { key, error } = await memoryContext(c);
|
|
29177
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29178
|
+
const result = await memoryCall("archiveArtifactTemplateTool", { ...params.data, archived }, key);
|
|
29179
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 200 : memoryStatus(result));
|
|
29180
|
+
});
|
|
29181
|
+
}
|
|
29182
|
+
scheduledResultApp.get("/schedule-runs", async (c) => {
|
|
29183
|
+
const parsed = ListScheduledRunsQuerySchema.safeParse(c.req.query());
|
|
29184
|
+
if (!parsed.success) return c.json(apiError("The scheduled result filters are invalid."), 400);
|
|
29185
|
+
const { key, error } = await memoryContext(c);
|
|
29186
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29187
|
+
const result = await memoryCall("listScheduledRunsTool", parsed.data, key);
|
|
29188
|
+
c.header("Cache-Control", "private, no-store");
|
|
29189
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 200 : memoryStatus(result));
|
|
29190
|
+
});
|
|
29191
|
+
scheduledResultApp.get("/schedule-runs/:runId", async (c) => {
|
|
29192
|
+
const params = RunParamsSchema.safeParse(c.req.param());
|
|
29193
|
+
if (!params.success) return c.json(apiError("The run id is invalid."), 400);
|
|
29194
|
+
const { key, error } = await memoryContext(c);
|
|
29195
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29196
|
+
const result = await memoryCall("getScheduledRunTool", params.data, key);
|
|
29197
|
+
c.header("Cache-Control", "private, no-store");
|
|
29198
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 200 : memoryStatus(result));
|
|
29199
|
+
});
|
|
29200
|
+
for (const [path5, upstreamName, input] of [
|
|
29201
|
+
["open", "markScheduledRunOpenedTool", {}],
|
|
29202
|
+
["unopen", "markScheduledRunUnopenedTool", {}],
|
|
29203
|
+
["archive", "archiveScheduledRunTool", { archived: true }],
|
|
29204
|
+
["restore", "archiveScheduledRunTool", { archived: false }]
|
|
29205
|
+
]) {
|
|
29206
|
+
scheduledResultApp.post(`/schedule-runs/:runId/${path5}`, async (c) => {
|
|
29207
|
+
const params = RunParamsSchema.safeParse(c.req.param());
|
|
29208
|
+
if (!params.success) return c.json(apiError("The run id is invalid."), 400);
|
|
29209
|
+
const { key, error } = await memoryContext(c);
|
|
29210
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29211
|
+
const result = await memoryCall(upstreamName, { ...params.data, ...input }, key);
|
|
29212
|
+
c.header("Cache-Control", "private, no-store");
|
|
29213
|
+
return c.json(result.ok ? result : safeMemoryFailure(result), result.ok ? 200 : memoryStatus(result));
|
|
29214
|
+
});
|
|
29215
|
+
}
|
|
29216
|
+
scheduledResultApp.post("/schedule-runs/:runId/view-links", async (c) => {
|
|
29217
|
+
const params = RunParamsSchema.safeParse(c.req.param());
|
|
29218
|
+
const body = CreateScheduledRunViewLinkInputSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
29219
|
+
if (!params.success || !body.success) return c.json(apiError("The view-link request is invalid."), 400);
|
|
29220
|
+
const { user, key, error } = await memoryContext(c);
|
|
29221
|
+
if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
|
|
29222
|
+
const rawResult = await memoryCall("getScheduledRunTool", params.data, key);
|
|
29223
|
+
const result = GetScheduledRunResponseSchema.safeParse(rawResult);
|
|
29224
|
+
if (!result.success) return c.json(apiError("The scheduled result was not found.", "not_found"), 404);
|
|
29225
|
+
const artifact = result.data.run.artifactOutputs.find((output) => output.artifactId === body.data.artifactId);
|
|
29226
|
+
if (!artifact) return c.json(apiError("The scheduled result artifact was not found.", "not_found"), 404);
|
|
29227
|
+
const ownerId = scheduledArtifactOwnerIdForApiKey(user.api_key);
|
|
29228
|
+
const { record, token: token3 } = await createScheduledRunViewLink({
|
|
29229
|
+
ownerId,
|
|
29230
|
+
runId: result.data.run.runId,
|
|
29231
|
+
artifactId: artifact.artifactId,
|
|
29232
|
+
title: artifact.title,
|
|
29233
|
+
summary: result.data.run.summary,
|
|
29234
|
+
rendererKey: artifact.rendererKey,
|
|
29235
|
+
expiresInDays: body.data.expiresInDays
|
|
29236
|
+
});
|
|
29237
|
+
const origin = process.env.APP_ORIGIN?.trim() || "https://mcpscraper.dev";
|
|
29238
|
+
return c.json({
|
|
29239
|
+
ok: true,
|
|
29240
|
+
shareId: record.shareId,
|
|
29241
|
+
runId: record.runId,
|
|
29242
|
+
artifactId: record.artifactId,
|
|
29243
|
+
url: `${origin.replace(/\/$/, "")}/view/scheduled-run/${token3}`,
|
|
29244
|
+
expiresAt: record.expiresAt,
|
|
29245
|
+
createdAt: record.createdAt
|
|
29246
|
+
}, 201);
|
|
29247
|
+
});
|
|
29248
|
+
scheduledResultApp.delete("/schedule-runs/:runId/view-links/:shareId", async (c) => {
|
|
29249
|
+
const params = ViewLinkParamsSchema.safeParse(c.req.param());
|
|
29250
|
+
if (!params.success) return c.json(apiError("The view-link id is invalid."), 400);
|
|
29251
|
+
const user = c.get("user");
|
|
29252
|
+
const revoked = await revokeScheduledRunViewLink(
|
|
29253
|
+
scheduledArtifactOwnerIdForApiKey(user.api_key),
|
|
29254
|
+
params.data.runId,
|
|
29255
|
+
params.data.shareId
|
|
29256
|
+
);
|
|
29257
|
+
if (!revoked) return c.json(apiError("The view link was not found.", "not_found"), 404);
|
|
29258
|
+
return c.json({ ok: true, revoked: true });
|
|
29259
|
+
});
|
|
29260
|
+
|
|
29261
|
+
// src/api/public-scheduled-result-routes.ts
|
|
29262
|
+
import { Hono as Hono31 } from "hono";
|
|
29263
|
+
|
|
29264
|
+
// src/scheduled-artifacts/scheduled-run-artifact-store.ts
|
|
29265
|
+
import { createHash as createHash13 } from "crypto";
|
|
29266
|
+
var SCHEDULED_RUN_ARTIFACT_PREFIX = "scheduled-run-artifacts/";
|
|
29267
|
+
var SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
|
|
29268
|
+
var SCHEDULED_RUN_ARTIFACT_MAX_BYTES = 2e6;
|
|
29269
|
+
function token2() {
|
|
29270
|
+
return process.env.SCHEDULED_ARTIFACT_READ_WRITE_TOKEN?.trim() || process.env.PRIVATE_ARTIFACT_READ_WRITE_TOKEN?.trim() || process.env.CONNECTED_DATA_READ_WRITE_TOKEN?.trim() || process.env.CONNECTED_DATA_BLOB_READ_WRITE_TOKEN?.trim() || null;
|
|
29271
|
+
}
|
|
29272
|
+
function policy2() {
|
|
29273
|
+
return {
|
|
29274
|
+
prefix: SCHEDULED_RUN_ARTIFACT_PREFIX,
|
|
29275
|
+
artifactTtlMs: null,
|
|
29276
|
+
downloadTtlMs: SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS,
|
|
29277
|
+
token: token2()
|
|
29278
|
+
};
|
|
29279
|
+
}
|
|
29280
|
+
function runStorageSegment(runId) {
|
|
29281
|
+
return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${createHash13("sha256").update(runId).digest("hex").slice(0, 32)}`;
|
|
29282
|
+
}
|
|
29283
|
+
async function createScheduledRunArtifact(args) {
|
|
29284
|
+
if (args.rendered.bytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) {
|
|
29285
|
+
throw new Error("Scheduled artifact HTML must be 2,000,000 bytes or fewer.");
|
|
29286
|
+
}
|
|
29287
|
+
const stored = await createPrivateArtifact({
|
|
29288
|
+
policy: policy2(),
|
|
29289
|
+
ownerId: args.request.ownerId,
|
|
29290
|
+
scopeSegments: [runStorageSegment(args.request.runId)],
|
|
29291
|
+
artifactKey: `${args.rendered.sha256.slice(0, 16)}-${args.rendered.filename}`,
|
|
29292
|
+
createdAt: args.rendered.generatedAt,
|
|
29293
|
+
filename: args.rendered.filename,
|
|
29294
|
+
contentType: "text/html",
|
|
29295
|
+
content: args.rendered.html
|
|
29296
|
+
});
|
|
29297
|
+
return {
|
|
29298
|
+
artifactId: stored.artifactId,
|
|
29299
|
+
templateId: args.request.templateId,
|
|
29300
|
+
templateVersionId: args.request.templateVersionId,
|
|
29301
|
+
rendererKey: args.request.rendererKey,
|
|
29302
|
+
rendererVersion: args.request.rendererVersion,
|
|
29303
|
+
mimeType: "text/html",
|
|
29304
|
+
title: args.rendered.title,
|
|
29305
|
+
bytes: stored.bytes,
|
|
29306
|
+
sha256: stored.sha256,
|
|
29307
|
+
createdAt: args.rendered.generatedAt,
|
|
29308
|
+
viewerPath: `/scheduled-actions/runs/${encodeURIComponent(args.request.runId)}`
|
|
29309
|
+
};
|
|
29310
|
+
}
|
|
29311
|
+
async function readScheduledRunArtifact(args) {
|
|
29312
|
+
if (!args.artifactId.startsWith(`${SCHEDULED_RUN_ARTIFACT_PREFIX}${args.ownerId}/`)) return null;
|
|
29313
|
+
const window2 = await readPrivateArtifactWindow({
|
|
29314
|
+
policy: policy2(),
|
|
29315
|
+
artifactId: args.artifactId,
|
|
29316
|
+
offset: 0,
|
|
29317
|
+
maxBytes: SCHEDULED_RUN_ARTIFACT_MAX_BYTES + 1
|
|
29318
|
+
});
|
|
29319
|
+
if (!window2 || window2.nextOffset !== null || window2.totalBytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) return null;
|
|
29320
|
+
return window2.text;
|
|
29321
|
+
}
|
|
29322
|
+
|
|
29323
|
+
// src/api/public-scheduled-result-routes.ts
|
|
29324
|
+
var publicScheduledResultApp = new Hono31();
|
|
29325
|
+
function allowedFrameAncestors() {
|
|
29326
|
+
const configured = (process.env.SCHEDULED_ARTIFACT_FRAME_ANCESTORS ?? "").split(",").map((value) => value.trim()).filter((value) => /^https?:\/\/[^/\s]+$/.test(value));
|
|
29327
|
+
const origins = configured.length ? configured : ["https://mcpscraper.dev", "https://www.mcpscraper.dev"];
|
|
29328
|
+
return origins.join(" ");
|
|
29329
|
+
}
|
|
29330
|
+
function publicHeaders(c, content = false) {
|
|
29331
|
+
c.header("Cache-Control", "private, no-store");
|
|
29332
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
29333
|
+
c.header("X-Robots-Tag", "noindex, nofollow");
|
|
29334
|
+
if (content) {
|
|
29335
|
+
c.header("Content-Security-Policy", [
|
|
29336
|
+
"default-src 'none'",
|
|
29337
|
+
"script-src 'unsafe-inline'",
|
|
29338
|
+
"style-src 'unsafe-inline'",
|
|
29339
|
+
"img-src https: data:",
|
|
29340
|
+
"font-src data:",
|
|
29341
|
+
"connect-src 'none'",
|
|
29342
|
+
`frame-ancestors ${allowedFrameAncestors()}`,
|
|
29343
|
+
"base-uri 'none'",
|
|
29344
|
+
"form-action 'none'"
|
|
29345
|
+
].join("; "));
|
|
29346
|
+
}
|
|
29347
|
+
}
|
|
29348
|
+
function unavailableJson(c) {
|
|
29349
|
+
publicHeaders(c);
|
|
29350
|
+
return c.json({
|
|
29351
|
+
ok: false,
|
|
29352
|
+
error: "This link is no longer available.",
|
|
29353
|
+
errorCode: "view_link_unavailable"
|
|
29354
|
+
}, 404);
|
|
29355
|
+
}
|
|
29356
|
+
function unavailableHtml() {
|
|
29357
|
+
return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Link unavailable</title><style>body{margin:0;min-height:100vh;display:grid;place-items:center;font:16px/1.5 system-ui;background:#f8f5ed;color:#20211f}main{max-width:34rem;padding:2rem}h1{font-size:1.5rem}</style></head><body><main><h1>This link is no longer available</h1><p>Ask the report owner for a new view link.</p></main></body></html>';
|
|
29358
|
+
}
|
|
29359
|
+
publicScheduledResultApp.get("/scheduled-run-view-links/:token", async (c) => {
|
|
29360
|
+
const parsed = PublicScheduledRunTokenSchema.safeParse(c.req.param());
|
|
29361
|
+
if (!parsed.success) return unavailableJson(c);
|
|
29362
|
+
const grant = await resolveScheduledRunViewLink(parsed.data.token);
|
|
29363
|
+
if (!grant) return unavailableJson(c);
|
|
29364
|
+
publicHeaders(c);
|
|
29365
|
+
const requestUrl = new URL(c.req.url);
|
|
29366
|
+
return c.json({
|
|
29367
|
+
ok: true,
|
|
29368
|
+
title: grant.title,
|
|
29369
|
+
summary: grant.summary,
|
|
29370
|
+
rendererKey: grant.rendererKey,
|
|
29371
|
+
contentUrl: `${requestUrl.origin}/public/scheduled-run-view-links/${parsed.data.token}/content`,
|
|
29372
|
+
expiresAt: grant.expiresAt
|
|
29373
|
+
});
|
|
29374
|
+
});
|
|
29375
|
+
publicScheduledResultApp.get("/scheduled-run-view-links/:token/content", async (c) => {
|
|
29376
|
+
publicHeaders(c, true);
|
|
29377
|
+
const parsed = PublicScheduledRunTokenSchema.safeParse(c.req.param());
|
|
29378
|
+
const grant = parsed.success ? await resolveScheduledRunViewLink(parsed.data.token) : null;
|
|
29379
|
+
const html = grant ? await readScheduledRunArtifact({ ownerId: grant.ownerId, artifactId: grant.artifactId }) : null;
|
|
29380
|
+
if (!html) return c.html(unavailableHtml(), 404);
|
|
29381
|
+
return c.body(html, 200, { "Content-Type": "text/html; charset=utf-8" });
|
|
29382
|
+
});
|
|
29383
|
+
|
|
29384
|
+
// src/api/scheduled-artifact-internal-routes.ts
|
|
29385
|
+
import { Hono as Hono32 } from "hono";
|
|
29386
|
+
|
|
29387
|
+
// src/api/scheduler-integration-auth.ts
|
|
29388
|
+
import { createHash as createHash14, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
29389
|
+
|
|
29390
|
+
// src/api/service-connections.ts
|
|
29391
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
29392
|
+
var schemaReady3 = null;
|
|
29393
|
+
var schemaDb2 = null;
|
|
29394
|
+
function ensureServiceConnectionsSchema() {
|
|
29395
|
+
const currentDb = getDb();
|
|
29396
|
+
if (schemaReady3 && schemaDb2 === currentDb) return schemaReady3;
|
|
29397
|
+
schemaDb2 = currentDb;
|
|
29398
|
+
schemaReady3 = (async () => {
|
|
29399
|
+
const db = currentDb;
|
|
29400
|
+
await db.execute(`
|
|
29401
|
+
CREATE TABLE IF NOT EXISTS service_connections (
|
|
29402
|
+
id TEXT PRIMARY KEY,
|
|
29403
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
29404
|
+
provider_config_key TEXT NOT NULL,
|
|
29405
|
+
provider TEXT,
|
|
29406
|
+
transport TEXT NOT NULL,
|
|
29407
|
+
upstream_connection_id TEXT,
|
|
29408
|
+
label TEXT,
|
|
29409
|
+
lifecycle_status TEXT NOT NULL DEFAULT 'pending',
|
|
29410
|
+
operational_status TEXT NOT NULL DEFAULT 'unknown',
|
|
29411
|
+
reconnect_required INTEGER NOT NULL DEFAULT 0,
|
|
29412
|
+
actions_enabled INTEGER NOT NULL DEFAULT 0,
|
|
29413
|
+
read_tools_json TEXT NOT NULL DEFAULT '[]',
|
|
29414
|
+
action_tools_json TEXT NOT NULL DEFAULT '[]',
|
|
29415
|
+
tool_revision TEXT,
|
|
29416
|
+
last_checked_at TEXT,
|
|
29417
|
+
last_successful_call_at TEXT,
|
|
29418
|
+
last_failure_at TEXT,
|
|
29419
|
+
last_failure_code TEXT,
|
|
29420
|
+
last_failure_retryable INTEGER,
|
|
29421
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
29422
|
+
vault_name TEXT,
|
|
29423
|
+
table_name TEXT,
|
|
29424
|
+
legacy_owner_identity TEXT,
|
|
29425
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
29426
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
29427
|
+
UNIQUE(user_id, provider_config_key, upstream_connection_id)
|
|
29428
|
+
)
|
|
29429
|
+
`);
|
|
29430
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
|
|
29431
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
|
|
29432
|
+
await db.execute(`
|
|
29433
|
+
CREATE TABLE IF NOT EXISTS service_connection_health_events (
|
|
29434
|
+
id TEXT PRIMARY KEY,
|
|
29435
|
+
connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
|
|
29436
|
+
operational_status TEXT NOT NULL,
|
|
29437
|
+
failure_code TEXT,
|
|
29438
|
+
retryable INTEGER,
|
|
29439
|
+
evidence_source TEXT NOT NULL,
|
|
29440
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
29441
|
+
)
|
|
29442
|
+
`);
|
|
29443
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
|
|
29444
|
+
await db.execute(`
|
|
29445
|
+
CREATE TABLE IF NOT EXISTS service_connection_action_audit (
|
|
29446
|
+
id TEXT PRIMARY KEY,
|
|
29447
|
+
connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
|
|
29448
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
29449
|
+
tool TEXT NOT NULL,
|
|
29450
|
+
request_id TEXT NOT NULL,
|
|
29451
|
+
status TEXT NOT NULL,
|
|
29452
|
+
error_code TEXT,
|
|
29453
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
29454
|
+
UNIQUE(user_id, request_id)
|
|
29455
|
+
)
|
|
29456
|
+
`);
|
|
29457
|
+
})().catch((error) => {
|
|
29458
|
+
schemaReady3 = null;
|
|
29459
|
+
schemaDb2 = null;
|
|
29460
|
+
throw error;
|
|
29461
|
+
});
|
|
29462
|
+
return schemaReady3;
|
|
29463
|
+
}
|
|
29464
|
+
function parseTools(value) {
|
|
29465
|
+
if (typeof value !== "string") return [];
|
|
29466
|
+
try {
|
|
29467
|
+
const parsed = JSON.parse(value);
|
|
29468
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
29469
|
+
} catch {
|
|
29470
|
+
return [];
|
|
29471
|
+
}
|
|
29472
|
+
}
|
|
29473
|
+
function rowToRecord(row) {
|
|
29474
|
+
return {
|
|
29475
|
+
id: String(row.id),
|
|
29476
|
+
userId: Number(row.user_id),
|
|
29477
|
+
providerConfigKey: String(row.provider_config_key),
|
|
29478
|
+
provider: typeof row.provider === "string" ? row.provider : null,
|
|
29479
|
+
transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
|
|
29480
|
+
upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
|
|
29481
|
+
label: typeof row.label === "string" ? row.label : null,
|
|
29482
|
+
lifecycleStatus: String(row.lifecycle_status),
|
|
29483
|
+
operationalStatus: String(row.operational_status),
|
|
29484
|
+
reconnectRequired: Number(row.reconnect_required) === 1,
|
|
29485
|
+
actionsEnabled: Number(row.actions_enabled) === 1,
|
|
29486
|
+
readTools: parseTools(row.read_tools_json),
|
|
29487
|
+
actionTools: parseTools(row.action_tools_json),
|
|
29488
|
+
toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
|
|
29489
|
+
lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
|
|
29490
|
+
lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
|
|
29491
|
+
lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
|
|
29492
|
+
lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
|
|
29493
|
+
lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
|
|
29494
|
+
consecutiveFailures: Number(row.consecutive_failures ?? 0),
|
|
29495
|
+
vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
|
|
29496
|
+
tableName: typeof row.table_name === "string" ? row.table_name : null,
|
|
29497
|
+
createdAt: String(row.created_at),
|
|
29498
|
+
updatedAt: String(row.updated_at)
|
|
29499
|
+
};
|
|
29500
|
+
}
|
|
29501
|
+
async function userIdForIdentity(identity) {
|
|
29502
|
+
const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
|
|
29503
|
+
if (!user) throw new Error("service_connection_user_not_found");
|
|
29504
|
+
return Number(user.id);
|
|
29505
|
+
}
|
|
29506
|
+
async function reconcileDiscoveredNangoConnections(identity, discovered) {
|
|
29507
|
+
await ensureServiceConnectionsSchema();
|
|
29508
|
+
const db = getDb();
|
|
29509
|
+
const userId = await userIdForIdentity(identity);
|
|
29510
|
+
for (const connection of discovered) {
|
|
29511
|
+
await db.execute({
|
|
29512
|
+
sql: `
|
|
29513
|
+
INSERT INTO service_connections (
|
|
29514
|
+
id, user_id, provider_config_key, provider, transport, upstream_connection_id,
|
|
29515
|
+
label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
|
|
29516
|
+
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
29517
|
+
ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
|
|
29518
|
+
provider = excluded.provider,
|
|
29519
|
+
label = COALESCE(excluded.label, service_connections.label),
|
|
29520
|
+
lifecycle_status = excluded.lifecycle_status,
|
|
29521
|
+
reconnect_required = excluded.reconnect_required,
|
|
29522
|
+
updated_at = excluded.updated_at
|
|
29523
|
+
`,
|
|
29524
|
+
args: [
|
|
29525
|
+
randomUUID14(),
|
|
29526
|
+
userId,
|
|
29527
|
+
connection.providerConfigKey,
|
|
29528
|
+
connection.provider,
|
|
29529
|
+
connection.upstreamConnectionId,
|
|
29530
|
+
connection.label,
|
|
29531
|
+
connection.lifecycleStatus,
|
|
29532
|
+
connection.reconnectRequired ? 1 : 0,
|
|
29533
|
+
identity,
|
|
29534
|
+
connection.createdAt,
|
|
29535
|
+
connection.updatedAt
|
|
29536
|
+
]
|
|
29537
|
+
});
|
|
29538
|
+
}
|
|
29539
|
+
return listServiceConnections(identity, "nango");
|
|
29540
|
+
}
|
|
29541
|
+
async function listServiceConnections(identity, transport) {
|
|
29542
|
+
await ensureServiceConnectionsSchema();
|
|
29543
|
+
const userId = await userIdForIdentity(identity);
|
|
29544
|
+
const result = await getDb().execute({
|
|
29545
|
+
sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
|
|
29546
|
+
args: transport ? [userId, transport] : [userId]
|
|
29547
|
+
});
|
|
29548
|
+
return result.rows.map((row) => rowToRecord(row));
|
|
29549
|
+
}
|
|
29550
|
+
async function getOwnedServiceConnection(identity, connectionId) {
|
|
29551
|
+
await ensureServiceConnectionsSchema();
|
|
29552
|
+
const userId = await userIdForIdentity(identity);
|
|
29553
|
+
const result = await getDb().execute({
|
|
29554
|
+
sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
|
|
29555
|
+
args: [connectionId, userId]
|
|
29556
|
+
});
|
|
29557
|
+
const row = result.rows[0];
|
|
29558
|
+
return row ? rowToRecord(row) : null;
|
|
29559
|
+
}
|
|
29560
|
+
async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
|
|
29561
|
+
await ensureServiceConnectionsSchema();
|
|
29562
|
+
await getDb().execute({
|
|
29563
|
+
sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
29564
|
+
args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
|
|
29565
|
+
});
|
|
29566
|
+
}
|
|
29567
|
+
async function recordServiceConnectionHealth(args) {
|
|
29568
|
+
await ensureServiceConnectionsSchema();
|
|
29569
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
29570
|
+
await getDb().execute({
|
|
29571
|
+
sql: `
|
|
29572
|
+
UPDATE service_connections SET
|
|
29573
|
+
operational_status = ?, last_checked_at = ?,
|
|
29574
|
+
last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
|
|
29575
|
+
last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
|
|
29576
|
+
last_failure_code = ?, last_failure_retryable = ?,
|
|
29577
|
+
consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
|
|
29578
|
+
updated_at = datetime('now')
|
|
29579
|
+
WHERE id = ?
|
|
29580
|
+
`,
|
|
29581
|
+
args: [
|
|
29582
|
+
args.operationalStatus,
|
|
29583
|
+
now,
|
|
29584
|
+
args.operationalStatus,
|
|
29585
|
+
now,
|
|
29586
|
+
args.operationalStatus,
|
|
29587
|
+
now,
|
|
29588
|
+
args.failureCode ?? null,
|
|
29589
|
+
args.retryable == null ? null : args.retryable ? 1 : 0,
|
|
29590
|
+
args.operationalStatus,
|
|
29591
|
+
args.connectionId
|
|
29592
|
+
]
|
|
29593
|
+
});
|
|
29594
|
+
await getDb().execute({
|
|
29595
|
+
sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
29596
|
+
args: [randomUUID14(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
|
|
29597
|
+
});
|
|
29598
|
+
}
|
|
29599
|
+
async function setServiceConnectionActions(identity, connectionId, enabled) {
|
|
29600
|
+
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
29601
|
+
if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
|
|
29602
|
+
await getDb().execute({
|
|
29603
|
+
sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
29604
|
+
args: [enabled ? 1 : 0, connectionId, connection.userId]
|
|
29605
|
+
});
|
|
29606
|
+
return enabled;
|
|
29607
|
+
}
|
|
29608
|
+
async function markServiceConnectionDisconnected(identity, connectionId) {
|
|
29609
|
+
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
29610
|
+
if (!connection) throw new Error("service_connection_not_found");
|
|
29611
|
+
await getDb().execute({
|
|
29612
|
+
sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
29613
|
+
args: [connectionId, connection.userId]
|
|
29614
|
+
});
|
|
29615
|
+
const remaining = await getDb().execute({
|
|
29616
|
+
sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
|
|
29617
|
+
args: [connection.userId]
|
|
29618
|
+
});
|
|
29619
|
+
return Number(remaining.rows[0]?.count ?? 0);
|
|
29620
|
+
}
|
|
29621
|
+
async function recordServiceConnectionAction(args) {
|
|
29622
|
+
await ensureServiceConnectionsSchema();
|
|
29623
|
+
const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
|
|
29624
|
+
if (!connection) throw new Error("service_connection_not_found");
|
|
29625
|
+
await getDb().execute({
|
|
29626
|
+
sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
|
|
29627
|
+
args: [args.status, args.errorCode ?? null, connection.userId, args.requestId, connection.id]
|
|
29628
|
+
});
|
|
29629
|
+
}
|
|
29630
|
+
async function claimServiceConnectionAction(args) {
|
|
29631
|
+
await ensureServiceConnectionsSchema();
|
|
29632
|
+
const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
|
|
29633
|
+
if (!connection) throw new Error("service_connection_not_found");
|
|
29634
|
+
const inserted = await getDb().execute({
|
|
29635
|
+
sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status) VALUES (?, ?, ?, ?, ?, 'started')`,
|
|
29636
|
+
args: [randomUUID14(), connection.id, connection.userId, args.tool, args.requestId]
|
|
29637
|
+
});
|
|
29638
|
+
if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
|
|
29639
|
+
const existing = await getDb().execute({
|
|
29640
|
+
sql: `SELECT status, error_code FROM service_connection_action_audit WHERE user_id = ? AND request_id = ? LIMIT 1`,
|
|
29641
|
+
args: [connection.userId, args.requestId]
|
|
29642
|
+
});
|
|
29643
|
+
return {
|
|
29644
|
+
claimed: false,
|
|
29645
|
+
status: String(existing.rows[0]?.status ?? "unknown"),
|
|
29646
|
+
errorCode: typeof existing.rows[0]?.error_code === "string" ? existing.rows[0].error_code : null
|
|
29647
|
+
};
|
|
29648
|
+
}
|
|
29649
|
+
|
|
29650
|
+
// src/api/scheduler-integration-auth.ts
|
|
29651
|
+
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
29652
|
+
var SchedulerIntegrationAuthError = class extends Error {
|
|
29653
|
+
code;
|
|
29654
|
+
constructor(code) {
|
|
29655
|
+
super(code);
|
|
29656
|
+
this.name = "SchedulerIntegrationAuthError";
|
|
29657
|
+
this.code = code;
|
|
29658
|
+
}
|
|
29659
|
+
};
|
|
29660
|
+
function signingSecret() {
|
|
29661
|
+
const secret2 = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();
|
|
29662
|
+
if (!secret2) throw new SchedulerIntegrationAuthError("not_configured");
|
|
29663
|
+
return secret2;
|
|
29664
|
+
}
|
|
29665
|
+
function schedulerIntegrationSignature(args) {
|
|
29666
|
+
const bodyHash = createHash14("sha256").update(args.body).digest("hex");
|
|
29667
|
+
return createHmac4("sha256", args.secret).update(`${args.method.toUpperCase()}
|
|
29668
|
+
${args.path}
|
|
29669
|
+
${args.timestamp}
|
|
29670
|
+
${args.nonce}
|
|
29671
|
+
${bodyHash}`).digest("hex");
|
|
29672
|
+
}
|
|
29673
|
+
async function claimNonce(nonce, timestampMs) {
|
|
29674
|
+
await ensureServiceConnectionsSchema();
|
|
29675
|
+
await getDb().execute(`
|
|
29676
|
+
CREATE TABLE IF NOT EXISTS service_connection_scheduler_nonces (
|
|
29677
|
+
nonce TEXT PRIMARY KEY,
|
|
29678
|
+
expires_at TEXT NOT NULL,
|
|
29679
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
29680
|
+
)
|
|
29681
|
+
`);
|
|
29682
|
+
await getDb().execute(`DELETE FROM service_connection_scheduler_nonces WHERE expires_at < datetime('now')`);
|
|
29683
|
+
try {
|
|
29684
|
+
await getDb().execute({
|
|
29685
|
+
sql: `INSERT INTO service_connection_scheduler_nonces (nonce, expires_at) VALUES (?, ?)`,
|
|
29686
|
+
args: [nonce, new Date(timestampMs + MAX_CLOCK_SKEW_MS).toISOString()]
|
|
29687
|
+
});
|
|
29688
|
+
} catch {
|
|
29689
|
+
throw new SchedulerIntegrationAuthError("replayed_request");
|
|
29690
|
+
}
|
|
29691
|
+
}
|
|
29692
|
+
async function verifySchedulerIntegrationRequest(request, rawBody) {
|
|
29693
|
+
const timestamp = request.headers.get("x-scheduler-timestamp")?.trim() ?? "";
|
|
29694
|
+
const nonce = request.headers.get("x-scheduler-nonce")?.trim() ?? "";
|
|
29695
|
+
const signature = request.headers.get("x-scheduler-signature")?.trim().toLowerCase() ?? "";
|
|
29696
|
+
const requestId = request.headers.get("x-scheduler-request-id")?.trim() ?? "";
|
|
29697
|
+
const timestampMs = Date.parse(timestamp);
|
|
29698
|
+
if (!timestamp || !nonce || !signature || !requestId || nonce.length > 200 || requestId.length > 200 || !Number.isFinite(timestampMs)) {
|
|
29699
|
+
throw new SchedulerIntegrationAuthError("invalid_signature");
|
|
29700
|
+
}
|
|
29701
|
+
if (Math.abs(Date.now() - timestampMs) > MAX_CLOCK_SKEW_MS) {
|
|
29702
|
+
throw new SchedulerIntegrationAuthError("expired_request");
|
|
29703
|
+
}
|
|
29704
|
+
const expected = schedulerIntegrationSignature({
|
|
29705
|
+
method: request.method,
|
|
29706
|
+
path: new URL(request.url).pathname,
|
|
29707
|
+
timestamp,
|
|
29708
|
+
nonce,
|
|
29709
|
+
body: rawBody,
|
|
29710
|
+
secret: signingSecret()
|
|
29711
|
+
});
|
|
29712
|
+
const suppliedBytes = Buffer.from(signature, "hex");
|
|
29713
|
+
const expectedBytes = Buffer.from(expected, "hex");
|
|
29714
|
+
if (suppliedBytes.length !== expectedBytes.length || !timingSafeEqual3(suppliedBytes, expectedBytes)) {
|
|
29715
|
+
throw new SchedulerIntegrationAuthError("invalid_signature");
|
|
29716
|
+
}
|
|
29717
|
+
await claimNonce(nonce, timestampMs);
|
|
29718
|
+
return { requestId };
|
|
29719
|
+
}
|
|
29720
|
+
|
|
29721
|
+
// src/scheduled-artifacts/scheduled-artifact-renderer-registry.ts
|
|
29722
|
+
var editorialReadingRoomV1 = {
|
|
29723
|
+
rendererKey: "editorial_reading_room_v1",
|
|
29724
|
+
rendererVersion: 1,
|
|
29725
|
+
async render(rawInput, rawConfig, generatedAt) {
|
|
29726
|
+
const input = EditorialReadingRoomV1InputSchema.parse(rawInput);
|
|
29727
|
+
const config = ArtifactTemplateConfigSchema.parse(rawConfig);
|
|
29728
|
+
const rendered = renderEditorialReadingRoomV1(input, config, generatedAt);
|
|
29729
|
+
if (rendered.bytes > 2e6) {
|
|
29730
|
+
throw new Error("Scheduled artifact HTML must be 2,000,000 bytes or fewer.");
|
|
29731
|
+
}
|
|
29732
|
+
return {
|
|
29733
|
+
html: rendered.html,
|
|
29734
|
+
filename: rendered.filename,
|
|
29735
|
+
title: input.title,
|
|
29736
|
+
bytes: rendered.bytes,
|
|
29737
|
+
sha256: rendered.sha256,
|
|
29738
|
+
generatedAt: rendered.generatedAt
|
|
29739
|
+
};
|
|
29740
|
+
}
|
|
29741
|
+
};
|
|
29742
|
+
var scheduledArtifactRendererRegistry = Object.freeze({
|
|
29743
|
+
"editorial_reading_room_v1:1": editorialReadingRoomV1
|
|
29744
|
+
});
|
|
29745
|
+
function getScheduledArtifactRenderer(rendererKey, rendererVersion) {
|
|
29746
|
+
return scheduledArtifactRendererRegistry[`${rendererKey}:${rendererVersion}`] ?? null;
|
|
29747
|
+
}
|
|
29748
|
+
|
|
29749
|
+
// src/api/scheduled-artifact-internal-routes.ts
|
|
29750
|
+
var scheduledArtifactInternalApp = new Hono32();
|
|
29751
|
+
function errorPayload(errorCode, error, requestId) {
|
|
29752
|
+
return ApiErrorResponseSchema.parse({
|
|
29753
|
+
ok: false,
|
|
29754
|
+
error,
|
|
29755
|
+
errorCode,
|
|
29756
|
+
...requestId ? { requestId } : {}
|
|
29757
|
+
});
|
|
29758
|
+
}
|
|
29759
|
+
scheduledArtifactInternalApp.post("/render", async (c) => {
|
|
29760
|
+
const rawBody = await c.req.text();
|
|
29761
|
+
let requestId;
|
|
29762
|
+
try {
|
|
29763
|
+
requestId = (await verifySchedulerIntegrationRequest(c.req.raw, rawBody)).requestId;
|
|
29764
|
+
} catch (error) {
|
|
29765
|
+
const code = error instanceof SchedulerIntegrationAuthError ? error.code : "invalid_signature";
|
|
29766
|
+
console.warn("[scheduled-artifact-render] rejected scheduler request", { code });
|
|
29767
|
+
return c.json(errorPayload("not_found", "Scheduled renderer is unavailable."), 401);
|
|
29768
|
+
}
|
|
29769
|
+
if (process.env.SCHEDULED_ARTIFACTS_V1_ENABLED !== "1") {
|
|
29770
|
+
return c.json(errorPayload("renderer_unavailable", "Scheduled renderer is unavailable.", requestId), 503);
|
|
29771
|
+
}
|
|
29772
|
+
const parsedJson = (() => {
|
|
29773
|
+
try {
|
|
29774
|
+
return JSON.parse(rawBody);
|
|
29775
|
+
} catch {
|
|
29776
|
+
return null;
|
|
29777
|
+
}
|
|
29778
|
+
})();
|
|
29779
|
+
const parsed = InternalScheduledArtifactRenderRequestSchema.safeParse(parsedJson);
|
|
29780
|
+
if (!parsed.success) {
|
|
29781
|
+
return c.json(errorPayload("invalid_request", "The scheduled artifact request is invalid.", requestId), 400);
|
|
29782
|
+
}
|
|
29783
|
+
const startedAt = Date.now();
|
|
29784
|
+
const request = parsed.data;
|
|
29785
|
+
const user = await getUserByEmail(request.identity);
|
|
29786
|
+
const expectedOwnerId = user?.api_key ? scheduledArtifactOwnerIdForApiKey(user.api_key) : null;
|
|
29787
|
+
if (!user || user.key_active !== 1 || !expectedOwnerId || expectedOwnerId !== request.ownerId) {
|
|
29788
|
+
console.warn("[scheduled-artifact-render] identity/owner binding rejected", {
|
|
29789
|
+
requestId,
|
|
29790
|
+
runId: request.runId,
|
|
29791
|
+
identityResolved: Boolean(user),
|
|
29792
|
+
ownerMatched: expectedOwnerId === request.ownerId
|
|
29793
|
+
});
|
|
29794
|
+
return c.json(errorPayload("not_found", "Scheduled renderer is unavailable.", requestId), 404);
|
|
29795
|
+
}
|
|
29796
|
+
const renderer = getScheduledArtifactRenderer(request.rendererKey, request.rendererVersion);
|
|
29797
|
+
if (!renderer) {
|
|
29798
|
+
return c.json(errorPayload("renderer_unavailable", "Scheduled renderer is unavailable.", requestId), 409);
|
|
29799
|
+
}
|
|
29800
|
+
try {
|
|
29801
|
+
const rendered = await renderer.render(request.input, request.config, /* @__PURE__ */ new Date());
|
|
29802
|
+
const artifact = await createScheduledRunArtifact({ request, rendered });
|
|
29803
|
+
console.info("[scheduled-artifact-render] completed", {
|
|
29804
|
+
requestId,
|
|
29805
|
+
runId: request.runId,
|
|
29806
|
+
scheduleId: request.scheduleId,
|
|
29807
|
+
templateId: request.templateId,
|
|
29808
|
+
templateVersionId: request.templateVersionId,
|
|
29809
|
+
rendererKey: request.rendererKey,
|
|
29810
|
+
artifactBytes: artifact.bytes,
|
|
29811
|
+
durationMs: Date.now() - startedAt
|
|
29812
|
+
});
|
|
29813
|
+
return c.json({ ok: true, artifact });
|
|
29814
|
+
} catch (error) {
|
|
29815
|
+
const message = error instanceof Error ? error.message : "";
|
|
29816
|
+
const tooLarge = message.includes("2,000,000 bytes");
|
|
29817
|
+
console.error("[scheduled-artifact-render] failed", {
|
|
29818
|
+
requestId,
|
|
29819
|
+
runId: request.runId,
|
|
29820
|
+
scheduleId: request.scheduleId,
|
|
29821
|
+
rendererKey: request.rendererKey,
|
|
29822
|
+
durationMs: Date.now() - startedAt,
|
|
29823
|
+
errorCode: tooLarge ? "artifact_too_large" : "render_failed"
|
|
29824
|
+
});
|
|
29825
|
+
return c.json(
|
|
29826
|
+
errorPayload(
|
|
29827
|
+
tooLarge ? "invalid_request" : "internal_error",
|
|
29828
|
+
tooLarge ? "The scheduled artifact is too large." : "The scheduled artifact could not be rendered.",
|
|
29829
|
+
requestId
|
|
29830
|
+
),
|
|
29831
|
+
tooLarge ? 413 : 500
|
|
29832
|
+
);
|
|
29833
|
+
}
|
|
29834
|
+
});
|
|
29835
|
+
|
|
28867
29836
|
// src/api/site-audit-worker.ts
|
|
28868
29837
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
28869
29838
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -28978,13 +29947,13 @@ async function reconcileSiteExtractSettlements(limit = 25) {
|
|
|
28978
29947
|
}
|
|
28979
29948
|
|
|
28980
29949
|
// src/api/page-diff.ts
|
|
28981
|
-
import { createHash as
|
|
29950
|
+
import { createHash as createHash15 } from "crypto";
|
|
28982
29951
|
import { diffLines } from "diff";
|
|
28983
29952
|
var MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
|
|
28984
29953
|
var MAX_DIFF_HUNKS = 200;
|
|
28985
29954
|
var MAX_DIFF_LINES_PER_RESPONSE = 2e3;
|
|
28986
29955
|
function sha256Hex(value) {
|
|
28987
|
-
return
|
|
29956
|
+
return createHash15("sha256").update(value).digest("hex");
|
|
28988
29957
|
}
|
|
28989
29958
|
function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
|
|
28990
29959
|
if (value.length <= maxChars) return { value, truncated: false };
|
|
@@ -29506,7 +30475,7 @@ function inferredWaybackCapturedAt(content) {
|
|
|
29506
30475
|
}
|
|
29507
30476
|
|
|
29508
30477
|
// src/api/connection-memory-import.ts
|
|
29509
|
-
import { createHash as
|
|
30478
|
+
import { createHash as createHash16 } from "crypto";
|
|
29510
30479
|
var CONNECTION_MEMORY_IMPORT_MAX_ARGS_BYTES = 64 * 1024;
|
|
29511
30480
|
var CONNECTION_MEMORY_IMPORT_MAX_RESULT_BYTES = 1e6;
|
|
29512
30481
|
var CONNECTION_MEMORY_IMPORT_MAX_STRING_CHARS = 256e3;
|
|
@@ -29536,7 +30505,7 @@ function canonicalJson(value) {
|
|
|
29536
30505
|
return JSON.stringify(value);
|
|
29537
30506
|
}
|
|
29538
30507
|
function sha2562(value) {
|
|
29539
|
-
return
|
|
30508
|
+
return createHash16("sha256").update(value).digest("hex");
|
|
29540
30509
|
}
|
|
29541
30510
|
function sensitiveKey(key) {
|
|
29542
30511
|
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
@@ -29756,15 +30725,15 @@ async function importServiceConnectionToMemory(identity, input, dependencies) {
|
|
|
29756
30725
|
import { readdir, stat, unlink } from "fs/promises";
|
|
29757
30726
|
import { homedir } from "os";
|
|
29758
30727
|
import { join as join6 } from "path";
|
|
29759
|
-
async function cleanupVercel(
|
|
30728
|
+
async function cleanupVercel(token3, cutoff) {
|
|
29760
30729
|
const { list, del } = await import("@vercel/blob");
|
|
29761
30730
|
let cursor;
|
|
29762
30731
|
let deleted = 0;
|
|
29763
30732
|
do {
|
|
29764
|
-
const res = await list({ prefix: SCRAPE_FALLBACK_PREFIX, cursor, token:
|
|
30733
|
+
const res = await list({ prefix: SCRAPE_FALLBACK_PREFIX, cursor, token: token3 });
|
|
29765
30734
|
const expired = res.blobs.filter((b) => new Date(b.uploadedAt).getTime() < cutoff);
|
|
29766
30735
|
if (expired.length) {
|
|
29767
|
-
await del(expired.map((b) => b.url), { token:
|
|
30736
|
+
await del(expired.map((b) => b.url), { token: token3 });
|
|
29768
30737
|
deleted += expired.length;
|
|
29769
30738
|
}
|
|
29770
30739
|
cursor = res.hasMore ? res.cursor : void 0;
|
|
@@ -29797,15 +30766,15 @@ async function cleanupLocal(cutoff) {
|
|
|
29797
30766
|
async function cleanupExpiredScrapeBlobs(maxAgeMs = SCRAPE_BLOB_TTL_MS) {
|
|
29798
30767
|
const cutoff = Date.now() - maxAgeMs;
|
|
29799
30768
|
try {
|
|
29800
|
-
const
|
|
29801
|
-
return
|
|
30769
|
+
const token3 = process.env.BLOB_READ_WRITE_TOKEN;
|
|
30770
|
+
return token3 ? await cleanupVercel(token3, cutoff) : await cleanupLocal(cutoff);
|
|
29802
30771
|
} catch {
|
|
29803
30772
|
return { deleted: 0, store: "none" };
|
|
29804
30773
|
}
|
|
29805
30774
|
}
|
|
29806
30775
|
|
|
29807
30776
|
// src/api/connected-data-export.ts
|
|
29808
|
-
import { randomUUID as
|
|
30777
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
29809
30778
|
var CONNECTED_DATA_INLINE_BUDGET_BYTES = Number(
|
|
29810
30779
|
process.env.MCP_SCRAPER_CONNECTED_DATA_INLINE_BUDGET_BYTES ?? 5e4
|
|
29811
30780
|
);
|
|
@@ -29976,7 +30945,7 @@ function finitePositive(value, fallback) {
|
|
|
29976
30945
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
29977
30946
|
}
|
|
29978
30947
|
async function collectConnectedDataExport(args) {
|
|
29979
|
-
const exportId =
|
|
30948
|
+
const exportId = randomUUID15();
|
|
29980
30949
|
const now = args.now ?? Date.now;
|
|
29981
30950
|
const startedAt = now();
|
|
29982
30951
|
const budgetMs = finitePositive(CONNECTED_DATA_EXPORT_BUDGET_MS, 24e4);
|
|
@@ -30092,7 +31061,7 @@ ${lines.length ? `${lines.join("\n")}
|
|
|
30092
31061
|
}
|
|
30093
31062
|
|
|
30094
31063
|
// src/api/search-console-table-export.ts
|
|
30095
|
-
import { randomUUID as
|
|
31064
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
30096
31065
|
var SEARCH_CONSOLE_TABLE_EXPORT_MAX_ROWS = 5e4;
|
|
30097
31066
|
var SEARCH_CONSOLE_TABLE_EXPORT_PAGE_SIZE = 2e3;
|
|
30098
31067
|
var SEARCH_CONSOLE_TABLE_EXPORT_MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -30192,7 +31161,7 @@ async function exportSearchConsoleTableData(args) {
|
|
|
30192
31161
|
offset += rows.length;
|
|
30193
31162
|
if (stoppedForBytes || rows.length < limit || offset >= matchedRows) break;
|
|
30194
31163
|
}
|
|
30195
|
-
const exportId =
|
|
31164
|
+
const exportId = randomUUID16();
|
|
30196
31165
|
const artifact = await args.writeArtifact({
|
|
30197
31166
|
ownerId: args.ownerId,
|
|
30198
31167
|
exportId,
|
|
@@ -30547,7 +31516,7 @@ async function dbUsage(identity, plan) {
|
|
|
30547
31516
|
}
|
|
30548
31517
|
|
|
30549
31518
|
// src/api/nango-control.ts
|
|
30550
|
-
import { createHash as
|
|
31519
|
+
import { createHash as createHash18, randomUUID as randomUUID18 } from "crypto";
|
|
30551
31520
|
|
|
30552
31521
|
// src/api/slack-archive-analysis.ts
|
|
30553
31522
|
function isRecord2(value) {
|
|
@@ -30823,17 +31792,19 @@ async function exportSlackChannelPage(input, dependencies) {
|
|
|
30823
31792
|
const state = decodeCursor(input.cursor, channelId, includeThreads);
|
|
30824
31793
|
const pageSize = Math.max(1, Math.min(200, Math.floor(input.pageSize)));
|
|
30825
31794
|
if (state.phase === "history") {
|
|
31795
|
+
const oldest = slackBoundaryTimestamp(input.from, -1);
|
|
31796
|
+
const latest = slackBoundaryTimestamp(input.to, 0);
|
|
30826
31797
|
const result = await callSlackToolWithRetry(dependencies, {
|
|
30827
31798
|
tool: "get-conversation-history",
|
|
30828
31799
|
input: {
|
|
30829
31800
|
channel_id: channelId,
|
|
30830
31801
|
limit: pageSize,
|
|
30831
|
-
oldest
|
|
30832
|
-
latest
|
|
31802
|
+
oldest,
|
|
31803
|
+
latest,
|
|
30833
31804
|
inclusive: false,
|
|
30834
31805
|
...state.historyCursor ? { cursor: state.historyCursor } : {}
|
|
30835
31806
|
},
|
|
30836
|
-
requestId: `slack-export:${input.connectionId}:${channelId}:history:${state.historyCursor ?? "start"}`
|
|
31807
|
+
requestId: `slack-export:${input.connectionId}:${channelId}:history:${oldest}:${latest}:${pageSize}:${state.historyCursor ?? "start"}`
|
|
30837
31808
|
});
|
|
30838
31809
|
const messages = messagesFromToolResult(result).slice(0, pageSize);
|
|
30839
31810
|
const historyCursor = nextCursorFromToolResult(result);
|
|
@@ -30885,7 +31856,7 @@ async function exportSlackChannelPage(input, dependencies) {
|
|
|
30885
31856
|
limit: perThreadLimit,
|
|
30886
31857
|
...thread.cursor ? { cursor: thread.cursor } : {}
|
|
30887
31858
|
},
|
|
30888
|
-
requestId: `slack-export:${input.connectionId}:${channelId}:thread:${thread.threadTs}:${thread.cursor ?? "start"}`
|
|
31859
|
+
requestId: `slack-export:${input.connectionId}:${channelId}:thread:${thread.threadTs}:${perThreadLimit}:${thread.cursor ?? "start"}`
|
|
30889
31860
|
})));
|
|
30890
31861
|
const records = [];
|
|
30891
31862
|
const stillPending = [];
|
|
@@ -30919,271 +31890,9 @@ async function exportSlackChannelPage(input, dependencies) {
|
|
|
30919
31890
|
}
|
|
30920
31891
|
|
|
30921
31892
|
// src/api/main-nango-transport.ts
|
|
30922
|
-
import { createHash as
|
|
31893
|
+
import { createHash as createHash17, randomUUID as randomUUID17 } from "crypto";
|
|
30923
31894
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
30924
31895
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
30925
|
-
|
|
30926
|
-
// src/api/service-connections.ts
|
|
30927
|
-
import { randomUUID as randomUUID15 } from "crypto";
|
|
30928
|
-
var schemaReady2 = null;
|
|
30929
|
-
var schemaDb2 = null;
|
|
30930
|
-
function ensureServiceConnectionsSchema() {
|
|
30931
|
-
const currentDb = getDb();
|
|
30932
|
-
if (schemaReady2 && schemaDb2 === currentDb) return schemaReady2;
|
|
30933
|
-
schemaDb2 = currentDb;
|
|
30934
|
-
schemaReady2 = (async () => {
|
|
30935
|
-
const db = currentDb;
|
|
30936
|
-
await db.execute(`
|
|
30937
|
-
CREATE TABLE IF NOT EXISTS service_connections (
|
|
30938
|
-
id TEXT PRIMARY KEY,
|
|
30939
|
-
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
30940
|
-
provider_config_key TEXT NOT NULL,
|
|
30941
|
-
provider TEXT,
|
|
30942
|
-
transport TEXT NOT NULL,
|
|
30943
|
-
upstream_connection_id TEXT,
|
|
30944
|
-
label TEXT,
|
|
30945
|
-
lifecycle_status TEXT NOT NULL DEFAULT 'pending',
|
|
30946
|
-
operational_status TEXT NOT NULL DEFAULT 'unknown',
|
|
30947
|
-
reconnect_required INTEGER NOT NULL DEFAULT 0,
|
|
30948
|
-
actions_enabled INTEGER NOT NULL DEFAULT 0,
|
|
30949
|
-
read_tools_json TEXT NOT NULL DEFAULT '[]',
|
|
30950
|
-
action_tools_json TEXT NOT NULL DEFAULT '[]',
|
|
30951
|
-
tool_revision TEXT,
|
|
30952
|
-
last_checked_at TEXT,
|
|
30953
|
-
last_successful_call_at TEXT,
|
|
30954
|
-
last_failure_at TEXT,
|
|
30955
|
-
last_failure_code TEXT,
|
|
30956
|
-
last_failure_retryable INTEGER,
|
|
30957
|
-
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
30958
|
-
vault_name TEXT,
|
|
30959
|
-
table_name TEXT,
|
|
30960
|
-
legacy_owner_identity TEXT,
|
|
30961
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
30962
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
30963
|
-
UNIQUE(user_id, provider_config_key, upstream_connection_id)
|
|
30964
|
-
)
|
|
30965
|
-
`);
|
|
30966
|
-
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
|
|
30967
|
-
await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
|
|
30968
|
-
await db.execute(`
|
|
30969
|
-
CREATE TABLE IF NOT EXISTS service_connection_health_events (
|
|
30970
|
-
id TEXT PRIMARY KEY,
|
|
30971
|
-
connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
|
|
30972
|
-
operational_status TEXT NOT NULL,
|
|
30973
|
-
failure_code TEXT,
|
|
30974
|
-
retryable INTEGER,
|
|
30975
|
-
evidence_source TEXT NOT NULL,
|
|
30976
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
30977
|
-
)
|
|
30978
|
-
`);
|
|
30979
|
-
await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
|
|
30980
|
-
await db.execute(`
|
|
30981
|
-
CREATE TABLE IF NOT EXISTS service_connection_action_audit (
|
|
30982
|
-
id TEXT PRIMARY KEY,
|
|
30983
|
-
connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
|
|
30984
|
-
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
30985
|
-
tool TEXT NOT NULL,
|
|
30986
|
-
request_id TEXT NOT NULL,
|
|
30987
|
-
status TEXT NOT NULL,
|
|
30988
|
-
error_code TEXT,
|
|
30989
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
30990
|
-
UNIQUE(user_id, request_id)
|
|
30991
|
-
)
|
|
30992
|
-
`);
|
|
30993
|
-
})().catch((error) => {
|
|
30994
|
-
schemaReady2 = null;
|
|
30995
|
-
schemaDb2 = null;
|
|
30996
|
-
throw error;
|
|
30997
|
-
});
|
|
30998
|
-
return schemaReady2;
|
|
30999
|
-
}
|
|
31000
|
-
function parseTools(value) {
|
|
31001
|
-
if (typeof value !== "string") return [];
|
|
31002
|
-
try {
|
|
31003
|
-
const parsed = JSON.parse(value);
|
|
31004
|
-
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
31005
|
-
} catch {
|
|
31006
|
-
return [];
|
|
31007
|
-
}
|
|
31008
|
-
}
|
|
31009
|
-
function rowToRecord(row) {
|
|
31010
|
-
return {
|
|
31011
|
-
id: String(row.id),
|
|
31012
|
-
userId: Number(row.user_id),
|
|
31013
|
-
providerConfigKey: String(row.provider_config_key),
|
|
31014
|
-
provider: typeof row.provider === "string" ? row.provider : null,
|
|
31015
|
-
transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
|
|
31016
|
-
upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
|
|
31017
|
-
label: typeof row.label === "string" ? row.label : null,
|
|
31018
|
-
lifecycleStatus: String(row.lifecycle_status),
|
|
31019
|
-
operationalStatus: String(row.operational_status),
|
|
31020
|
-
reconnectRequired: Number(row.reconnect_required) === 1,
|
|
31021
|
-
actionsEnabled: Number(row.actions_enabled) === 1,
|
|
31022
|
-
readTools: parseTools(row.read_tools_json),
|
|
31023
|
-
actionTools: parseTools(row.action_tools_json),
|
|
31024
|
-
toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
|
|
31025
|
-
lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
|
|
31026
|
-
lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
|
|
31027
|
-
lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
|
|
31028
|
-
lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
|
|
31029
|
-
lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
|
|
31030
|
-
consecutiveFailures: Number(row.consecutive_failures ?? 0),
|
|
31031
|
-
vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
|
|
31032
|
-
tableName: typeof row.table_name === "string" ? row.table_name : null,
|
|
31033
|
-
createdAt: String(row.created_at),
|
|
31034
|
-
updatedAt: String(row.updated_at)
|
|
31035
|
-
};
|
|
31036
|
-
}
|
|
31037
|
-
async function userIdForIdentity(identity) {
|
|
31038
|
-
const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
|
|
31039
|
-
if (!user) throw new Error("service_connection_user_not_found");
|
|
31040
|
-
return Number(user.id);
|
|
31041
|
-
}
|
|
31042
|
-
async function reconcileDiscoveredNangoConnections(identity, discovered) {
|
|
31043
|
-
await ensureServiceConnectionsSchema();
|
|
31044
|
-
const db = getDb();
|
|
31045
|
-
const userId = await userIdForIdentity(identity);
|
|
31046
|
-
for (const connection of discovered) {
|
|
31047
|
-
await db.execute({
|
|
31048
|
-
sql: `
|
|
31049
|
-
INSERT INTO service_connections (
|
|
31050
|
-
id, user_id, provider_config_key, provider, transport, upstream_connection_id,
|
|
31051
|
-
label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
|
|
31052
|
-
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
31053
|
-
ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
|
|
31054
|
-
provider = excluded.provider,
|
|
31055
|
-
label = COALESCE(excluded.label, service_connections.label),
|
|
31056
|
-
lifecycle_status = excluded.lifecycle_status,
|
|
31057
|
-
reconnect_required = excluded.reconnect_required,
|
|
31058
|
-
updated_at = excluded.updated_at
|
|
31059
|
-
`,
|
|
31060
|
-
args: [
|
|
31061
|
-
randomUUID15(),
|
|
31062
|
-
userId,
|
|
31063
|
-
connection.providerConfigKey,
|
|
31064
|
-
connection.provider,
|
|
31065
|
-
connection.upstreamConnectionId,
|
|
31066
|
-
connection.label,
|
|
31067
|
-
connection.lifecycleStatus,
|
|
31068
|
-
connection.reconnectRequired ? 1 : 0,
|
|
31069
|
-
identity,
|
|
31070
|
-
connection.createdAt,
|
|
31071
|
-
connection.updatedAt
|
|
31072
|
-
]
|
|
31073
|
-
});
|
|
31074
|
-
}
|
|
31075
|
-
return listServiceConnections(identity, "nango");
|
|
31076
|
-
}
|
|
31077
|
-
async function listServiceConnections(identity, transport) {
|
|
31078
|
-
await ensureServiceConnectionsSchema();
|
|
31079
|
-
const userId = await userIdForIdentity(identity);
|
|
31080
|
-
const result = await getDb().execute({
|
|
31081
|
-
sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
|
|
31082
|
-
args: transport ? [userId, transport] : [userId]
|
|
31083
|
-
});
|
|
31084
|
-
return result.rows.map((row) => rowToRecord(row));
|
|
31085
|
-
}
|
|
31086
|
-
async function getOwnedServiceConnection(identity, connectionId) {
|
|
31087
|
-
await ensureServiceConnectionsSchema();
|
|
31088
|
-
const userId = await userIdForIdentity(identity);
|
|
31089
|
-
const result = await getDb().execute({
|
|
31090
|
-
sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
|
|
31091
|
-
args: [connectionId, userId]
|
|
31092
|
-
});
|
|
31093
|
-
const row = result.rows[0];
|
|
31094
|
-
return row ? rowToRecord(row) : null;
|
|
31095
|
-
}
|
|
31096
|
-
async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
|
|
31097
|
-
await ensureServiceConnectionsSchema();
|
|
31098
|
-
await getDb().execute({
|
|
31099
|
-
sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
31100
|
-
args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
|
|
31101
|
-
});
|
|
31102
|
-
}
|
|
31103
|
-
async function recordServiceConnectionHealth(args) {
|
|
31104
|
-
await ensureServiceConnectionsSchema();
|
|
31105
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31106
|
-
await getDb().execute({
|
|
31107
|
-
sql: `
|
|
31108
|
-
UPDATE service_connections SET
|
|
31109
|
-
operational_status = ?, last_checked_at = ?,
|
|
31110
|
-
last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
|
|
31111
|
-
last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
|
|
31112
|
-
last_failure_code = ?, last_failure_retryable = ?,
|
|
31113
|
-
consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
|
|
31114
|
-
updated_at = datetime('now')
|
|
31115
|
-
WHERE id = ?
|
|
31116
|
-
`,
|
|
31117
|
-
args: [
|
|
31118
|
-
args.operationalStatus,
|
|
31119
|
-
now,
|
|
31120
|
-
args.operationalStatus,
|
|
31121
|
-
now,
|
|
31122
|
-
args.operationalStatus,
|
|
31123
|
-
now,
|
|
31124
|
-
args.failureCode ?? null,
|
|
31125
|
-
args.retryable == null ? null : args.retryable ? 1 : 0,
|
|
31126
|
-
args.operationalStatus,
|
|
31127
|
-
args.connectionId
|
|
31128
|
-
]
|
|
31129
|
-
});
|
|
31130
|
-
await getDb().execute({
|
|
31131
|
-
sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
31132
|
-
args: [randomUUID15(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
|
|
31133
|
-
});
|
|
31134
|
-
}
|
|
31135
|
-
async function setServiceConnectionActions(identity, connectionId, enabled) {
|
|
31136
|
-
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
31137
|
-
if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
|
|
31138
|
-
await getDb().execute({
|
|
31139
|
-
sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
31140
|
-
args: [enabled ? 1 : 0, connectionId, connection.userId]
|
|
31141
|
-
});
|
|
31142
|
-
return enabled;
|
|
31143
|
-
}
|
|
31144
|
-
async function markServiceConnectionDisconnected(identity, connectionId) {
|
|
31145
|
-
const connection = await getOwnedServiceConnection(identity, connectionId);
|
|
31146
|
-
if (!connection) throw new Error("service_connection_not_found");
|
|
31147
|
-
await getDb().execute({
|
|
31148
|
-
sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
|
|
31149
|
-
args: [connectionId, connection.userId]
|
|
31150
|
-
});
|
|
31151
|
-
const remaining = await getDb().execute({
|
|
31152
|
-
sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
|
|
31153
|
-
args: [connection.userId]
|
|
31154
|
-
});
|
|
31155
|
-
return Number(remaining.rows[0]?.count ?? 0);
|
|
31156
|
-
}
|
|
31157
|
-
async function recordServiceConnectionAction(args) {
|
|
31158
|
-
await ensureServiceConnectionsSchema();
|
|
31159
|
-
const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
|
|
31160
|
-
if (!connection) throw new Error("service_connection_not_found");
|
|
31161
|
-
await getDb().execute({
|
|
31162
|
-
sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
|
|
31163
|
-
args: [args.status, args.errorCode ?? null, connection.userId, args.requestId, connection.id]
|
|
31164
|
-
});
|
|
31165
|
-
}
|
|
31166
|
-
async function claimServiceConnectionAction(args) {
|
|
31167
|
-
await ensureServiceConnectionsSchema();
|
|
31168
|
-
const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
|
|
31169
|
-
if (!connection) throw new Error("service_connection_not_found");
|
|
31170
|
-
const inserted = await getDb().execute({
|
|
31171
|
-
sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status) VALUES (?, ?, ?, ?, ?, 'started')`,
|
|
31172
|
-
args: [randomUUID15(), connection.id, connection.userId, args.tool, args.requestId]
|
|
31173
|
-
});
|
|
31174
|
-
if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
|
|
31175
|
-
const existing = await getDb().execute({
|
|
31176
|
-
sql: `SELECT status, error_code FROM service_connection_action_audit WHERE user_id = ? AND request_id = ? LIMIT 1`,
|
|
31177
|
-
args: [connection.userId, args.requestId]
|
|
31178
|
-
});
|
|
31179
|
-
return {
|
|
31180
|
-
claimed: false,
|
|
31181
|
-
status: String(existing.rows[0]?.status ?? "unknown"),
|
|
31182
|
-
errorCode: typeof existing.rows[0]?.error_code === "string" ? existing.rows[0].error_code : null
|
|
31183
|
-
};
|
|
31184
|
-
}
|
|
31185
|
-
|
|
31186
|
-
// src/api/main-nango-transport.ts
|
|
31187
31896
|
var DEFAULT_NANGO_MCP_URL = "https://api.nango.dev/mcp";
|
|
31188
31897
|
var NANGO_TIMEOUT_MS = 2e4;
|
|
31189
31898
|
var NANGO_CONNECTION_PAGE_SIZE = 100;
|
|
@@ -31528,7 +32237,7 @@ async function listNangoToolsDirect(identity, connectionId) {
|
|
|
31528
32237
|
});
|
|
31529
32238
|
const readTools = tools.filter((tool) => classifyTool(tool) === "read").map((tool) => tool.name);
|
|
31530
32239
|
const actionTools = tools.filter((tool) => classifyTool(tool) === "action").map((tool) => tool.name);
|
|
31531
|
-
const revision =
|
|
32240
|
+
const revision = createHash17("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
|
|
31532
32241
|
await updateServiceConnectionTools(connection.id, readTools, actionTools, revision);
|
|
31533
32242
|
const refreshed = await getOwnedServiceConnection(identity, connection.id);
|
|
31534
32243
|
return { connection: refreshed ?? { ...connection, readTools, actionTools, toolRevision: revision }, tools };
|
|
@@ -31548,8 +32257,8 @@ async function callNangoToolDirect(args) {
|
|
|
31548
32257
|
identity: args.identity,
|
|
31549
32258
|
ratePolicyVersion: CONNECTED_USAGE_RATE_POLICY_VERSION
|
|
31550
32259
|
});
|
|
31551
|
-
const requestId = args.requestId?.trim() ||
|
|
31552
|
-
const idempotencyKey2 = `main-nango:${
|
|
32260
|
+
const requestId = args.requestId?.trim() || randomUUID17();
|
|
32261
|
+
const idempotencyKey2 = `main-nango:${createHash17("sha256").update(args.identity.toLowerCase()).update("\0").update(connection.id).update("\0").update(args.tool).update("\0").update(requestId).digest("hex")}`;
|
|
31553
32262
|
const startedAt = /* @__PURE__ */ new Date();
|
|
31554
32263
|
const started = performance.now();
|
|
31555
32264
|
let result;
|
|
@@ -31574,7 +32283,7 @@ async function callNangoToolDirect(args) {
|
|
|
31574
32283
|
toolName: args.tool,
|
|
31575
32284
|
operationKind: args.operationKind ?? args.classification,
|
|
31576
32285
|
outcome: providerError ? "error" : "partial",
|
|
31577
|
-
requestId: requestId.length <= 200 ? requestId :
|
|
32286
|
+
requestId: requestId.length <= 200 ? requestId : createHash17("sha256").update(requestId).digest("hex"),
|
|
31578
32287
|
startedAt: startedAt.toISOString(),
|
|
31579
32288
|
completedAt: completedAt.toISOString()
|
|
31580
32289
|
}
|
|
@@ -31611,7 +32320,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
|
|
|
31611
32320
|
providerContractHash: MAIN_INTEGRATION_CONTRACT_HASH,
|
|
31612
32321
|
protocolVersion: null,
|
|
31613
32322
|
schemaSource: "live_tools_list",
|
|
31614
|
-
schemaHash:
|
|
32323
|
+
schemaHash: createHash17("sha256").update(JSON.stringify(projected)).digest("hex"),
|
|
31615
32324
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31616
32325
|
};
|
|
31617
32326
|
}
|
|
@@ -32335,7 +33044,7 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
|
|
|
32335
33044
|
return data.connection.actionsEnabled === true;
|
|
32336
33045
|
}
|
|
32337
33046
|
async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
|
|
32338
|
-
const requestId = `main-connected-action:${
|
|
33047
|
+
const requestId = `main-connected-action:${createHash18("sha256").update(identity).update("\0").update(idempotencyKey2?.trim() || randomUUID18()).digest("hex")}`;
|
|
32339
33048
|
if (mainOwnsIntegrations()) {
|
|
32340
33049
|
const selectedTool = tool?.trim();
|
|
32341
33050
|
if (!selectedTool) throw new NangoControlError("An action tool is required.", 400, "invalid_request", false);
|
|
@@ -32477,7 +33186,7 @@ function canonicalJson2(value) {
|
|
|
32477
33186
|
return JSON.stringify(value);
|
|
32478
33187
|
}
|
|
32479
33188
|
function projectedToolSchemaHash(tool) {
|
|
32480
|
-
return
|
|
33189
|
+
return createHash18("sha256").update(canonicalJson2(tool)).digest("hex");
|
|
32481
33190
|
}
|
|
32482
33191
|
async function describeNangoTool(identity, connectionId, tool, fresh) {
|
|
32483
33192
|
if (mainOwnsIntegrations()) {
|
|
@@ -33078,78 +33787,6 @@ async function callResendExportPage(identity, input) {
|
|
|
33078
33787
|
};
|
|
33079
33788
|
}
|
|
33080
33789
|
|
|
33081
|
-
// src/api/scheduler-integration-auth.ts
|
|
33082
|
-
import { createHash as createHash15, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
33083
|
-
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
33084
|
-
var SchedulerIntegrationAuthError = class extends Error {
|
|
33085
|
-
code;
|
|
33086
|
-
constructor(code) {
|
|
33087
|
-
super(code);
|
|
33088
|
-
this.name = "SchedulerIntegrationAuthError";
|
|
33089
|
-
this.code = code;
|
|
33090
|
-
}
|
|
33091
|
-
};
|
|
33092
|
-
function signingSecret() {
|
|
33093
|
-
const secret2 = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();
|
|
33094
|
-
if (!secret2) throw new SchedulerIntegrationAuthError("not_configured");
|
|
33095
|
-
return secret2;
|
|
33096
|
-
}
|
|
33097
|
-
function schedulerIntegrationSignature(args) {
|
|
33098
|
-
const bodyHash = createHash15("sha256").update(args.body).digest("hex");
|
|
33099
|
-
return createHmac4("sha256", args.secret).update(`${args.method.toUpperCase()}
|
|
33100
|
-
${args.path}
|
|
33101
|
-
${args.timestamp}
|
|
33102
|
-
${args.nonce}
|
|
33103
|
-
${bodyHash}`).digest("hex");
|
|
33104
|
-
}
|
|
33105
|
-
async function claimNonce(nonce, timestampMs) {
|
|
33106
|
-
await ensureServiceConnectionsSchema();
|
|
33107
|
-
await getDb().execute(`
|
|
33108
|
-
CREATE TABLE IF NOT EXISTS service_connection_scheduler_nonces (
|
|
33109
|
-
nonce TEXT PRIMARY KEY,
|
|
33110
|
-
expires_at TEXT NOT NULL,
|
|
33111
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
33112
|
-
)
|
|
33113
|
-
`);
|
|
33114
|
-
await getDb().execute(`DELETE FROM service_connection_scheduler_nonces WHERE expires_at < datetime('now')`);
|
|
33115
|
-
try {
|
|
33116
|
-
await getDb().execute({
|
|
33117
|
-
sql: `INSERT INTO service_connection_scheduler_nonces (nonce, expires_at) VALUES (?, ?)`,
|
|
33118
|
-
args: [nonce, new Date(timestampMs + MAX_CLOCK_SKEW_MS).toISOString()]
|
|
33119
|
-
});
|
|
33120
|
-
} catch {
|
|
33121
|
-
throw new SchedulerIntegrationAuthError("replayed_request");
|
|
33122
|
-
}
|
|
33123
|
-
}
|
|
33124
|
-
async function verifySchedulerIntegrationRequest(request, rawBody) {
|
|
33125
|
-
const timestamp = request.headers.get("x-scheduler-timestamp")?.trim() ?? "";
|
|
33126
|
-
const nonce = request.headers.get("x-scheduler-nonce")?.trim() ?? "";
|
|
33127
|
-
const signature = request.headers.get("x-scheduler-signature")?.trim().toLowerCase() ?? "";
|
|
33128
|
-
const requestId = request.headers.get("x-scheduler-request-id")?.trim() ?? "";
|
|
33129
|
-
const timestampMs = Date.parse(timestamp);
|
|
33130
|
-
if (!timestamp || !nonce || !signature || !requestId || nonce.length > 200 || requestId.length > 200 || !Number.isFinite(timestampMs)) {
|
|
33131
|
-
throw new SchedulerIntegrationAuthError("invalid_signature");
|
|
33132
|
-
}
|
|
33133
|
-
if (Math.abs(Date.now() - timestampMs) > MAX_CLOCK_SKEW_MS) {
|
|
33134
|
-
throw new SchedulerIntegrationAuthError("expired_request");
|
|
33135
|
-
}
|
|
33136
|
-
const expected = schedulerIntegrationSignature({
|
|
33137
|
-
method: request.method,
|
|
33138
|
-
path: new URL(request.url).pathname,
|
|
33139
|
-
timestamp,
|
|
33140
|
-
nonce,
|
|
33141
|
-
body: rawBody,
|
|
33142
|
-
secret: signingSecret()
|
|
33143
|
-
});
|
|
33144
|
-
const suppliedBytes = Buffer.from(signature, "hex");
|
|
33145
|
-
const expectedBytes = Buffer.from(expected, "hex");
|
|
33146
|
-
if (suppliedBytes.length !== expectedBytes.length || !timingSafeEqual3(suppliedBytes, expectedBytes)) {
|
|
33147
|
-
throw new SchedulerIntegrationAuthError("invalid_signature");
|
|
33148
|
-
}
|
|
33149
|
-
await claimNonce(nonce, timestampMs);
|
|
33150
|
-
return { requestId };
|
|
33151
|
-
}
|
|
33152
|
-
|
|
33153
33790
|
// src/api/server.ts
|
|
33154
33791
|
var secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
33155
33792
|
var isProduction2 = secureCookies2;
|
|
@@ -33211,9 +33848,9 @@ var auth2 = createMiddleware4(async (c, next) => {
|
|
|
33211
33848
|
return next();
|
|
33212
33849
|
});
|
|
33213
33850
|
var sessionAuth = createMiddleware4(async (c, next) => {
|
|
33214
|
-
const
|
|
33215
|
-
if (!
|
|
33216
|
-
const userId = verifySession(
|
|
33851
|
+
const token3 = getCookie2(c, "session");
|
|
33852
|
+
if (!token3) return c.json({ error: "Not authenticated" }, 401);
|
|
33853
|
+
const userId = verifySession(token3);
|
|
33217
33854
|
if (!userId) return c.json({ error: "Session expired" }, 401);
|
|
33218
33855
|
const user = await getUserById(userId);
|
|
33219
33856
|
if (!user) return c.json({ error: "User not found" }, 401);
|
|
@@ -33238,7 +33875,7 @@ var requirePaidSchedulingTier = createMiddleware4(async (c, next) => {
|
|
|
33238
33875
|
if (!hasPaidSchedulingAccess(user)) return c.json(paidPlanRequiredPayload(), 403);
|
|
33239
33876
|
return next();
|
|
33240
33877
|
});
|
|
33241
|
-
var app = new
|
|
33878
|
+
var app = new Hono33();
|
|
33242
33879
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
33243
33880
|
function requireStripeSecret() {
|
|
33244
33881
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -33355,8 +33992,8 @@ app.post("/auth/register", requireAllowedOrigin, async (c) => {
|
|
|
33355
33992
|
console.warn("[auth/register] memory provision failed (will retry on first write):", err instanceof Error ? err.message : String(err));
|
|
33356
33993
|
}
|
|
33357
33994
|
})();
|
|
33358
|
-
const
|
|
33359
|
-
setCookie2(c, "session",
|
|
33995
|
+
const token3 = signSession(user.id);
|
|
33996
|
+
setCookie2(c, "session", token3, sessionCookieOptions2);
|
|
33360
33997
|
return c.json({ email: user.email, api_key: newKey }, 201);
|
|
33361
33998
|
} catch (err) {
|
|
33362
33999
|
const msg = err instanceof Error ? err.message : "";
|
|
@@ -33374,8 +34011,8 @@ app.post("/auth/login", requireAllowedOrigin, async (c) => {
|
|
|
33374
34011
|
if (!user || !user.password_hash || !verifyPassword(password, user.password_hash)) {
|
|
33375
34012
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
33376
34013
|
}
|
|
33377
|
-
const
|
|
33378
|
-
setCookie2(c, "session",
|
|
34014
|
+
const token3 = signSession(user.id);
|
|
34015
|
+
setCookie2(c, "session", token3, sessionCookieOptions2);
|
|
33379
34016
|
return c.json({ email: user.email, api_key: user.key_active ? user.api_key : null });
|
|
33380
34017
|
});
|
|
33381
34018
|
app.post("/auth/logout", requireAllowedOrigin, (c) => {
|
|
@@ -33430,9 +34067,9 @@ app.post("/auth/forgot-password", requireAllowedOrigin, async (c) => {
|
|
|
33430
34067
|
if (emailLimited) return emailLimited;
|
|
33431
34068
|
const user = await getUserByEmail(normalizedEmail);
|
|
33432
34069
|
if (!user) return c.json({ ok: true });
|
|
33433
|
-
const
|
|
34070
|
+
const token3 = await createPasswordResetToken(user.id);
|
|
33434
34071
|
const appUrl = process.env.APP_URL ?? "https://mcpscraper.dev";
|
|
33435
|
-
const resetUrl = `${appUrl}/reset-password?token=${
|
|
34072
|
+
const resetUrl = `${appUrl}/reset-password?token=${token3}`;
|
|
33436
34073
|
if (process.env.RESEND_API_KEY) {
|
|
33437
34074
|
try {
|
|
33438
34075
|
const resend = new Resend3(process.env.RESEND_API_KEY);
|
|
@@ -33452,12 +34089,12 @@ app.post("/auth/forgot-password", requireAllowedOrigin, async (c) => {
|
|
|
33452
34089
|
return c.json({ ok: true });
|
|
33453
34090
|
});
|
|
33454
34091
|
app.post("/auth/reset-password", requireAllowedOrigin, async (c) => {
|
|
33455
|
-
const { token:
|
|
33456
|
-
if (!
|
|
34092
|
+
const { token: token3, password } = await c.req.json();
|
|
34093
|
+
if (!token3 || !password) return c.json({ error: "Token and password required" }, 400);
|
|
33457
34094
|
if (password.length < 8) return c.json({ error: "Password must be at least 8 characters" }, 400);
|
|
33458
34095
|
const limited = await enforceRateLimit(c, "auth_reset", rateLimitKey(c), 10, 60 * 60);
|
|
33459
34096
|
if (limited) return limited;
|
|
33460
|
-
const userId = await consumePasswordResetToken(
|
|
34097
|
+
const userId = await consumePasswordResetToken(token3);
|
|
33461
34098
|
if (!userId) return c.json({ error: "Invalid or expired reset link" }, 400);
|
|
33462
34099
|
await setPassword(userId, password);
|
|
33463
34100
|
return c.json({ ok: true });
|
|
@@ -33494,9 +34131,9 @@ function sessionBootstrapPayload(user) {
|
|
|
33494
34131
|
}
|
|
33495
34132
|
app.get("/me", async (c) => {
|
|
33496
34133
|
c.header("Cache-Control", "no-store");
|
|
33497
|
-
const
|
|
33498
|
-
if (!
|
|
33499
|
-
const userId = verifySession(
|
|
34134
|
+
const token3 = getCookie2(c, "session");
|
|
34135
|
+
if (!token3) return c.json({ authenticated: false });
|
|
34136
|
+
const userId = verifySession(token3);
|
|
33500
34137
|
if (!userId) {
|
|
33501
34138
|
deleteCookie(c, "session", { path: "/", secure: secureCookies2, sameSite: "Strict" });
|
|
33502
34139
|
return c.json({ authenticated: false });
|
|
@@ -34738,11 +35375,11 @@ app.put("/schedule-actions/:id", auth2, requirePaidSchedulingTier, async (c) =>
|
|
|
34738
35375
|
if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
|
|
34739
35376
|
const body = await c.req.json().catch(() => ({}));
|
|
34740
35377
|
const updates = {};
|
|
34741
|
-
for (const field of ["description", "vault", "cadence", "timeOfDay", "timezone"]) {
|
|
35378
|
+
for (const field of ["description", "vault", "cadence", "timeOfDay", "timezone", "artifactSelection"]) {
|
|
34742
35379
|
if (Object.prototype.hasOwnProperty.call(body, field)) updates[field] = body[field];
|
|
34743
35380
|
}
|
|
34744
35381
|
if (Object.keys(updates).length === 0) {
|
|
34745
|
-
return c.json({ ok: false, error: "Provide at least one of description, vault, cadence, timeOfDay, timezone." }, 400);
|
|
35382
|
+
return c.json({ ok: false, error: "Provide at least one of description, vault, cadence, timeOfDay, timezone, artifactSelection." }, 400);
|
|
34746
35383
|
}
|
|
34747
35384
|
const r = await memoryCall("updateScheduledActionTool", { id: c.req.param("id"), ...updates }, key);
|
|
34748
35385
|
return c.json(r);
|
|
@@ -34910,7 +35547,7 @@ app.post("/harvest", auth2, async (c) => {
|
|
|
34910
35547
|
if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
|
|
34911
35548
|
jobId2 = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
|
|
34912
35549
|
} else {
|
|
34913
|
-
jobId2 =
|
|
35550
|
+
jobId2 = randomUUID19();
|
|
34914
35551
|
const billingDebitKey = `paa-harvest:${jobId2}:hold`;
|
|
34915
35552
|
const description = `PAA harvest: ${options.query}`.slice(0, 500);
|
|
34916
35553
|
const hold = await debitMcIdempotent(
|
|
@@ -36064,7 +36701,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
36064
36701
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
36065
36702
|
return c.json({ error: "Unauthorized" }, 401);
|
|
36066
36703
|
}
|
|
36067
|
-
const { drainQueue } = await import("./worker-
|
|
36704
|
+
const { drainQueue } = await import("./worker-4R3UWQZS.js");
|
|
36068
36705
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
36069
36706
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
36070
36707
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation] = await Promise.all([
|
|
@@ -36117,8 +36754,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
36117
36754
|
finishExtractJob: finishExtractJob2,
|
|
36118
36755
|
terminalExtractJobStatus: terminalExtractJobStatus2,
|
|
36119
36756
|
extractJobLimitInfo: extractJobLimitInfo2
|
|
36120
|
-
} = await import("./site-extract-repository-
|
|
36121
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
36757
|
+
} = await import("./site-extract-repository-B42SIGAZ.js");
|
|
36758
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-DMZ63HZU.js");
|
|
36122
36759
|
const existing = await getExtractJob2(jobId2);
|
|
36123
36760
|
if (!existing) return c.json({ error: "job not found" }, 404);
|
|
36124
36761
|
if (existing.status !== "failed") {
|
|
@@ -36145,7 +36782,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
36145
36782
|
if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
|
|
36146
36783
|
let settlement = "already_settled_or_refunded";
|
|
36147
36784
|
if (job.billedMc == null && job.userId != null) {
|
|
36148
|
-
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
36785
|
+
const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-B42SIGAZ.js");
|
|
36149
36786
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
36150
36787
|
const successful = await countSuccessfulPages2(jobId2);
|
|
36151
36788
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -36192,6 +36829,9 @@ app.route("/schedule", scheduleApp);
|
|
|
36192
36829
|
app.route("/resend", resendInboundApp);
|
|
36193
36830
|
app.route("/workshops", workshopRegistrationApp);
|
|
36194
36831
|
app.route("/editorial-reading-room", editorialReadingRoomApp);
|
|
36832
|
+
app.route("/", scheduledResultApp);
|
|
36833
|
+
app.route("/public", publicScheduledResultApp);
|
|
36834
|
+
app.route("/api/internal/scheduled-artifacts", scheduledArtifactInternalApp);
|
|
36195
36835
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
36196
36836
|
app.get("/console/auth/:id", async (c) => {
|
|
36197
36837
|
const id = c.req.param("id");
|
|
@@ -36321,4 +36961,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
36321
36961
|
export {
|
|
36322
36962
|
app
|
|
36323
36963
|
};
|
|
36324
|
-
//# sourceMappingURL=server-
|
|
36964
|
+
//# sourceMappingURL=server-MVCGZ4OK.js.map
|