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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/api-server.cjs +4331 -3081
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +2 -2
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +2 -2
  11. package/dist/bin/mcp-stdio-server.cjs +2821 -2309
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +9 -6
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/bin/paa-harvest.cjs.map +1 -1
  16. package/dist/bin/paa-harvest.js +3 -3
  17. package/dist/{chunk-6J57U6HA.js → chunk-4MFUKI4X.js} +2 -2
  18. package/dist/{chunk-SIE5LZ2V.js → chunk-6O6DPKLX.js} +2983 -2450
  19. package/dist/chunk-6O6DPKLX.js.map +1 -0
  20. package/dist/{chunk-QZXKQB7Y.js → chunk-6Q7V4IQV.js} +2 -2
  21. package/dist/{chunk-EHES33KB.js → chunk-DAS7L4SF.js} +2 -2
  22. package/dist/{chunk-JVIW4GK2.js → chunk-DH42LIR3.js} +2 -2
  23. package/dist/{chunk-XELSA2MS.js → chunk-ENJQ2NSX.js} +2 -2
  24. package/dist/{chunk-XELSA2MS.js.map → chunk-ENJQ2NSX.js.map} +1 -1
  25. package/dist/{chunk-IQTT7CAB.js → chunk-FCFQ634B.js} +28 -2
  26. package/dist/chunk-FCFQ634B.js.map +1 -0
  27. package/dist/{chunk-345BQXZH.js → chunk-MJKVDLVR.js} +18 -13
  28. package/dist/chunk-MJKVDLVR.js.map +1 -0
  29. package/dist/{chunk-27DUCRAZ.js → chunk-QOWBJ4YY.js} +2 -2
  30. package/dist/chunk-S5OVG2MK.js +7 -0
  31. package/dist/chunk-S5OVG2MK.js.map +1 -0
  32. package/dist/{chunk-CS4HE6QY.js → chunk-ZBQ6EXZW.js} +2 -2
  33. package/dist/{db-LVVU6NOK.js → db-C5ELI55I.js} +2 -2
  34. package/dist/{extract-bundle-SKXEDG4Z.js → extract-bundle-DMZ63HZU.js} +5 -5
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.js +3 -3
  37. package/dist/{location-data-repository-NBTCOA7B.js → location-data-repository-SHEOIBTL.js} +3 -3
  38. package/dist/{server-2JJPCZH4.js → server-MVCGZ4OK.js} +1160 -520
  39. package/dist/server-MVCGZ4OK.js.map +1 -0
  40. package/dist/{site-extract-repository-6GX72U4N.js → site-extract-repository-B42SIGAZ.js} +3 -3
  41. package/dist/{worker-5RWLFSAJ.js → worker-4R3UWQZS.js} +5 -5
  42. package/package.json +1 -1
  43. package/dist/chunk-345BQXZH.js.map +0 -1
  44. package/dist/chunk-IQTT7CAB.js.map +0 -1
  45. package/dist/chunk-OOB35KFT.js +0 -7
  46. package/dist/chunk-OOB35KFT.js.map +0 -1
  47. package/dist/chunk-SIE5LZ2V.js.map +0 -1
  48. package/dist/server-2JJPCZH4.js.map +0 -1
  49. /package/dist/{chunk-6J57U6HA.js.map → chunk-4MFUKI4X.js.map} +0 -0
  50. /package/dist/{chunk-QZXKQB7Y.js.map → chunk-6Q7V4IQV.js.map} +0 -0
  51. /package/dist/{chunk-EHES33KB.js.map → chunk-DAS7L4SF.js.map} +0 -0
  52. /package/dist/{chunk-JVIW4GK2.js.map → chunk-DH42LIR3.js.map} +0 -0
  53. /package/dist/{chunk-27DUCRAZ.js.map → chunk-QOWBJ4YY.js.map} +0 -0
  54. /package/dist/{chunk-CS4HE6QY.js.map → chunk-ZBQ6EXZW.js.map} +0 -0
  55. /package/dist/{db-LVVU6NOK.js.map → db-C5ELI55I.js.map} +0 -0
  56. /package/dist/{extract-bundle-SKXEDG4Z.js.map → extract-bundle-DMZ63HZU.js.map} +0 -0
  57. /package/dist/{location-data-repository-NBTCOA7B.js.map → location-data-repository-SHEOIBTL.js.map} +0 -0
  58. /package/dist/{site-extract-repository-6GX72U4N.js.map → site-extract-repository-B42SIGAZ.js.map} +0 -0
  59. /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-QZXKQB7Y.js";
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-27DUCRAZ.js";
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-CS4HE6QY.js";
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-JVIW4GK2.js";
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-SIE5LZ2V.js";
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-345BQXZH.js";
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-EHES33KB.js";
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-OOB35KFT.js";
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-6J57U6HA.js";
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-IQTT7CAB.js";
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 randomUUID18 } from "crypto";
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 Hono30 } from "hono";
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-SKXEDG4Z.js");
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-NBTCOA7B.js");
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-NBTCOA7B.js");
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(token2) {
12639
- return createHash3("sha256").update(token2).digest("hex");
12652
+ function tokenHash(token3) {
12653
+ return createHash3("sha256").update(token3).digest("hex");
12640
12654
  }
12641
- function verifyToken(row, token2) {
12655
+ function verifyToken(row, token3) {
12642
12656
  const expected = Buffer.from(row.token_hash, "hex");
12643
- const actual = Buffer.from(tokenHash(token2), "hex");
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 token2 = tokenFor(id, idempotencyKey2);
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(token2), safeMetadata2(args.metadata), expiresAt]
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: token2,
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(token2) {
13051
- const dot = token2.lastIndexOf(".");
13064
+ function verifySession(token3) {
13065
+ const dot = token3.lastIndexOf(".");
13052
13066
  if (dot === -1) return null;
13053
- const payload = token2.slice(0, dot);
13054
- const sig = token2.slice(dot + 1);
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: token2, ctx }) => {
14397
+ async ({ ep, token: token3, ctx }) => {
14384
14398
  const body = {
14385
- continuation: token2,
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 token2 of queryTokens) if (candidateTokens.has(token2)) overlap++;
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(token2) {
22642
- return !token2.startsWith("sk_") && token2.split(".").length === 3;
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(token2) {
22651
- const { payload } = await jwtVerify(token2, getJwks(), {
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(token2) {
25355
- if (!token2) return null;
25356
- const userId = verifySession(token2);
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(token2) {
25704
- return !!token2 && token2.startsWith("mk_") && token2.length > 10;
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 token2 = c.req.query("token");
25711
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
25712
- const r = await memoryCall("listVaultsTool", {}, token2);
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 token2 = c.req.query("token");
25719
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
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 }, token2);
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 token2 = c.req.query("token");
25728
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
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 }, token2);
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 token2 = c.req.query("token");
25757
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
25758
- const vaultsRes = await memoryCall("listVaultsTool", {}, token2);
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 }, token2)));
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 token2 = c.req.query("token");
25770
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
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 }, token2);
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 token2 = c.req.query("token");
25786
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
25787
- const r = await memoryCall("myMentionsTool", {}, token2);
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 token2 = c.req.query("token");
25792
- if (!tokenOk(token2)) return jsonError("missing or malformed token", 401);
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 }, token2);
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 token2 = c.req.param("token");
25832
- if (!tokenOk(token2)) {
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(token2));
25850
+ return c.html(renderChatPage(token3));
25836
25851
  });
25837
- function renderChatPage(token2) {
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(token2)};
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(token2) {
26393
- return !!token2 && token2.startsWith("mk_") && token2.length > 20 && token2.length < 256;
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, token2) {
26520
- let result = await memoryCall(tool, args, token2);
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, token2);
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, token2) {
26541
- const list = await readMemory("listTool", { vault }, token2);
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 }, token2)
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 token2 = c.req.query("token");
26555
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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, token2));
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 token2 = typeof body.token === "string" ? body.token : void 0;
26570
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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 } }, token2);
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 token2 = typeof body.token === "string" ? body.token : void 0;
26587
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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 }, token2);
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
- }, token2);
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 token2 = typeof body.token === "string" ? body.token : void 0;
26608
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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 }, token2);
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
- }, token2);
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 token2 = typeof body.token === "string" ? body.token : void 0;
26636
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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 }, token2);
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
- }, token2);
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 token2 = typeof body.token === "string" ? body.token : void 0;
26656
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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 }, token2);
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
- }, token2);
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 token2 = typeof body.token === "string" ? body.token : void 0;
26697
- if (!validToken(token2)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
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::/, "") }, token2);
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
- }, token2);
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(token2) {
26743
- return !!token2 && token2.startsWith("mk_") && token2.length > 10;
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 token2 = c.req.query("token");
26750
- if (!tokenOk2(token2)) return jsonError2("missing or malformed token", 401);
26751
- const r = await memoryCall("getScheduleStatusTool", {}, token2);
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 token2 = c.req.query("token");
26756
- if (!tokenOk2(token2)) return jsonError2("missing or malformed token", 401);
26757
- const r = await memoryCall("listVaultsTool", {}, token2);
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 token2 = c.req.query("token");
26764
- if (!tokenOk2(token2)) return jsonError2("missing or malformed token", 401);
26765
- const r = await memoryCall("listScheduledActionsTool", {}, token2);
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 token2 = c.req.param("token");
26802
- if (!tokenOk2(token2)) {
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(token2));
26820
+ return c.html(renderSchedulePage(token3));
26806
26821
  });
26807
- function renderSchedulePage(token2) {
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(token2)};
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(token2, receivingDomain) {
27149
- const safeToken = token2.toLowerCase().replace(/[^a-f0-9]/g, "").slice(0, 24);
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(token2) {
27177
- return `support/threads/${token2}`;
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, token2, threadAddressValue) {
27362
- const path5 = issuePath(token2);
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:${token2}`,
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:${token2}`,
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: token2,
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, token2, parentPath) {
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: token2,
27479
- thread_ref: token2,
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, token2) {
27540
- const safeToken = token2.toLowerCase().replace(/[^a-f0-9]/g, "").slice(0, 24);
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 token2 = options.existingThread?.threadToken ?? supportThreadToken(customer.email, normalizedSubject);
27569
- const threadAddressValue = options.existingThread?.threadAddress ?? supportThreadAddress(token2, options.receivingDomain);
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
- token2,
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
- token2,
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: token2,
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 token2 = extractSupportThreadToken(receivedEvent.data.to ?? [], ...domains);
27771
- const thread = token2 ? await getSupportThreadByToken(memoryKey, token2) : null;
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 createHash11 } from "crypto";
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 createHash11("sha256").update(value).digest("hex");
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 createHash12 } from "crypto";
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 createHash12("sha256").update(value).digest("hex");
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(token2, cutoff) {
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: token2 });
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: token2 });
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 token2 = process.env.BLOB_READ_WRITE_TOKEN;
29801
- return token2 ? await cleanupVercel(token2, cutoff) : await cleanupLocal(cutoff);
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 randomUUID13 } from "crypto";
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 = randomUUID13();
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 randomUUID14 } from "crypto";
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 = randomUUID14();
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 createHash14, randomUUID as randomUUID17 } from "crypto";
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: slackBoundaryTimestamp(input.from, -1),
30832
- latest: slackBoundaryTimestamp(input.to, 0),
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 createHash13, randomUUID as randomUUID16 } from "crypto";
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 = createHash13("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
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() || randomUUID16();
31552
- const idempotencyKey2 = `main-nango:${createHash13("sha256").update(args.identity.toLowerCase()).update("\0").update(connection.id).update("\0").update(args.tool).update("\0").update(requestId).digest("hex")}`;
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 : createHash13("sha256").update(requestId).digest("hex"),
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: createHash13("sha256").update(JSON.stringify(projected)).digest("hex"),
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:${createHash14("sha256").update(identity).update("\0").update(idempotencyKey2?.trim() || randomUUID17()).digest("hex")}`;
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 createHash14("sha256").update(canonicalJson2(tool)).digest("hex");
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 token2 = getCookie2(c, "session");
33215
- if (!token2) return c.json({ error: "Not authenticated" }, 401);
33216
- const userId = verifySession(token2);
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 Hono30();
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 token2 = signSession(user.id);
33359
- setCookie2(c, "session", token2, sessionCookieOptions2);
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 token2 = signSession(user.id);
33378
- setCookie2(c, "session", token2, sessionCookieOptions2);
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 token2 = await createPasswordResetToken(user.id);
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=${token2}`;
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: token2, password } = await c.req.json();
33456
- if (!token2 || !password) return c.json({ error: "Token and password required" }, 400);
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(token2);
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 token2 = getCookie2(c, "session");
33498
- if (!token2) return c.json({ authenticated: false });
33499
- const userId = verifySession(token2);
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 = randomUUID18();
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-5RWLFSAJ.js");
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-6GX72U4N.js");
36121
- const { assembleExtractArtifacts } = await import("./extract-bundle-SKXEDG4Z.js");
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-6GX72U4N.js");
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-2JJPCZH4.js.map
36964
+ //# sourceMappingURL=server-MVCGZ4OK.js.map