mcp-scraper 0.66.5 → 0.66.7

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 (34) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +1 -1
  3. package/dist/bin/api-server.cjs +910 -601
  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.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +2 -2
  8. package/dist/bin/mcp-scraper-install.js +2 -2
  9. package/dist/bin/mcp-stdio-server.cjs +105 -18
  10. package/dist/bin/mcp-stdio-server.js +6 -6
  11. package/dist/bin/paa-harvest.cjs +14 -1
  12. package/dist/bin/paa-harvest.js +3 -3
  13. package/dist/{chunk-RSFMR7O7.js → chunk-7KB5QTHP.js} +1 -1
  14. package/dist/{chunk-K7Y27ND3.js → chunk-CGOMGGOF.js} +2 -2
  15. package/dist/{chunk-DBWWIK7O.js → chunk-EVOBJYBE.js} +1 -1
  16. package/dist/{chunk-3EYALCYK.js → chunk-F434YJPV.js} +1 -1
  17. package/dist/{chunk-PMCT2YQM.js → chunk-HBZENASD.js} +1 -1
  18. package/dist/{chunk-KXRWY7DZ.js → chunk-HWGQVP3Q.js} +1 -1
  19. package/dist/{chunk-55B3T5L2.js → chunk-HZDJ5UZ4.js} +68 -1
  20. package/dist/{chunk-ABYBPODM.js → chunk-JZPSG4NR.js} +1 -1
  21. package/dist/{chunk-CETDPNMZ.js → chunk-LVXRWJJJ.js} +1 -1
  22. package/dist/{chunk-TCOP5BT7.js → chunk-O7ZC5Y3Z.js} +1 -1
  23. package/dist/{chunk-3TKKJQB4.js → chunk-U2UHJWFW.js} +1 -1
  24. package/dist/{chunk-OJFWURGD.js → chunk-Z5QQEWXA.js} +53 -19
  25. package/dist/{db-4RVZ3NYZ.js → db-MNNNIWVS.js} +1 -1
  26. package/dist/{extract-bundle-EA5URLXN.js → extract-bundle-RW7CVIRF.js} +3 -3
  27. package/dist/index.cjs +14 -1
  28. package/dist/index.js +3 -3
  29. package/dist/{lead-list-enrichment-repository-IBXNWM6H.js → lead-list-enrichment-repository-PZYE6TDP.js} +2 -2
  30. package/dist/{location-data-repository-WHHQUCUI.js → location-data-repository-EPCNGMOM.js} +2 -2
  31. package/dist/{server-LESUYFDI.js → server-KSVJ4X4V.js} +650 -445
  32. package/dist/{site-extract-repository-NTM4MDQN.js → site-extract-repository-CZ7SVT3Z.js} +2 -2
  33. package/dist/{worker-UTBUWEXQ.js → worker-IBLGV272.js} +5 -5
  34. package/package.json +1 -1
@@ -164,6 +164,20 @@ function publicErrorType(code) {
164
164
  case "site_export_read_failed":
165
165
  case "site_export_image_not_found":
166
166
  return "extraction";
167
+ case "image_url_invalid":
168
+ case "image_url_not_https":
169
+ case "image_host_unresolvable":
170
+ case "image_host_blocked":
171
+ case "image_redirect_invalid":
172
+ case "image_source_unavailable":
173
+ case "image_too_large":
174
+ case "image_redirect_limit":
175
+ case "image_base64_invalid":
176
+ case "image_source_missing":
177
+ case "image_format_unsupported":
178
+ case "image_storage_unconfigured":
179
+ case "featured_image_unhostable":
180
+ return "invalid_request";
167
181
  case "insufficient_balance":
168
182
  return "billing";
169
183
  case "vendor_unavailable":
@@ -237,6 +251,32 @@ function publicErrorMessage(code) {
237
251
  return "The stored site export could not be read or did not pass its integrity check.";
238
252
  case "site_export_image_not_found":
239
253
  return "The requested site-export image was not found, expired, or does not belong to this account.";
254
+ case "image_url_invalid":
255
+ return "The image source is not a valid URL. Use a stable public HTTPS URL or inline image bytes.";
256
+ case "image_url_not_https":
257
+ return "Commons image sources must use a public HTTPS URL or inline image bytes.";
258
+ case "image_host_unresolvable":
259
+ return "The image host could not be resolved. Use a different stable public URL or inline image bytes.";
260
+ case "image_host_blocked":
261
+ return "The image source resolves to a network address Commons cannot fetch safely.";
262
+ case "image_redirect_invalid":
263
+ return "The image source redirected to an invalid or unsafe destination.";
264
+ case "image_source_unavailable":
265
+ return "The image source could not be fetched. Host a different stable public URL or provide inline image bytes.";
266
+ case "image_too_large":
267
+ return "The image exceeds the Commons image size limit.";
268
+ case "image_redirect_limit":
269
+ return "The image source exceeded the Commons redirect limit.";
270
+ case "image_base64_invalid":
271
+ return "The supplied inline image bytes are invalid.";
272
+ case "image_source_missing":
273
+ return "Provide either a stable public image URL or inline image bytes.";
274
+ case "image_format_unsupported":
275
+ return "Commons accepts JPEG, PNG, GIF, or WebP image bytes.";
276
+ case "image_storage_unconfigured":
277
+ return "Commons image storage is temporarily unavailable.";
278
+ case "featured_image_unhostable":
279
+ return "The required featured image could not be hosted for Commons publication.";
240
280
  default:
241
281
  return PUBLIC_SERVICE_UNAVAILABLE_MESSAGE;
242
282
  }
@@ -299,7 +339,20 @@ var init_errors = __esm({
299
339
  "site_export_not_found",
300
340
  "site_export_format_unavailable",
301
341
  "site_export_read_failed",
302
- "site_export_image_not_found"
342
+ "site_export_image_not_found",
343
+ "image_url_invalid",
344
+ "image_url_not_https",
345
+ "image_host_unresolvable",
346
+ "image_host_blocked",
347
+ "image_redirect_invalid",
348
+ "image_source_unavailable",
349
+ "image_too_large",
350
+ "image_redirect_limit",
351
+ "image_base64_invalid",
352
+ "image_source_missing",
353
+ "image_format_unsupported",
354
+ "image_storage_unconfigured",
355
+ "featured_image_unhostable"
303
356
  ];
304
357
  PUBLIC_ERROR_TYPES = [
305
358
  "request_aborted",
@@ -4495,6 +4548,20 @@ async function migrate(options = {}) {
4495
4548
  PRIMARY KEY (user_id, connection_id, period_start)
4496
4549
  )
4497
4550
  `);
4551
+ await db.execute(`
4552
+ CREATE TABLE IF NOT EXISTS connected_account_charge_transfers (
4553
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4554
+ user_id INTEGER NOT NULL,
4555
+ period_start TEXT NOT NULL,
4556
+ from_connection_id TEXT NOT NULL,
4557
+ to_connection_id TEXT NOT NULL,
4558
+ provider_config_key TEXT NOT NULL,
4559
+ transferred_at TEXT NOT NULL DEFAULT (datetime('now')),
4560
+ UNIQUE (user_id, period_start, from_connection_id),
4561
+ CHECK (from_connection_id <> to_connection_id)
4562
+ )
4563
+ `);
4564
+ await db.execute(`CREATE INDEX IF NOT EXISTS connected_account_charge_transfers_destination ON connected_account_charge_transfers(user_id, period_start, to_connection_id, transferred_at DESC)`);
4498
4565
  await db.execute(`
4499
4566
  CREATE TABLE IF NOT EXISTS billing_credits (
4500
4567
  idempotency_key TEXT PRIMARY KEY,
@@ -36288,6 +36355,357 @@ var init_subscription_status_policy = __esm({
36288
36355
  }
36289
36356
  });
36290
36357
 
36358
+ // src/api/service-connections.ts
36359
+ function ensureServiceConnectionsSchema() {
36360
+ const currentDb = getDb();
36361
+ if (schemaReady && schemaDb5 === currentDb) return schemaReady;
36362
+ schemaDb5 = currentDb;
36363
+ schemaReady = (async () => {
36364
+ const db = currentDb;
36365
+ await db.execute(`
36366
+ CREATE TABLE IF NOT EXISTS service_connections (
36367
+ id TEXT PRIMARY KEY,
36368
+ user_id INTEGER NOT NULL REFERENCES users(id),
36369
+ provider_config_key TEXT NOT NULL,
36370
+ provider TEXT,
36371
+ transport TEXT NOT NULL,
36372
+ upstream_connection_id TEXT,
36373
+ label TEXT,
36374
+ user_label TEXT,
36375
+ provider_account_id TEXT,
36376
+ provider_account_email TEXT,
36377
+ provider_account_name TEXT,
36378
+ provider_identity_checked_at TEXT,
36379
+ provider_identity_source_updated_at TEXT,
36380
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending',
36381
+ operational_status TEXT NOT NULL DEFAULT 'unknown',
36382
+ reconnect_required INTEGER NOT NULL DEFAULT 0,
36383
+ actions_enabled INTEGER NOT NULL DEFAULT 0,
36384
+ read_tools_json TEXT NOT NULL DEFAULT '[]',
36385
+ action_tools_json TEXT NOT NULL DEFAULT '[]',
36386
+ tool_revision TEXT,
36387
+ last_checked_at TEXT,
36388
+ last_successful_call_at TEXT,
36389
+ last_failure_at TEXT,
36390
+ last_failure_code TEXT,
36391
+ last_failure_retryable INTEGER,
36392
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
36393
+ vault_name TEXT,
36394
+ table_name TEXT,
36395
+ legacy_owner_identity TEXT,
36396
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36397
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
36398
+ UNIQUE(user_id, provider_config_key, upstream_connection_id)
36399
+ )
36400
+ `);
36401
+ try {
36402
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
36403
+ } catch {
36404
+ }
36405
+ try {
36406
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
36407
+ } catch {
36408
+ }
36409
+ try {
36410
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
36411
+ } catch {
36412
+ }
36413
+ try {
36414
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
36415
+ } catch {
36416
+ }
36417
+ try {
36418
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
36419
+ } catch {
36420
+ }
36421
+ try {
36422
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
36423
+ } catch {
36424
+ }
36425
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
36426
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
36427
+ await db.execute(`
36428
+ CREATE TABLE IF NOT EXISTS service_connection_health_events (
36429
+ id TEXT PRIMARY KEY,
36430
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
36431
+ operational_status TEXT NOT NULL,
36432
+ failure_code TEXT,
36433
+ retryable INTEGER,
36434
+ evidence_source TEXT NOT NULL,
36435
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
36436
+ )
36437
+ `);
36438
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
36439
+ await db.execute(`
36440
+ CREATE TABLE IF NOT EXISTS service_connection_action_audit (
36441
+ id TEXT PRIMARY KEY,
36442
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
36443
+ user_id INTEGER NOT NULL REFERENCES users(id),
36444
+ tool TEXT NOT NULL,
36445
+ request_id TEXT NOT NULL,
36446
+ status TEXT NOT NULL,
36447
+ error_code TEXT,
36448
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36449
+ UNIQUE(user_id, request_id)
36450
+ )
36451
+ `);
36452
+ try {
36453
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
36454
+ } catch {
36455
+ }
36456
+ try {
36457
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
36458
+ } catch {
36459
+ }
36460
+ })().catch((error) => {
36461
+ schemaReady = null;
36462
+ schemaDb5 = null;
36463
+ throw error;
36464
+ });
36465
+ return schemaReady;
36466
+ }
36467
+ function parseTools(value) {
36468
+ if (typeof value !== "string") return [];
36469
+ try {
36470
+ const parsed = JSON.parse(value);
36471
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
36472
+ } catch {
36473
+ return [];
36474
+ }
36475
+ }
36476
+ function rowToRecord(row) {
36477
+ return {
36478
+ id: String(row.id),
36479
+ userId: Number(row.user_id),
36480
+ providerConfigKey: String(row.provider_config_key),
36481
+ provider: typeof row.provider === "string" ? row.provider : null,
36482
+ transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
36483
+ upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
36484
+ label: typeof row.label === "string" ? row.label : null,
36485
+ userLabel: typeof row.user_label === "string" ? row.user_label : null,
36486
+ providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
36487
+ providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
36488
+ providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
36489
+ providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
36490
+ providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
36491
+ lifecycleStatus: String(row.lifecycle_status),
36492
+ operationalStatus: String(row.operational_status),
36493
+ reconnectRequired: Number(row.reconnect_required) === 1,
36494
+ actionsEnabled: Number(row.actions_enabled) === 1,
36495
+ readTools: parseTools(row.read_tools_json),
36496
+ actionTools: parseTools(row.action_tools_json),
36497
+ toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
36498
+ lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
36499
+ lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
36500
+ lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
36501
+ lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
36502
+ lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
36503
+ consecutiveFailures: Number(row.consecutive_failures ?? 0),
36504
+ vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
36505
+ tableName: typeof row.table_name === "string" ? row.table_name : null,
36506
+ createdAt: String(row.created_at),
36507
+ updatedAt: String(row.updated_at)
36508
+ };
36509
+ }
36510
+ async function userIdForIdentity(identity) {
36511
+ const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
36512
+ if (!user) throw new Error("service_connection_user_not_found");
36513
+ return Number(user.id);
36514
+ }
36515
+ async function reconcileDiscoveredNangoConnections(identity, discovered) {
36516
+ await ensureServiceConnectionsSchema();
36517
+ const db = getDb();
36518
+ const userId = await userIdForIdentity(identity);
36519
+ for (const connection of discovered) {
36520
+ await db.execute({
36521
+ sql: `
36522
+ INSERT INTO service_connections (
36523
+ id, user_id, provider_config_key, provider, transport, upstream_connection_id,
36524
+ label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
36525
+ ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
36526
+ ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
36527
+ provider = excluded.provider,
36528
+ label = COALESCE(excluded.label, service_connections.label),
36529
+ lifecycle_status = excluded.lifecycle_status,
36530
+ reconnect_required = excluded.reconnect_required,
36531
+ updated_at = excluded.updated_at
36532
+ `,
36533
+ args: [
36534
+ (0, import_node_crypto21.randomUUID)(),
36535
+ userId,
36536
+ connection.providerConfigKey,
36537
+ connection.provider,
36538
+ connection.upstreamConnectionId,
36539
+ connection.label,
36540
+ connection.lifecycleStatus,
36541
+ connection.reconnectRequired ? 1 : 0,
36542
+ identity,
36543
+ connection.createdAt,
36544
+ connection.updatedAt
36545
+ ]
36546
+ });
36547
+ }
36548
+ return listServiceConnections(identity, "nango");
36549
+ }
36550
+ async function listServiceConnections(identity, transport) {
36551
+ await ensureServiceConnectionsSchema();
36552
+ const userId = await userIdForIdentity(identity);
36553
+ const result = await getDb().execute({
36554
+ sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
36555
+ args: transport ? [userId, transport] : [userId]
36556
+ });
36557
+ return result.rows.map((row) => rowToRecord(row));
36558
+ }
36559
+ async function getOwnedServiceConnection(identity, connectionId) {
36560
+ await ensureServiceConnectionsSchema();
36561
+ const userId = await userIdForIdentity(identity);
36562
+ const result = await getDb().execute({
36563
+ sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
36564
+ args: [connectionId, userId]
36565
+ });
36566
+ const row = result.rows[0];
36567
+ return row ? rowToRecord(row) : null;
36568
+ }
36569
+ async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
36570
+ await ensureServiceConnectionsSchema();
36571
+ await getDb().execute({
36572
+ sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
36573
+ args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
36574
+ });
36575
+ }
36576
+ async function setServiceConnectionProviderIdentity(args) {
36577
+ await ensureServiceConnectionsSchema();
36578
+ await getDb().execute({
36579
+ sql: `
36580
+ UPDATE service_connections SET
36581
+ provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
36582
+ provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
36583
+ updated_at = datetime('now')
36584
+ WHERE id = ?
36585
+ `,
36586
+ args: [
36587
+ args.providerAccountId ?? null,
36588
+ args.providerAccountEmail?.toLowerCase() ?? null,
36589
+ args.providerAccountName ?? null,
36590
+ args.sourceUpdatedAt,
36591
+ args.connectionId
36592
+ ]
36593
+ });
36594
+ }
36595
+ async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
36596
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36597
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
36598
+ await getDb().execute({
36599
+ sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36600
+ args: [userLabel, connectionId, connection.userId]
36601
+ });
36602
+ return userLabel;
36603
+ }
36604
+ async function recordServiceConnectionHealth(args) {
36605
+ await ensureServiceConnectionsSchema();
36606
+ const now = (/* @__PURE__ */ new Date()).toISOString();
36607
+ await getDb().execute({
36608
+ sql: `
36609
+ UPDATE service_connections SET
36610
+ operational_status = ?, last_checked_at = ?,
36611
+ last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
36612
+ last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
36613
+ last_failure_code = ?, last_failure_retryable = ?,
36614
+ consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
36615
+ updated_at = datetime('now')
36616
+ WHERE id = ?
36617
+ `,
36618
+ args: [
36619
+ args.operationalStatus,
36620
+ now,
36621
+ args.operationalStatus,
36622
+ now,
36623
+ args.operationalStatus,
36624
+ now,
36625
+ args.failureCode ?? null,
36626
+ args.retryable == null ? null : args.retryable ? 1 : 0,
36627
+ args.operationalStatus,
36628
+ args.connectionId
36629
+ ]
36630
+ });
36631
+ await getDb().execute({
36632
+ sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
36633
+ args: [(0, import_node_crypto21.randomUUID)(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
36634
+ });
36635
+ }
36636
+ async function setServiceConnectionActions(identity, connectionId, enabled) {
36637
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36638
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
36639
+ await getDb().execute({
36640
+ sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36641
+ args: [enabled ? 1 : 0, connectionId, connection.userId]
36642
+ });
36643
+ return enabled;
36644
+ }
36645
+ async function markServiceConnectionDisconnected(identity, connectionId) {
36646
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36647
+ if (!connection) throw new Error("service_connection_not_found");
36648
+ await getDb().execute({
36649
+ sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36650
+ args: [connectionId, connection.userId]
36651
+ });
36652
+ const remaining = await getDb().execute({
36653
+ sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
36654
+ args: [connection.userId]
36655
+ });
36656
+ return Number(remaining.rows[0]?.count ?? 0);
36657
+ }
36658
+ async function recordServiceConnectionAction(args) {
36659
+ await ensureServiceConnectionsSchema();
36660
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
36661
+ if (!connection) throw new Error("service_connection_not_found");
36662
+ await getDb().execute({
36663
+ sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
36664
+ args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
36665
+ });
36666
+ }
36667
+ async function claimServiceConnectionAction(args) {
36668
+ await ensureServiceConnectionsSchema();
36669
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
36670
+ if (!connection) throw new Error("service_connection_not_found");
36671
+ const inserted = await getDb().execute({
36672
+ sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
36673
+ args: [(0, import_node_crypto21.randomUUID)(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
36674
+ });
36675
+ if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
36676
+ const existing = await getDb().execute({
36677
+ sql: `SELECT connection_id, tool, status, error_code, request_digest, result_json FROM service_connection_action_audit WHERE user_id = ? AND request_id = ? LIMIT 1`,
36678
+ args: [connection.userId, args.requestId]
36679
+ });
36680
+ const row = existing.rows[0];
36681
+ const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
36682
+ let result = void 0;
36683
+ if (!conflict && typeof row?.result_json === "string") {
36684
+ try {
36685
+ result = JSON.parse(row.result_json);
36686
+ } catch {
36687
+ result = void 0;
36688
+ }
36689
+ }
36690
+ return {
36691
+ claimed: false,
36692
+ status: String(row?.status ?? "unknown"),
36693
+ errorCode: typeof row?.error_code === "string" ? row.error_code : null,
36694
+ conflict,
36695
+ ...result !== void 0 ? { result } : {}
36696
+ };
36697
+ }
36698
+ var import_node_crypto21, schemaReady, schemaDb5;
36699
+ var init_service_connections = __esm({
36700
+ "src/api/service-connections.ts"() {
36701
+ "use strict";
36702
+ import_node_crypto21 = require("crypto");
36703
+ init_db();
36704
+ schemaReady = null;
36705
+ schemaDb5 = null;
36706
+ }
36707
+ });
36708
+
36291
36709
  // src/api/connected-account-billing.ts
36292
36710
  function connectedBillingPeriodStart(now = /* @__PURE__ */ new Date()) {
36293
36711
  return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -36302,6 +36720,87 @@ async function chargedConnectionIds(userId, periodStart) {
36302
36720
  });
36303
36721
  return new Set(result.rows.map((row) => String(row.connection_id)));
36304
36722
  }
36723
+ async function inheritDisconnectedConnectionCharge(userId, connectionId, periodStart) {
36724
+ await ensureServiceConnectionsSchema();
36725
+ const normalizedUserId = Number(userId);
36726
+ const db = getDb();
36727
+ await db.batch([
36728
+ {
36729
+ sql: `
36730
+ INSERT OR IGNORE INTO connected_account_charge_transfers (
36731
+ user_id, period_start, from_connection_id, to_connection_id, provider_config_key
36732
+ )
36733
+ SELECT ?, ?, previous.id, replacement.id, replacement.provider_config_key
36734
+ FROM service_connections AS replacement
36735
+ JOIN service_connections AS previous
36736
+ ON previous.user_id = replacement.user_id
36737
+ AND previous.transport = 'nango'
36738
+ AND previous.provider_config_key = replacement.provider_config_key
36739
+ AND previous.lifecycle_status = 'disconnected'
36740
+ JOIN connected_account_charges AS charge
36741
+ ON charge.user_id = previous.user_id
36742
+ AND charge.connection_id = previous.id
36743
+ AND charge.period_start = ?
36744
+ WHERE replacement.id = ?
36745
+ AND replacement.user_id = ?
36746
+ AND replacement.transport = 'nango'
36747
+ AND replacement.lifecycle_status IN ('connected', 'needs_reauth')
36748
+ AND NOT EXISTS (
36749
+ SELECT 1 FROM connected_account_charges AS existing
36750
+ WHERE existing.user_id = replacement.user_id
36751
+ AND existing.connection_id = replacement.id
36752
+ AND existing.period_start = ?
36753
+ )
36754
+ ORDER BY previous.updated_at DESC, previous.id ASC
36755
+ LIMIT 1
36756
+ `,
36757
+ args: [normalizedUserId, periodStart, periodStart, connectionId, normalizedUserId, periodStart]
36758
+ },
36759
+ {
36760
+ sql: `
36761
+ UPDATE connected_account_charges
36762
+ SET connection_id = ?
36763
+ WHERE user_id = ?
36764
+ AND period_start = ?
36765
+ AND connection_id = (
36766
+ SELECT transfer.from_connection_id
36767
+ FROM connected_account_charge_transfers AS transfer
36768
+ JOIN connected_account_charges AS current_charge
36769
+ ON current_charge.user_id = transfer.user_id
36770
+ AND current_charge.period_start = transfer.period_start
36771
+ AND current_charge.connection_id = transfer.from_connection_id
36772
+ WHERE transfer.user_id = ?
36773
+ AND transfer.period_start = ?
36774
+ AND transfer.to_connection_id = ?
36775
+ ORDER BY transfer.transferred_at DESC, transfer.id DESC
36776
+ LIMIT 1
36777
+ )
36778
+ AND NOT EXISTS (
36779
+ SELECT 1 FROM connected_account_charges AS existing
36780
+ WHERE existing.user_id = ?
36781
+ AND existing.connection_id = ?
36782
+ AND existing.period_start = ?
36783
+ )
36784
+ `,
36785
+ args: [
36786
+ connectionId,
36787
+ normalizedUserId,
36788
+ periodStart,
36789
+ normalizedUserId,
36790
+ periodStart,
36791
+ connectionId,
36792
+ normalizedUserId,
36793
+ connectionId,
36794
+ periodStart
36795
+ ]
36796
+ }
36797
+ ], "write");
36798
+ const inherited = await db.execute({
36799
+ sql: "SELECT 1 FROM connected_account_charges WHERE user_id = ? AND connection_id = ? AND period_start = ? LIMIT 1",
36800
+ args: [normalizedUserId, connectionId, periodStart]
36801
+ });
36802
+ return inherited.rows.length > 0;
36803
+ }
36305
36804
  async function getConnectedAccountBillingState2(userId) {
36306
36805
  const result = await getDb().execute({
36307
36806
  sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
@@ -36430,6 +36929,7 @@ async function reconcileConnectedAccountBilling(user, connections) {
36430
36929
  const alreadyCharged = await chargedConnectionIds(user.id, periodStart);
36431
36930
  const unpaid = billableConnectionIds.filter((id) => !alreadyCharged.has(id));
36432
36931
  for (const connectionId of unpaid) {
36932
+ if (await inheritDisconnectedConnectionCharge(user.id, connectionId, periodStart)) continue;
36433
36933
  const feeMc = CONNECTED_ACTIVE_CONNECTION_MONTHLY_MC;
36434
36934
  const debit = await debitMcIdempotent(
36435
36935
  user.id,
@@ -36491,6 +36991,7 @@ var init_connected_account_billing = __esm({
36491
36991
  init_db();
36492
36992
  init_rates();
36493
36993
  init_connected_cost_telemetry();
36994
+ init_service_connections();
36494
36995
  DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
36495
36996
  BILLABLE_LIFECYCLE = /* @__PURE__ */ new Set(["connected", "needs_reauth"]);
36496
36997
  ConnectedAccountBillingError = class extends Error {
@@ -40059,7 +40560,7 @@ async function applyAdjustment(input) {
40059
40560
  const credits = validateCredits(input.credits, input.confirmLarge === true);
40060
40561
  const reason = validateReason(input.reason);
40061
40562
  const actor = validateActor(input.actor);
40062
- const reference = input.reference?.trim() || `adj_${(0, import_node_crypto21.randomUUID)()}`;
40563
+ const reference = input.reference?.trim() || `adj_${(0, import_node_crypto22.randomUUID)()}`;
40063
40564
  if (reference.length > 120) throw new AdminCreditError(400, "reference must be at most 120 characters");
40064
40565
  const db = getDb();
40065
40566
  const existing = await db.execute({
@@ -40204,11 +40705,11 @@ async function lookupAccount(target, limit = 20) {
40204
40705
  }))
40205
40706
  };
40206
40707
  }
40207
- var import_node_crypto21, MIN_ADJUSTMENT_CREDITS, LARGE_ADJUSTMENT_CREDITS, MAX_ADJUSTMENT_CREDITS, MIN_REASON_LENGTH, MAX_REASON_LENGTH, MAX_ACTOR_LENGTH, AdminCreditError;
40708
+ var import_node_crypto22, MIN_ADJUSTMENT_CREDITS, LARGE_ADJUSTMENT_CREDITS, MAX_ADJUSTMENT_CREDITS, MIN_REASON_LENGTH, MAX_REASON_LENGTH, MAX_ACTOR_LENGTH, AdminCreditError;
40208
40709
  var init_admin_credits = __esm({
40209
40710
  "src/api/admin-credits.ts"() {
40210
40711
  "use strict";
40211
- import_node_crypto21 = require("crypto");
40712
+ import_node_crypto22 = require("crypto");
40212
40713
  init_db();
40213
40714
  init_rates();
40214
40715
  MIN_ADJUSTMENT_CREDITS = 1;
@@ -42173,7 +42674,7 @@ async function packageMapsMedia(args) {
42173
42674
  files.push({ path: "summary.json", content: Buffer.from(JSON.stringify(summary, null, 2)) });
42174
42675
  files.push({ path: "images.jsonl", content: Buffer.from(cleanImages.map((image) => JSON.stringify(image)).join("\n") + "\n") });
42175
42676
  const archive = await zipBuffer(files);
42176
- const id = (0, import_node_crypto22.randomBytes)(6).toString("hex");
42677
+ const id = (0, import_node_crypto23.randomBytes)(6).toString("hex");
42177
42678
  const pointer = await createPrivateArtifact({
42178
42679
  policy: policy3(),
42179
42680
  ownerId: args.ownerId,
@@ -42186,13 +42687,13 @@ async function packageMapsMedia(args) {
42186
42687
  const localPath = token() ? null : (0, import_node_path13.join)(process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path13.join)((0, import_node_os9.homedir)(), "Downloads", "mcp-scraper"), "blobs", pointer.artifactId);
42187
42688
  return { media: args.media, artifact: { ...pointer, localPath } };
42188
42689
  }
42189
- var import_node_os9, import_node_path13, import_node_crypto22, import_yazl2, import_p_limit5, MAPS_MEDIA_ARTIFACT_PREFIX, MAPS_MEDIA_ARTIFACT_TTL_MS, MAPS_MEDIA_DOWNLOAD_TTL_MS, MAX_IMAGE_BYTES2, MAX_ARCHIVE_IMAGE_BYTES, MAX_INLINE_IMAGE_BYTES, MAX_INLINE_TOTAL_BYTES, DOWNLOAD_CONCURRENCY, MAX_REDIRECTS;
42690
+ var import_node_os9, import_node_path13, import_node_crypto23, import_yazl2, import_p_limit5, MAPS_MEDIA_ARTIFACT_PREFIX, MAPS_MEDIA_ARTIFACT_TTL_MS, MAPS_MEDIA_DOWNLOAD_TTL_MS, MAX_IMAGE_BYTES2, MAX_ARCHIVE_IMAGE_BYTES, MAX_INLINE_IMAGE_BYTES, MAX_INLINE_TOTAL_BYTES, DOWNLOAD_CONCURRENCY, MAX_REDIRECTS;
42190
42691
  var init_maps_media_artifacts = __esm({
42191
42692
  "src/api/maps-media-artifacts.ts"() {
42192
42693
  "use strict";
42193
42694
  import_node_os9 = require("os");
42194
42695
  import_node_path13 = require("path");
42195
- import_node_crypto22 = require("crypto");
42696
+ import_node_crypto23 = require("crypto");
42196
42697
  import_yazl2 = require("yazl");
42197
42698
  import_p_limit5 = __toESM(require("p-limit"), 1);
42198
42699
  init_private_artifacts();
@@ -42816,7 +43317,7 @@ function retryDelaySeconds(attempts) {
42816
43317
  }
42817
43318
  async function dispatchPendingDirectoryWorkflows(limit = 25) {
42818
43319
  const rows = await claimDirectoryWorkflowOutbox({
42819
- workerId: `directory-dispatch-${process.pid}-${(0, import_node_crypto23.randomUUID)().slice(0, 8)}`,
43320
+ workerId: `directory-dispatch-${process.pid}-${(0, import_node_crypto24.randomUUID)().slice(0, 8)}`,
42820
43321
  limit
42821
43322
  });
42822
43323
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
@@ -42838,11 +43339,11 @@ async function dispatchPendingDirectoryWorkflows(limit = 25) {
42838
43339
  }
42839
43340
  return result;
42840
43341
  }
42841
- var import_node_crypto23;
43342
+ var import_node_crypto24;
42842
43343
  var init_directory_workflow_dispatch = __esm({
42843
43344
  "src/api/directory-workflow-dispatch.ts"() {
42844
43345
  "use strict";
42845
- import_node_crypto23 = require("crypto");
43346
+ import_node_crypto24 = require("crypto");
42846
43347
  init_client();
42847
43348
  init_directory_workflow_repository();
42848
43349
  }
@@ -42854,7 +43355,7 @@ function safeOptions(options) {
42854
43355
  return safe2;
42855
43356
  }
42856
43357
  function requestFingerprint(options) {
42857
- return (0, import_node_crypto24.createHash)("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
43358
+ return (0, import_node_crypto25.createHash)("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
42858
43359
  }
42859
43360
  function idempotencyKey(raw) {
42860
43361
  if (raw !== void 0) {
@@ -42863,14 +43364,14 @@ function idempotencyKey(raw) {
42863
43364
  if (trimmed.length > 500) return { ok: false, message: "Idempotency-Key must be 500 characters or fewer." };
42864
43365
  return { ok: true, key: trimmed };
42865
43366
  }
42866
- return { ok: true, key: `directory-${(0, import_node_crypto24.randomUUID)()}` };
43367
+ return { ok: true, key: `directory-${(0, import_node_crypto25.randomUUID)()}` };
42867
43368
  }
42868
43369
  function debitKeyFor(userId, responseKey) {
42869
- const digest2 = (0, import_node_crypto24.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
43370
+ const digest2 = (0, import_node_crypto25.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
42870
43371
  return `directory-workflow:${userId}:${digest2}`;
42871
43372
  }
42872
43373
  function jobId() {
42873
- return `dir_${(0, import_node_crypto24.randomUUID)().replace(/-/g, "")}`;
43374
+ return `dir_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
42874
43375
  }
42875
43376
  function publicStatus(job, result) {
42876
43377
  if (job.status === "failed") return "failed";
@@ -42979,7 +43480,7 @@ async function runSynchronously(c, user, options, plan, responseKey) {
42979
43480
  const csv = renderDirectoryWorkflowCsv(result);
42980
43481
  const artifact = await createDirectoryCsvArtifact({
42981
43482
  ownerId: String(user.id),
42982
- jobId: (0, import_node_crypto24.createHash)("sha256").update(debitKey2).digest("hex").slice(0, 32),
43483
+ jobId: (0, import_node_crypto25.createHash)("sha256").update(debitKey2).digest("hex").slice(0, 32),
42983
43484
  createdAt: result.extractedAt,
42984
43485
  filename: `${options.state}-${options.query}-directory.csv`,
42985
43486
  csv,
@@ -43035,11 +43536,11 @@ async function runSynchronously(c, user, options, plan, responseKey) {
43035
43536
  await releaseConcurrencyGate(gate.lockId);
43036
43537
  }
43037
43538
  }
43038
- var import_node_crypto24, import_hono16, directoryApp;
43539
+ var import_node_crypto25, import_hono16, directoryApp;
43039
43540
  var init_directory_routes = __esm({
43040
43541
  "src/api/directory-routes.ts"() {
43041
43542
  "use strict";
43042
- import_node_crypto24 = require("crypto");
43543
+ import_node_crypto25 = require("crypto");
43043
43544
  import_hono16 = require("hono");
43044
43545
  init_api_auth();
43045
43546
  init_db();
@@ -43257,15 +43758,15 @@ function bounded(value, field, min, max) {
43257
43758
  return normalized;
43258
43759
  }
43259
43760
  function newLeadListUploadId() {
43260
- return `upl_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
43761
+ return `upl_${(0, import_node_crypto26.randomUUID)().replace(/-/g, "")}`;
43261
43762
  }
43262
43763
  function newImportedLeadListId() {
43263
- return `lst_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
43764
+ return `lst_${(0, import_node_crypto26.randomUUID)().replace(/-/g, "")}`;
43264
43765
  }
43265
43766
  async function ensureLeadListImportRepositorySchema() {
43266
43767
  const db = getDb();
43267
- if (schemaDb5 !== db) {
43268
- schemaDb5 = db;
43768
+ if (schemaDb6 !== db) {
43769
+ schemaDb6 = db;
43269
43770
  schemaPromise5 = null;
43270
43771
  }
43271
43772
  if (!schemaPromise5) {
@@ -43460,13 +43961,13 @@ async function deleteExpiredLeadListInputRecords(now = /* @__PURE__ */ new Date(
43460
43961
  ], "write");
43461
43962
  return { uploads: Number(uploads.rowsAffected ?? 0), lists: Number(lists.rowsAffected ?? 0) };
43462
43963
  }
43463
- var import_node_crypto25, schemaDb5, schemaPromise5;
43964
+ var import_node_crypto26, schemaDb6, schemaPromise5;
43464
43965
  var init_lead_list_import_repository = __esm({
43465
43966
  "src/api/lead-list-import-repository.ts"() {
43466
43967
  "use strict";
43467
- import_node_crypto25 = require("crypto");
43968
+ import_node_crypto26 = require("crypto");
43468
43969
  init_db();
43469
- schemaDb5 = null;
43970
+ schemaDb6 = null;
43470
43971
  schemaPromise5 = null;
43471
43972
  }
43472
43973
  });
@@ -43506,7 +44007,7 @@ function safeFilenameHint(value) {
43506
44007
  return cleaned || null;
43507
44008
  }
43508
44009
  function requestFingerprint2(filenameHint) {
43509
- return (0, import_node_crypto26.createHash)("sha256").update(JSON.stringify({ filenameHint })).digest("hex");
44010
+ return (0, import_node_crypto27.createHash)("sha256").update(JSON.stringify({ filenameHint })).digest("hex");
43510
44011
  }
43511
44012
  function publicBaseUrl() {
43512
44013
  const configured = process.env.MCP_SCRAPER_PUBLIC_BASE_URL?.trim() || process.env.PUBLIC_BASE_URL?.trim() || (process.env.VERCEL_PROJECT_PRODUCTION_URL ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}` : "") || "https://mcpscraper.dev";
@@ -43662,7 +44163,7 @@ async function inspectLeadListUpload(uploadId, ownerId2) {
43662
44163
  maxBytes: LEAD_LIST_UPLOAD_MAX_BYTES
43663
44164
  });
43664
44165
  if (!buffer) return null;
43665
- const sha2565 = (0, import_node_crypto26.createHash)("sha256").update(buffer).digest("hex");
44166
+ const sha2565 = (0, import_node_crypto27.createHash)("sha256").update(buffer).digest("hex");
43666
44167
  const completed = await completeLeadListUpload({
43667
44168
  id: record.id,
43668
44169
  ownerId: record.ownerId,
@@ -43786,11 +44287,11 @@ async function cleanupExpiredLeadListInputs(args = {}) {
43786
44287
  const records = await deleteExpiredLeadListInputRecords(now);
43787
44288
  return { deletedBlobs, deletedUploadRecords: records.uploads, deletedListRecords: records.lists };
43788
44289
  }
43789
- var import_node_crypto26, import_promises11, import_node_os10, import_node_path14, LEAD_LIST_UPLOAD_PREFIX, LEAD_LIST_NORMALIZED_PREFIX, LEAD_LIST_UPLOAD_MAX_BYTES, LEAD_LIST_UPLOAD_URL_TTL_MS, LEAD_LIST_UPLOAD_SOURCE_TTL_MS, LEAD_LIST_NORMALIZED_TTL_MS, LEAD_LIST_DOWNLOAD_TTL_MS, LEAD_LIST_UPLOAD_MIME_TYPES;
44290
+ var import_node_crypto27, import_promises11, import_node_os10, import_node_path14, LEAD_LIST_UPLOAD_PREFIX, LEAD_LIST_NORMALIZED_PREFIX, LEAD_LIST_UPLOAD_MAX_BYTES, LEAD_LIST_UPLOAD_URL_TTL_MS, LEAD_LIST_UPLOAD_SOURCE_TTL_MS, LEAD_LIST_NORMALIZED_TTL_MS, LEAD_LIST_DOWNLOAD_TTL_MS, LEAD_LIST_UPLOAD_MIME_TYPES;
43790
44291
  var init_lead_list_input_artifacts = __esm({
43791
44292
  "src/api/lead-list-input-artifacts.ts"() {
43792
44293
  "use strict";
43793
- import_node_crypto26 = require("crypto");
44294
+ import_node_crypto27 = require("crypto");
43794
44295
  import_promises11 = require("fs/promises");
43795
44296
  import_node_os10 = require("os");
43796
44297
  import_node_path14 = require("path");
@@ -44173,7 +44674,7 @@ function canonicalize(value) {
44173
44674
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(object[key])}`).join(",")}}`;
44174
44675
  }
44175
44676
  function fingerprint(value) {
44176
- return (0, import_node_crypto27.createHash)("sha256").update(canonicalize(value)).digest("hex");
44677
+ return (0, import_node_crypto28.createHash)("sha256").update(canonicalize(value)).digest("hex");
44177
44678
  }
44178
44679
  async function safeJson(c) {
44179
44680
  try {
@@ -44250,11 +44751,11 @@ function textDelimiter(raw, text2, observedMime) {
44250
44751
  if (observedMime === "text/tab-separated-values") return " ";
44251
44752
  return detectLeadListTextDelimiter(text2);
44252
44753
  }
44253
- var import_node_crypto27, import_hono17, leadListInputApp;
44754
+ var import_node_crypto28, import_hono17, leadListInputApp;
44254
44755
  var init_lead_list_input_routes = __esm({
44255
44756
  "src/api/lead-list-input-routes.ts"() {
44256
44757
  "use strict";
44257
- import_node_crypto27 = require("crypto");
44758
+ import_node_crypto28 = require("crypto");
44258
44759
  import_hono17 = require("hono");
44259
44760
  init_api_auth();
44260
44761
  init_lead_list_input_artifacts();
@@ -44446,7 +44947,7 @@ function retryDelaySeconds2(attempts) {
44446
44947
  return Math.min(3600, Math.max(15, 15 * 2 ** Math.min(8, Math.max(0, attempts - 1))));
44447
44948
  }
44448
44949
  async function dispatchPendingLeadListEnrichments(limit = 25) {
44449
- const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${(0, import_node_crypto28.randomUUID)().slice(0, 8)}`, limit });
44950
+ const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${(0, import_node_crypto29.randomUUID)().slice(0, 8)}`, limit });
44450
44951
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
44451
44952
  for (const row of rows) {
44452
44953
  if (!row.claimToken) continue;
@@ -44465,11 +44966,11 @@ async function dispatchPendingLeadListEnrichments(limit = 25) {
44465
44966
  }
44466
44967
  return result;
44467
44968
  }
44468
- var import_node_crypto28;
44969
+ var import_node_crypto29;
44469
44970
  var init_lead_list_enrichment_dispatch = __esm({
44470
44971
  "src/api/lead-list-enrichment-dispatch.ts"() {
44471
44972
  "use strict";
44472
- import_node_crypto28 = require("crypto");
44973
+ import_node_crypto29 = require("crypto");
44473
44974
  init_client();
44474
44975
  init_lead_list_enrichment_repository();
44475
44976
  }
@@ -44485,15 +44986,15 @@ function idempotencyKey2(raw) {
44485
44986
  return { ok: true, value };
44486
44987
  }
44487
44988
  function newJobId() {
44488
- return `lle_${(0, import_node_crypto29.randomUUID)().replace(/-/g, "")}`;
44989
+ return `lle_${(0, import_node_crypto30.randomUUID)().replace(/-/g, "")}`;
44489
44990
  }
44490
44991
  function debitKeyFor2(userId, key) {
44491
- const digest2 = (0, import_node_crypto29.createHash)("sha256").update(String(userId)).update("\0").update(key).digest("hex");
44992
+ const digest2 = (0, import_node_crypto30.createHash)("sha256").update(String(userId)).update("\0").update(key).digest("hex");
44492
44993
  return `lead-list-enrichment:${userId}:${digest2}`;
44493
44994
  }
44494
44995
  function inlineSourceDigest(headers, rows) {
44495
44996
  const values = rows.map((row) => headers.map((header) => row[header] ?? null));
44496
- return (0, import_node_crypto29.createHash)("sha256").update(stableCanonicalJson({ headers, values })).digest("hex");
44997
+ return (0, import_node_crypto30.createHash)("sha256").update(stableCanonicalJson({ headers, values })).digest("hex");
44497
44998
  }
44498
44999
  function issueMessage(error) {
44499
45000
  const issue = error.issues[0];
@@ -44691,11 +45192,11 @@ async function resolveInput(ownerId2, parsed) {
44691
45192
  sourceDigest: sourceDigest ?? inlineSourceDigest(normalized.headers, rows)
44692
45193
  };
44693
45194
  }
44694
- var import_node_crypto29, import_hono18, import_zod31, SourceSchema, StartSchema, TERMINAL, leadListEnrichmentApp;
45195
+ var import_node_crypto30, import_hono18, import_zod31, SourceSchema, StartSchema, TERMINAL, leadListEnrichmentApp;
44695
45196
  var init_lead_list_enrichment_routes = __esm({
44696
45197
  "src/api/lead-list-enrichment-routes.ts"() {
44697
45198
  "use strict";
44698
- import_node_crypto29 = require("crypto");
45199
+ import_node_crypto30 = require("crypto");
44699
45200
  import_hono18 = require("hono");
44700
45201
  import_zod31 = require("zod");
44701
45202
  init_api_auth();
@@ -48162,7 +48663,7 @@ async function readManifestFromSummary(summary) {
48162
48663
  function webhookSignature(body, timestamp2) {
48163
48664
  const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
48164
48665
  if (!secret2) return null;
48165
- return (0, import_node_crypto30.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
48666
+ return (0, import_node_crypto31.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
48166
48667
  }
48167
48668
  async function deliverWorkflowWebhook(input) {
48168
48669
  if (!input.webhookUrl) return;
@@ -48369,11 +48870,11 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
48369
48870
  }
48370
48871
  return { dispatched: results.length, results };
48371
48872
  }
48372
- var import_node_crypto30, import_promises14, import_hono21, import_zod41, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
48873
+ var import_node_crypto31, import_promises14, import_hono21, import_zod41, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
48373
48874
  var init_workflow_routes = __esm({
48374
48875
  "src/api/workflow-routes.ts"() {
48375
48876
  "use strict";
48376
- import_node_crypto30 = require("crypto");
48877
+ import_node_crypto31 = require("crypto");
48377
48878
  import_promises14 = require("fs/promises");
48378
48879
  import_hono21 = require("hono");
48379
48880
  import_zod41 = require("zod");
@@ -48654,7 +49155,7 @@ var init_workflow_routes = __esm({
48654
49155
  // src/serp-intelligence/page-snapshot-extractor.ts
48655
49156
  function sha2562(value) {
48656
49157
  if (!value) return null;
48657
- return (0, import_node_crypto31.createHash)("sha256").update(value).digest("hex");
49158
+ return (0, import_node_crypto32.createHash)("sha256").update(value).digest("hex");
48658
49159
  }
48659
49160
  function countWords(markdown) {
48660
49161
  const matches = markdown.trim().match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu);
@@ -48954,11 +49455,11 @@ async function capturePageSnapshots(targets, options = {}) {
48954
49455
  }
48955
49456
  };
48956
49457
  }
48957
- var import_node_crypto31, import_p_limit6, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
49458
+ var import_node_crypto32, import_p_limit6, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
48958
49459
  var init_page_snapshot_extractor = __esm({
48959
49460
  "src/serp-intelligence/page-snapshot-extractor.ts"() {
48960
49461
  "use strict";
48961
- import_node_crypto31 = require("crypto");
49462
+ import_node_crypto32 = require("crypto");
48962
49463
  import_p_limit6 = __toESM(require("p-limit"), 1);
48963
49464
  init_kpo_extractor();
48964
49465
  init_url_utils();
@@ -49455,21 +49956,21 @@ async function logRequestEventBestEffort(input) {
49455
49956
  }
49456
49957
  }
49457
49958
  function captureBillingKeys(userId, suppliedKey, body) {
49458
- const responseKey = suppliedKey?.trim() || (0, import_node_crypto32.randomUUID)();
49459
- const requestFingerprint3 = (0, import_node_crypto32.createHash)("sha256").update(JSON.stringify(body)).digest("hex");
49460
- const keyDigest = (0, import_node_crypto32.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
49959
+ const responseKey = suppliedKey?.trim() || (0, import_node_crypto33.randomUUID)();
49960
+ const requestFingerprint3 = (0, import_node_crypto33.createHash)("sha256").update(JSON.stringify(body)).digest("hex");
49961
+ const keyDigest = (0, import_node_crypto33.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
49461
49962
  return {
49462
49963
  responseKey,
49463
49964
  debitKey: `serp-capture:${userId}:${keyDigest}`,
49464
49965
  debitDescription: `${body.query} [request:${requestFingerprint3}]`
49465
49966
  };
49466
49967
  }
49467
- var import_hono22, import_node_crypto32, SERP_INTELLIGENCE_RATE_LIMIT, SERP_INTELLIGENCE_RATE_WINDOW_SECONDS, POST_CAPTURE_ROUTE_LABEL, POST_PAGE_SNAPSHOTS_ROUTE_LABEL, SERP_CAPTURE_BILLING_SOURCE, serpIntelligenceApp;
49968
+ var import_hono22, import_node_crypto33, SERP_INTELLIGENCE_RATE_LIMIT, SERP_INTELLIGENCE_RATE_WINDOW_SECONDS, POST_CAPTURE_ROUTE_LABEL, POST_PAGE_SNAPSHOTS_ROUTE_LABEL, SERP_CAPTURE_BILLING_SOURCE, serpIntelligenceApp;
49468
49969
  var init_serp_intelligence_routes = __esm({
49469
49970
  "src/api/serp-intelligence-routes.ts"() {
49470
49971
  "use strict";
49471
49972
  import_hono22 = require("hono");
49472
- import_node_crypto32 = require("crypto");
49973
+ import_node_crypto33 = require("crypto");
49473
49974
  init_browser_service_env();
49474
49975
  init_page_snapshot_extractor();
49475
49976
  init_serp_capture_service();
@@ -49706,7 +50207,7 @@ var PACKAGE_VERSION;
49706
50207
  var init_version = __esm({
49707
50208
  "src/version.ts"() {
49708
50209
  "use strict";
49709
- PACKAGE_VERSION = "0.66.5";
50210
+ PACKAGE_VERSION = "0.66.7";
49710
50211
  }
49711
50212
  });
49712
50213
 
@@ -49775,11 +50276,20 @@ seam is noted so you can chain them.
49775
50276
  - Plan a new or edited entity -> **commons_prepare_entity**. This is the Commons equivalent of
49776
50277
  prepare-memory-write: it returns the live entity profile contract, duplicate candidates, tag reuse/review/create
49777
50278
  guidance, recommended sections, optional sections, and heading guidance before any write occurs.
49778
- - Validate a composed entity without writing -> **commons_validate_entity**. Use it after composing and before submit
49779
- to catch missing featured images, missing source/body evidence, duplicate candidates, and
49780
- unsupported placeholder sections.
50279
+ - Host a featured image -> **commons_host_image**. The normal write sequence is **commons_prepare_entity ->
50280
+ compose the payload -> commons_host_image -> commons_validate_entity -> commons_submit_entity**. Put the
50281
+ permanent returned URL in featuredImage.url. Use imageBase64 for available attachment bytes within the request
50282
+ bound, or a stable direct public HTTPS image URL; never use a caller-local path, opaque chat attachment reference,
50283
+ HTML page, private URL, or temporary/signed URL.
50284
+ - Validate a composed entity without writing -> **commons_validate_entity**. Use it after image hosting and before
50285
+ submit. Validation does not fetch or store image bytes; an external/unregistered featured image is reported in
50286
+ imageDiagnostics as not publishable until commons_host_image returns its permanent Commons URL. Validation also
50287
+ catches missing source/body evidence, duplicate candidates, and unsupported placeholder sections.
49781
50288
  - Create or edit a public wiki entity -> **commons_submit_entity**. This is a governed proposal write,
49782
- not direct rendered HTML editing. Normal workflow is prepare -> validate -> submit. Pick the closest entity type
50289
+ not direct rendered HTML editing. Submit retains defensive auto-hosting for a stable direct public HTTPS image,
50290
+ but image ingress failure returns an actionable tool error: host different bytes or a different stable source,
50291
+ replace featuredImage.url, validate again, and retry the same intended write with the same idempotency key.
50292
+ Pick the closest entity type
49783
50293
  first: SoftwareApplication, Organization, Person, Event, Place, Taxon, ScienceConcept, MathConcept, TechArticle,
49784
50294
  Trail, or PublicArticle.
49785
50295
  Creating a new entity costs 10 Credits; editing an existing entity costs 2 Credits. Preparation, validation,
@@ -51521,7 +52031,7 @@ var init_contracts = __esm({
51521
52031
  });
51522
52032
 
51523
52033
  // src/mcp/mcp-tool-schemas.ts
51524
- var import_zod45, WEBSITE_URL_OR_DOMAIN_ERROR, WebsiteUrlOrDomainSchema, HarvestPaaInputSchema, HarvestPaaHostedInputSchema, ExtractUrlBaseInputSchema, ExtractUrlInputSchema, ExtractUrlLocalInputSchema, DiffPageBaseInputSchema, DiffPageInputSchema, DiffPageLocalInputSchema, MapSiteUrlsInputSchema, MapWaybackSnapshotsInputSchema, ExtractSiteInputSchema, AnalyzeSiteSimilarityInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, SiteExportReadInputSchema, SiteExportImageInputSchema, ArchiveReadInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, RedditTrendingInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, LeadScalarSchema2, LeadRowSchema, LeadColumnMapSchema2, LeadRowsImportSourceSchema, LeadCsvTextImportSourceSchema, LeadUploadImportSourceSchema, LeadListUploadStartInputSchema, LeadListImportInputSchema, LeadRowsEnrichmentSourceSchema, ImportedLeadListSourceSchema, LeadListEnrichInputSchema, LeadListEnrichStatusInputSchema, LocationMarketsInputSchema, CommonsSearchEntitiesInputSchema, CommonsGetEntityInputSchema, CommonsGetEntityLinksetInputSchema, CommonsFeaturedImageInputSchema, CommonsMediaInputSchema, CommonsCitationInputSchema, CommonsSourceInputSchema, CommonsRelatedLinkInputSchema, CommonsClaimInputSchema, CommonsPrepareEntityInputSchema, CommonsSubmitEntityInputSchema, CommonsValidateEntityInputSchema, CommonsGetEntityLedgerInputSchema, CommonsHostImageInputSchema, CommonsGetProposalInputSchema, CommonsSaveFilterInputSchema, CommonsListFiltersInputSchema, CommonsListNeedsLinksInputSchema, CommonsGenericOutputSchema, DirectoryWorkflowStatusInputSchema, LocalSourcebookSubmitInputSchema, LocalSourcebookCategorySchema, LocalSourcebookSchemaTypeInputSchema, LocalSourcebookTagCandidateObjectSchema, LocalSourcebookTagDecisionObjectSchema, LocalSourcebookIdentityObjectSchema, GetLocalSourcebookContractInputSchema, ListLocalSourcebookTagsInputSchema, ResolveLocalSourcebookTagsInputSchema, PrepareLocalSourcebookWriteInputSchema, ValidateLocalSourcebookWriteInputSchema, LocalSourcebookCaptureInputSchema, LocalSourcebookSubmissionStatusInputSchema, LocalSourcebookRefreshInputSchema, LocalSourcebookOutputSchema, ArtifactPointerOutputSchema, EditorialReadingRoomSiteSchema, EditorialReadingRoomImageSchema, EditorialReadingRoomArticleSchema, EditorialReadingRoomGuideInputSchema, EditorialReadingRoomGuideOutputSchema, CreateEditorialReadingRoomInputSchema, EditorialReadingRoomArtifactSchema, CreateEditorialReadingRoomOutputSchema, RenewEditorialReadingRoomDownloadInputSchema, RenewEditorialReadingRoomDownloadOutputSchema, CommonsPublicationSubdomainSchema, CommonsPreparePublicationInputSchema, CommonsValidatePublicationInputSchema, CommonsClaimPublicationInputSchema, CommonsPublishEditorialInputSchema, CommonsUpdateEditorialArticleInputSchema, CommonsGetPublicationInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryCsvArtifactOutput, DirectoryWorkflowOutputSchema, LeadSuggestedColumnOutputSchema, LeadColumnMapSuggestionOutputSchema, LeadArtifactOutputSchema, LeadListUploadStartOutputSchema, LeadListImportOutputSchema, LeadCandidateOutputSchema, LeadProgressOutputSchema, LeadBillingOutputSchema, LeadAssociatedPersonSourceOutputSchema, LeadAssociatedPersonOutputSchema, LeadSampleRowOutputSchema, LeadListEnrichmentOutputSchema, LocationDatasetProvenanceOutput, LocationMarketsOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, PageMediaAssetOutput, PageMediaArtifactOutput, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, SiteExportReadOutputSchema, SiteExportImageOutputSchema, ArchiveEntryOutputSchema, ArchiveReadOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, WaybackCaptureOutputSchema, MapWaybackSnapshotsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, RedditTrendingOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, TestServiceConnectionInputSchema, TestServiceConnectionOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, SearchConsoleTableColumnSchema, SearchConsoleTableFilterSchema, ExportSearchConsoleTableDataInputSchema, ExportSearchConsoleTableDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GmailSearchContactsInputSchema, GmailSearchContactsOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
52034
+ var import_zod45, WEBSITE_URL_OR_DOMAIN_ERROR, WebsiteUrlOrDomainSchema, HarvestPaaInputSchema, HarvestPaaHostedInputSchema, ExtractUrlBaseInputSchema, ExtractUrlInputSchema, ExtractUrlLocalInputSchema, DiffPageBaseInputSchema, DiffPageInputSchema, DiffPageLocalInputSchema, MapSiteUrlsInputSchema, MapWaybackSnapshotsInputSchema, ExtractSiteInputSchema, AnalyzeSiteSimilarityInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, SiteExportReadInputSchema, SiteExportImageInputSchema, ArchiveReadInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, RedditTrendingInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, LeadScalarSchema2, LeadRowSchema, LeadColumnMapSchema2, LeadRowsImportSourceSchema, LeadCsvTextImportSourceSchema, LeadUploadImportSourceSchema, LeadListUploadStartInputSchema, LeadListImportInputSchema, LeadRowsEnrichmentSourceSchema, ImportedLeadListSourceSchema, LeadListEnrichInputSchema, LeadListEnrichStatusInputSchema, LocationMarketsInputSchema, CommonsSearchEntitiesInputSchema, CommonsGetEntityInputSchema, CommonsGetEntityLinksetInputSchema, CommonsFeaturedImageInputSchema, CommonsMediaInputSchema, CommonsCitationInputSchema, CommonsSourceInputSchema, CommonsRelatedLinkInputSchema, CommonsClaimInputSchema, CommonsPrepareEntityInputSchema, CommonsSubmitEntityInputSchema, CommonsValidateEntityInputSchema, CommonsGetEntityLedgerInputSchema, CommonsHostImageInputSchema, CommonsGetProposalInputSchema, CommonsSaveFilterInputSchema, CommonsListFiltersInputSchema, CommonsListNeedsLinksInputSchema, CommonsGenericOutputSchema, CommonsFeaturedImageDiagnosticsOutputSchema, CommonsValidateEntityOutputSchema, DirectoryWorkflowStatusInputSchema, LocalSourcebookSubmitInputSchema, LocalSourcebookCategorySchema, LocalSourcebookSchemaTypeInputSchema, LocalSourcebookTagCandidateObjectSchema, LocalSourcebookTagDecisionObjectSchema, LocalSourcebookIdentityObjectSchema, GetLocalSourcebookContractInputSchema, ListLocalSourcebookTagsInputSchema, ResolveLocalSourcebookTagsInputSchema, PrepareLocalSourcebookWriteInputSchema, ValidateLocalSourcebookWriteInputSchema, LocalSourcebookCaptureInputSchema, LocalSourcebookSubmissionStatusInputSchema, LocalSourcebookRefreshInputSchema, LocalSourcebookOutputSchema, ArtifactPointerOutputSchema, EditorialReadingRoomSiteSchema, EditorialReadingRoomImageSchema, EditorialReadingRoomArticleSchema, EditorialReadingRoomGuideInputSchema, EditorialReadingRoomGuideOutputSchema, CreateEditorialReadingRoomInputSchema, EditorialReadingRoomArtifactSchema, CreateEditorialReadingRoomOutputSchema, RenewEditorialReadingRoomDownloadInputSchema, RenewEditorialReadingRoomDownloadOutputSchema, CommonsPublicationSubdomainSchema, CommonsPreparePublicationInputSchema, CommonsValidatePublicationInputSchema, CommonsClaimPublicationInputSchema, CommonsPublishEditorialInputSchema, CommonsUpdateEditorialArticleInputSchema, CommonsGetPublicationInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryCsvArtifactOutput, DirectoryWorkflowOutputSchema, LeadSuggestedColumnOutputSchema, LeadColumnMapSuggestionOutputSchema, LeadArtifactOutputSchema, LeadListUploadStartOutputSchema, LeadListImportOutputSchema, LeadCandidateOutputSchema, LeadProgressOutputSchema, LeadBillingOutputSchema, LeadAssociatedPersonSourceOutputSchema, LeadAssociatedPersonOutputSchema, LeadSampleRowOutputSchema, LeadListEnrichmentOutputSchema, LocationDatasetProvenanceOutput, LocationMarketsOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, PageMediaAssetOutput, PageMediaArtifactOutput, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, SiteExportReadOutputSchema, SiteExportImageOutputSchema, ArchiveEntryOutputSchema, ArchiveReadOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, WaybackCaptureOutputSchema, MapWaybackSnapshotsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, RedditTrendingOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, TestServiceConnectionInputSchema, TestServiceConnectionOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, SearchConsoleTableColumnSchema, SearchConsoleTableFilterSchema, ExportSearchConsoleTableDataInputSchema, ExportSearchConsoleTableDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GmailSearchContactsInputSchema, GmailSearchContactsOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
51525
52035
  var init_mcp_tool_schemas = __esm({
51526
52036
  "src/mcp/mcp-tool-schemas.ts"() {
51527
52037
  "use strict";
@@ -51934,7 +52444,7 @@ var init_mcp_tool_schemas = __esm({
51934
52444
  idOrSlug: import_zod45.z.string().trim().min(1).max(180).describe("Transparent Commons TPW-Q entity id or /wiki/ slug whose governed RFC 9264 linkset should be read.")
51935
52445
  };
51936
52446
  CommonsFeaturedImageInputSchema = import_zod45.z.object({
51937
- url: import_zod45.z.string().url().describe("Required public image URL for a publishable entity. Use extract_url includeFeaturedImage or preserved media when available."),
52447
+ url: import_zod45.z.string().url().describe("For a publish-ready entity, use the permanent URL returned by commons_host_image. Submit can auto-host a stable direct public HTTPS image URL as a compatibility fallback, but validation reports any unregistered external URL as not ready. Do not use a chat attachment reference, caller-local path, temporary or signed URL, private URL, or HTML page URL."),
51938
52448
  alt: import_zod45.z.string().trim().max(500).optional(),
51939
52449
  caption: import_zod45.z.string().trim().max(1e3).optional(),
51940
52450
  sourceUrl: import_zod45.z.string().url().optional(),
@@ -52026,14 +52536,14 @@ var init_mcp_tool_schemas = __esm({
52026
52536
  maxCandidates: import_zod45.z.number().int().min(1).max(20).default(8).describe("Maximum duplicate candidates to return.")
52027
52537
  };
52028
52538
  CommonsSubmitEntityInputSchema = {
52029
- idempotencyKey: import_zod45.z.string().trim().min(8).max(200).describe("Required unique opaque ID for this intended Commons write. Reuse only when retrying the same write after a timeout; use a new value for each intentional create or edit."),
52539
+ idempotencyKey: import_zod45.z.string().trim().min(8).max(200).describe("Required unique opaque ID for this intended Commons write. Reuse it when retrying the same intended write after a timeout or after correcting a pre-persistence image-ingress failure; use a new value for each intentional create or edit."),
52030
52540
  title: import_zod45.z.string().trim().min(3).max(180).describe("Public page title. This is also used for duplicate slug/entity checks when slug is omitted."),
52031
52541
  description: import_zod45.z.string().trim().min(8).max(600).describe("Short article summary and schema.org description."),
52032
52542
  slug: import_zod45.z.string().trim().max(180).optional().describe("Optional URL slug. Omit to derive one from title."),
52033
52543
  entityId: import_zod45.z.string().trim().max(80).optional().describe("Existing Transparent Public Wiki entity id when proposing an edit. New entities normally omit this and receive a TPW-Q id."),
52034
52544
  entityType: import_zod45.z.string().trim().max(120).default("PublicArticle").describe("Backend Wikidata-style type. Prefer precise entity classes such as SoftwareApplication, Organization, Person, Event, Place, Taxon, ScienceConcept, MathConcept, TechArticle, or PublicArticle; the public article structure should match the selected type."),
52035
52545
  disambiguationName: import_zod45.z.string().trim().max(240).optional().describe("Clarifying name used when the concept could be confused with another entity."),
52036
- featuredImage: CommonsFeaturedImageInputSchema.optional().describe("Required for auto-published public entities. The image is also added to the media manifest if absent."),
52546
+ featuredImage: CommonsFeaturedImageInputSchema.optional().describe("Required for auto-published public entities. First call commons_host_image and place its returned permanent URL here; validation does not fetch external images and reports unregistered URLs as not publishable. Submit retains a compatibility fallback that auto-hosts a stable direct public HTTPS image URL. The image is also added to the media manifest if absent."),
52037
52547
  source: CommonsSourceInputSchema.optional().describe("Source provenance. Store original URL, source byline, and origin canonical here; canonical does not replace rights review."),
52038
52548
  tags: import_zod45.z.array(import_zod45.z.string().trim().min(1).max(80)).max(24).optional().describe("Standardized topic tags. Use existing/searchable concepts when possible."),
52039
52549
  keywords: import_zod45.z.array(import_zod45.z.string().trim().min(1).max(120)).max(50).optional().describe("SEO and retrieval keywords."),
@@ -52095,8 +52605,8 @@ var init_mcp_tool_schemas = __esm({
52095
52605
  idOrSlug: import_zod45.z.string().trim().min(1).max(180).describe("Published entity id or /wiki/ slug whose contribution ledger should be read.")
52096
52606
  };
52097
52607
  CommonsHostImageInputSchema = {
52098
- sourceUrl: import_zod45.z.string().url().optional().describe("Public https image URL to download and host. The wiki never serves a third-party URL directly, so pass the original source here rather than putting it on the entity."),
52099
- imageBase64: import_zod45.z.string().min(16).optional().describe("Base64 image bytes for an image you already hold. Use for images under about 3 MB; larger files should be published from a URL."),
52608
+ sourceUrl: import_zod45.z.string().url().optional().describe("Stable direct public HTTPS URL whose response is the image bytes to host. Do not pass an HTML webpage, chat attachment reference, caller-local path, temporary or signed URL, or private/authenticated URL."),
52609
+ imageBase64: import_zod45.z.string().min(16).optional().describe("Base64 image bytes for an image you already hold, including forwarded chat attachment bytes. Prefer this over a temporary attachment URL when the serialized request remains under about 3 MB; larger files require a stable direct public HTTPS source URL."),
52100
52610
  alt: import_zod45.z.string().trim().max(500).optional().describe("Alt text describing the image for readers who cannot see it."),
52101
52611
  license: import_zod45.z.string().trim().max(240).optional().describe("License or usage terms of the original image, preserved with the hosted copy."),
52102
52612
  attribution: import_zod45.z.string().trim().max(500).optional().describe("Credit line for the original photographer, publication, or archive.")
@@ -52128,6 +52638,30 @@ var init_mcp_tool_schemas = __esm({
52128
52638
  error: import_zod45.z.string().optional(),
52129
52639
  message: import_zod45.z.string().optional()
52130
52640
  };
52641
+ CommonsFeaturedImageDiagnosticsOutputSchema = import_zod45.z.object({
52642
+ state: import_zod45.z.enum(["missing", "external_unhosted", "commons_hosted"]).describe("Storage-readiness state for the candidate featured image."),
52643
+ url: import_zod45.z.string().url().nullable().describe("Normalized candidate featured-image URL, or null when none was supplied."),
52644
+ commonsImageId: import_zod45.z.string().nullable().describe("Registered Commons image ID when metadata proves the image is hosted, otherwise null."),
52645
+ readyForPublish: import_zod45.z.boolean().describe("Whether Commons storage metadata proves this featured image is ready to publish."),
52646
+ submitWillAttemptAutoHost: import_zod45.z.boolean().describe("Whether submit will use its compatibility fallback to try hosting this external image."),
52647
+ requiredAction: import_zod45.z.string().nullable().describe("Action the caller must take before submit, or null when the image is ready.")
52648
+ }).describe("Featured-image storage readiness determined without fetching or storing external bytes.");
52649
+ CommonsValidateEntityOutputSchema = {
52650
+ ok: import_zod45.z.boolean(),
52651
+ data: import_zod45.z.object({
52652
+ ok: import_zod45.z.literal(true),
52653
+ valid: import_zod45.z.boolean(),
52654
+ publishable: import_zod45.z.boolean(),
52655
+ autoPublishEligible: import_zod45.z.boolean(),
52656
+ errors: import_zod45.z.array(import_zod45.z.string()),
52657
+ warnings: import_zod45.z.array(import_zod45.z.string()),
52658
+ imageDiagnostics: import_zod45.z.object({
52659
+ featuredImage: CommonsFeaturedImageDiagnosticsOutputSchema
52660
+ }).describe("Read-only image readiness and recovery guidance for this payload.")
52661
+ }).passthrough().optional(),
52662
+ error: import_zod45.z.string().optional(),
52663
+ message: import_zod45.z.string().optional()
52664
+ };
52131
52665
  DirectoryWorkflowStatusInputSchema = {
52132
52666
  jobId: import_zod45.z.string().trim().min(1).describe("The jobId returned by directory_workflow. Poll until status is complete, partial, empty, or failed.")
52133
52667
  };
@@ -54839,11 +55373,11 @@ function requireTasksCapability(capabilityValue) {
54839
55373
  );
54840
55374
  }
54841
55375
  function taskKey(secret2) {
54842
- return (0, import_node_crypto33.createHash)("sha256").update("mcp-scraper-task-handle\0", "utf8").update(secret2, "utf8").digest();
55376
+ return (0, import_node_crypto34.createHash)("sha256").update("mcp-scraper-task-handle\0", "utf8").update(secret2, "utf8").digest();
54843
55377
  }
54844
55378
  function encodeTaskHandle(payload, secret2) {
54845
- const nonce = (0, import_node_crypto33.randomBytes)(12);
54846
- const cipher = (0, import_node_crypto33.createCipheriv)("aes-256-gcm", taskKey(secret2), nonce);
55379
+ const nonce = (0, import_node_crypto34.randomBytes)(12);
55380
+ const cipher = (0, import_node_crypto34.createCipheriv)("aes-256-gcm", taskKey(secret2), nonce);
54847
55381
  const ciphertext = Buffer.concat([
54848
55382
  cipher.update(JSON.stringify(payload), "utf8"),
54849
55383
  cipher.final()
@@ -54860,7 +55394,7 @@ function decodeTaskHandle(taskId, secret2, ownerId2) {
54860
55394
  const nonce = bytes.subarray(0, 12);
54861
55395
  const tag = bytes.subarray(bytes.length - 16);
54862
55396
  const ciphertext = bytes.subarray(12, bytes.length - 16);
54863
- const decipher = (0, import_node_crypto33.createDecipheriv)("aes-256-gcm", taskKey(secret2), nonce);
55397
+ const decipher = (0, import_node_crypto34.createDecipheriv)("aes-256-gcm", taskKey(secret2), nonce);
54864
55398
  decipher.setAuthTag(tag);
54865
55399
  const parsed = JSON.parse(Buffer.concat([
54866
55400
  decipher.update(ciphertext),
@@ -55224,12 +55758,12 @@ async function handleMcpTasksHttpRequest(request, executor, options) {
55224
55758
  return taskHttpError(id, error instanceof import_server.ProtocolError ? error : new import_server.ProtocolError(-32603, error instanceof Error ? error.message : "Internal error"));
55225
55759
  }
55226
55760
  }
55227
- var import_server, import_node_crypto33, import_zod46, MCP_TASKS_EXTENSION_ID, TASK_HANDLE_VERSION, TASK_TTL_MS, TASK_POLL_INTERVAL_MS, TASK_INVALID_PARAMS, TASK_MISSING_CAPABILITY, TASK_START_TOOL_NAMES;
55761
+ var import_server, import_node_crypto34, import_zod46, MCP_TASKS_EXTENSION_ID, TASK_HANDLE_VERSION, TASK_TTL_MS, TASK_POLL_INTERVAL_MS, TASK_INVALID_PARAMS, TASK_MISSING_CAPABILITY, TASK_START_TOOL_NAMES;
55228
55762
  var init_mcp_tasks_extension = __esm({
55229
55763
  "src/mcp/mcp-tasks-extension.ts"() {
55230
55764
  "use strict";
55231
55765
  import_server = require("@modelcontextprotocol/server");
55232
- import_node_crypto33 = require("crypto");
55766
+ import_node_crypto34 = require("crypto");
55233
55767
  import_zod46 = require("zod");
55234
55768
  init_mcp_tool_schemas();
55235
55769
  MCP_TASKS_EXTENSION_ID = "io.modelcontextprotocol/tasks";
@@ -55537,7 +56071,7 @@ var init_analytics_mcp_tools = __esm({
55537
56071
 
55538
56072
  // src/mcp/paa-mcp-server.ts
55539
56073
  function hashOwnerId(callerKey) {
55540
- return (0, import_node_crypto34.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
56074
+ return (0, import_node_crypto35.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
55541
56075
  }
55542
56076
  function liveWebToolAnnotations(title) {
55543
56077
  return {
@@ -56024,9 +56558,9 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56024
56558
  }, async (input) => executor.commonsPrepareEntity(input));
56025
56559
  server.registerTool("commons_validate_entity", {
56026
56560
  title: "Transparent Commons Validate Entity",
56027
- description: "Validate a proposed Transparent Commons entity payload without writing. Checks publishable basics, featured image, source/body evidence, existing-entity conflict state, heading profile alignment, and unsupported placeholder sections. Call this after composing the page and before commons_submit_entity.",
56561
+ description: "Read-only validation for a proposed Transparent Commons entity payload. Call it after commons_prepare_entity, composing the page, and commons_host_image. It does not fetch or store image bytes: an external or otherwise unregistered featured image remains not publishable until commons_host_image returns the permanent Commons URL. Inspect imageDiagnostics for readiness and the required action before commons_submit_entity. Also checks source/body evidence, existing-entity conflict state, heading profile alignment, and unsupported placeholder sections.",
56028
56562
  inputSchema: CommonsValidateEntityInputSchema,
56029
- outputSchema: recordOutputSchema("commons_validate_entity", CommonsGenericOutputSchema),
56563
+ outputSchema: recordOutputSchema("commons_validate_entity", CommonsValidateEntityOutputSchema),
56030
56564
  annotations: {
56031
56565
  title: "Transparent Commons Validate Entity",
56032
56566
  readOnlyHint: true,
@@ -56037,7 +56571,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56037
56571
  }, async (input) => executor.commonsValidateEntity(input));
56038
56572
  server.registerTool("commons_submit_entity", {
56039
56573
  title: "Transparent Commons Governed Entity Write",
56040
- description: "Create or propose an edit to a Transparent Commons public wiki entity through the governed MCP Scraper write plane. A new entity costs 10 Credits; an edit to an existing entity costs 2 Credits. Preparation and validation are free, and idempotent retries are not charged twice. This never edits rendered HTML directly. Normal workflow is commons_prepare_entity, compose, commons_validate_entity, then submit. commons_prepare_entity returns the entity profile contract and section guidance to compose against. Transparent Commons is an open wiki: any account may edit any entity, and every edit publishes and is recorded on the public contribution ledger. Publishing requires a featured image and body/source evidence. Pass baseRevision when editing so the ledger can show whether the edit was written on top of a newer revision. Requires idempotencyKey.",
56574
+ description: "Charged governed write that creates or proposes an edit to a Transparent Commons public wiki entity. A new entity costs 10 Credits; an edit costs 2 Credits. Preparation, image hosting, and validation are free, and idempotent retries are not charged twice. Normal workflow is commons_prepare_entity, compose the page, commons_host_image, commons_validate_entity, then submit with the returned permanent featuredImage.url. Submit retains a compatibility fallback that auto-hosts a stable direct public HTTPS image URL; if image ingress fails, it returns an actionable image tool error so you can host different bytes or a different stable source, replace the URL, validate again, and retry the same intended write with the same idempotencyKey. This never edits rendered HTML directly. Transparent Commons is an open wiki: any account may edit any entity, and every applied edit is recorded on the public contribution ledger. Publishing requires a ready featured image and body/source evidence. Pass baseRevision when editing so the ledger can show whether the edit was written on top of a newer revision. Requires idempotencyKey.",
56041
56575
  inputSchema: CommonsSubmitEntityInputSchema,
56042
56576
  outputSchema: recordOutputSchema("commons_submit_entity", CommonsGenericOutputSchema),
56043
56577
  annotations: {
@@ -56063,7 +56597,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56063
56597
  }, async (input) => executor.commonsGetEntityLedger(input));
56064
56598
  server.registerTool("commons_host_image", {
56065
56599
  title: "Host a Transparent Commons Image",
56066
- description: "Host an image from a public https URL or base64 bytes, returning a permanent Commons URL. Commons never serves a third-party image URL \u2014 commons_submit_entity hosts external images automatically, so use this only to host one before or independently of a write. JPEG, PNG, GIF, WebP up to 10 MB; SVG rejected. Identical bytes are stored once.",
56600
+ description: "Normal prerequisite for any featured image whose URL was not already returned by commons_host_image. Host image bytes from imageBase64 when you already have an attachment and the serialized request stays under about 3 MB, or from a stable direct public HTTPS image URL; do not pass a webpage, caller-local path, private URL, or temporary/signed attachment URL. Returns the permanent Commons URL to place in featuredImage.url before commons_validate_entity and commons_submit_entity. Submit can still auto-host a stable public source as a compatibility fallback. JPEG, PNG, GIF, and WebP are accepted up to 10 MB; SVG is rejected. Identical bytes are stored once.",
56067
56601
  inputSchema: CommonsHostImageInputSchema,
56068
56602
  outputSchema: recordOutputSchema("commons_host_image", CommonsGenericOutputSchema),
56069
56603
  annotations: {
@@ -56525,7 +57059,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56525
57059
  annotations: { title: "Set Scheduled Action Connections", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
56526
57060
  }, async (input) => executor.setScheduledActionConnections(input));
56527
57061
  }
56528
- var import_server2, import_zod48, import_node_fs11, import_node_path17, import_node_crypto34, ACTION_CONFIRMATION_SCHEMA;
57062
+ var import_server2, import_zod48, import_node_fs11, import_node_path17, import_node_crypto35, ACTION_CONFIRMATION_SCHEMA;
56529
57063
  var init_paa_mcp_server = __esm({
56530
57064
  "src/mcp/paa-mcp-server.ts"() {
56531
57065
  "use strict";
@@ -56533,7 +57067,7 @@ var init_paa_mcp_server = __esm({
56533
57067
  import_zod48 = require("zod");
56534
57068
  import_node_fs11 = require("fs");
56535
57069
  import_node_path17 = require("path");
56536
- import_node_crypto34 = require("crypto");
57070
+ import_node_crypto35 = require("crypto");
56537
57071
  init_version();
56538
57072
  init_rates();
56539
57073
  init_mcp_response_formatter();
@@ -56618,7 +57152,8 @@ function httpErrorPayload(path6, res, data) {
56618
57152
  const safeCode = privateUpstreamState ? "service_unavailable" : recognized ? candidateCode : normalizePublicErrorCode(candidateCode);
56619
57153
  const bodyMessage = objectData ? objectData.message ?? objectData.error ?? rawCode : data;
56620
57154
  return {
56621
- ...recognized && objectData ? objectData : {},
57155
+ ...recognized && objectData && safeCode !== "mcp_request_timeout" ? objectData : {},
57156
+ ...recognized ? { code: safeCode } : {},
56622
57157
  error: safeCode,
56623
57158
  error_code: safeCode,
56624
57159
  error_type: recognized && typeof objectData?.error_type === "string" ? objectData.error_type : "http",
@@ -56626,7 +57161,7 @@ function httpErrorPayload(path6, res, data) {
56626
57161
  status: res.status,
56627
57162
  statusText: res.statusText,
56628
57163
  path: path6,
56629
- message: recognized && typeof bodyMessage === "string" && bodyMessage.trim() ? sanitizeVendorName(bodyMessage) : publicErrorMessage(safeCode)
57164
+ message: safeCode === "mcp_request_timeout" ? "The connected service operation timed out before returning a result. Retry failed items or use a smaller batch." : recognized && typeof bodyMessage === "string" && bodyMessage.trim() ? sanitizeVendorName(bodyMessage) : publicErrorMessage(safeCode)
56630
57165
  };
56631
57166
  }
56632
57167
  function withDefaultHostedProfile(input) {
@@ -56654,11 +57189,11 @@ function analyticsReportPath(input, report) {
56654
57189
  const suffix2 = query.size ? `?${query.toString()}` : "";
56655
57190
  return `/analytics/sites/${encodeURIComponent(input.siteId)}/${report}${suffix2}`;
56656
57191
  }
56657
- var import_node_crypto35, HttpMcpToolExecutor;
57192
+ var import_node_crypto36, HttpMcpToolExecutor;
56658
57193
  var init_http_mcp_tool_executor = __esm({
56659
57194
  "src/mcp/http-mcp-tool-executor.ts"() {
56660
57195
  "use strict";
56661
- import_node_crypto35 = require("crypto");
57196
+ import_node_crypto36 = require("crypto");
56662
57197
  init_harvest_timeout();
56663
57198
  init_browser_service_env();
56664
57199
  init_errors();
@@ -56727,13 +57262,13 @@ var init_http_mcp_tool_executor = __esm({
56727
57262
  });
56728
57263
  }
56729
57264
  async callDirectoryWorkflowStart(body, explicitIdempotencyKey) {
56730
- const idempotencyKey4 = `mcp-directory-${(0, import_node_crypto35.createHash)("sha256").update(explicitIdempotencyKey).digest("hex")}`;
57265
+ const idempotencyKey4 = `mcp-directory-${(0, import_node_crypto36.createHash)("sha256").update(explicitIdempotencyKey).digest("hex")}`;
56731
57266
  return this.call("/directory/run", body, this.timeoutMs, "POST", {
56732
57267
  "Idempotency-Key": idempotencyKey4
56733
57268
  });
56734
57269
  }
56735
57270
  async callSiteExtractStart(toolName, body, explicitIdempotencyKey) {
56736
- const idempotencyKey4 = `mcp-site-${(0, import_node_crypto35.createHash)("sha256").update(toolName).update("\0").update(explicitIdempotencyKey).digest("hex")}`;
57271
+ const idempotencyKey4 = `mcp-site-${(0, import_node_crypto36.createHash)("sha256").update(toolName).update("\0").update(explicitIdempotencyKey).digest("hex")}`;
56737
57272
  return this.call("/extract-site", body, this.timeoutMs, "POST", {
56738
57273
  "Idempotency-Key": idempotencyKey4
56739
57274
  });
@@ -57177,7 +57712,7 @@ var init_http_mcp_tool_executor = __esm({
57177
57712
  report: input.report,
57178
57713
  format: input.format
57179
57714
  }, this.timeoutMs, "POST", {
57180
- "Idempotency-Key": `analytics-export-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57715
+ "Idempotency-Key": `analytics-export-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57181
57716
  });
57182
57717
  }
57183
57718
  commonsSearchEntities(input) {
@@ -57216,7 +57751,7 @@ var init_http_mcp_tool_executor = __esm({
57216
57751
  commonsSubmitEntity(input) {
57217
57752
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57218
57753
  return this.call("/commons/entities/propose", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57219
- "Idempotency-Key": `commons-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57754
+ "Idempotency-Key": `commons-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57220
57755
  });
57221
57756
  }
57222
57757
  commonsGetEntityLedger(input) {
@@ -57231,7 +57766,7 @@ var init_http_mcp_tool_executor = __esm({
57231
57766
  commonsUpdateEditorialArticle(input) {
57232
57767
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57233
57768
  return this.call("/commons/publications/articles", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57234
- "Idempotency-Key": `commons-article-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57769
+ "Idempotency-Key": `commons-article-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57235
57770
  });
57236
57771
  }
57237
57772
  commonsSaveFilter(input) {
@@ -57252,13 +57787,13 @@ var init_http_mcp_tool_executor = __esm({
57252
57787
  commonsClaimPublication(input) {
57253
57788
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57254
57789
  return this.call("/commons/publications/claim", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57255
- "Idempotency-Key": `commons-publication-claim-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57790
+ "Idempotency-Key": `commons-publication-claim-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57256
57791
  });
57257
57792
  }
57258
57793
  commonsPublishEditorial(input) {
57259
57794
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57260
57795
  return this.call("/commons/publications/publish", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57261
- "Idempotency-Key": `commons-publication-publish-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57796
+ "Idempotency-Key": `commons-publication-publish-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57262
57797
  });
57263
57798
  }
57264
57799
  commonsGetPublication(input) {
@@ -57266,13 +57801,13 @@ var init_http_mcp_tool_executor = __esm({
57266
57801
  return this.getJson(input.subdomain ? `/commons/publications/${encodeURIComponent(input.subdomain)}?${query}` : `/commons/publications/me?${query}`);
57267
57802
  }
57268
57803
  async captureSerpSnapshot(input) {
57269
- const fingerprint2 = (0, import_node_crypto35.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
57804
+ const fingerprint2 = (0, import_node_crypto36.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
57270
57805
  const now = Date.now();
57271
57806
  for (const [pendingFingerprint, pendingEntry] of this.pendingSerpCaptureBillingKeys) {
57272
57807
  if (pendingEntry.expiresAt <= now) this.pendingSerpCaptureBillingKeys.delete(pendingFingerprint);
57273
57808
  }
57274
57809
  const pending = this.pendingSerpCaptureBillingKeys.get(fingerprint2);
57275
- const idempotencyKey4 = pending && pending.expiresAt > now ? pending.key : (0, import_node_crypto35.randomUUID)();
57810
+ const idempotencyKey4 = pending && pending.expiresAt > now ? pending.key : (0, import_node_crypto36.randomUUID)();
57276
57811
  this.pendingSerpCaptureBillingKeys.set(fingerprint2, {
57277
57812
  key: idempotencyKey4,
57278
57813
  expiresAt: now + 15 * 6e4
@@ -63282,7 +63817,7 @@ async function createImageSourceArtifact(args) {
63282
63817
  if (args.content.length === 0 || args.content.length > IMAGE_SOURCE_MAX_BYTES) {
63283
63818
  throw new Error("image_source_size_invalid");
63284
63819
  }
63285
- const id = (0, import_node_crypto36.randomUUID)().replaceAll("-", "");
63820
+ const id = (0, import_node_crypto37.randomUUID)().replaceAll("-", "");
63286
63821
  return createPrivateArtifact({
63287
63822
  policy: policy4(),
63288
63823
  ownerId: args.ownerId,
@@ -63301,11 +63836,11 @@ async function readOwnedImageSourceArtifact(args) {
63301
63836
  maxBytes: IMAGE_SOURCE_MAX_BYTES
63302
63837
  });
63303
63838
  }
63304
- var import_node_crypto36, IMAGE_SOURCE_ARTIFACT_PREFIX, IMAGE_SOURCE_ARTIFACT_TTL_MS, IMAGE_SOURCE_DOWNLOAD_TTL_MS, IMAGE_SOURCE_MAX_BYTES, ALLOWED_IMAGE_TYPES;
63839
+ var import_node_crypto37, IMAGE_SOURCE_ARTIFACT_PREFIX, IMAGE_SOURCE_ARTIFACT_TTL_MS, IMAGE_SOURCE_DOWNLOAD_TTL_MS, IMAGE_SOURCE_MAX_BYTES, ALLOWED_IMAGE_TYPES;
63305
63840
  var init_image_source_artifacts = __esm({
63306
63841
  "src/api/image-source-artifacts.ts"() {
63307
63842
  "use strict";
63308
- import_node_crypto36 = require("crypto");
63843
+ import_node_crypto37 = require("crypto");
63309
63844
  init_private_artifacts();
63310
63845
  IMAGE_SOURCE_ARTIFACT_PREFIX = "image-sources/";
63311
63846
  IMAGE_SOURCE_ARTIFACT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -64919,7 +65454,7 @@ async function runBrowserAgentMigration() {
64919
65454
  }
64920
65455
  async function createExtensionRow(input) {
64921
65456
  const db = getDb();
64922
- const id = `bext_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65457
+ const id = `bext_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
64923
65458
  await db.execute({
64924
65459
  sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
64925
65460
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -64954,7 +65489,7 @@ async function deleteExtensionRow(userId, name) {
64954
65489
  }
64955
65490
  async function createAuthConnectionRow(input) {
64956
65491
  const db = getDb();
64957
- const connectionId = `authc_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65492
+ const connectionId = `authc_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
64958
65493
  await db.execute({
64959
65494
  sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
64960
65495
  VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
@@ -65039,7 +65574,7 @@ async function deleteProfileLabel(userId, profile) {
65039
65574
  }
65040
65575
  async function createSessionRow(input) {
65041
65576
  const db = getDb();
65042
- const id = `bas_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65577
+ const id = `bas_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65043
65578
  await db.execute({
65044
65579
  sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, concurrency_lock_id, last_action_at)
65045
65580
  VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
@@ -65116,7 +65651,7 @@ async function recordAction(input) {
65116
65651
  sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
65117
65652
  VALUES (?, ?, ?, ?, ?, ?)`,
65118
65653
  args: [
65119
- `baa_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
65654
+ `baa_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
65120
65655
  input.sessionId,
65121
65656
  input.type,
65122
65657
  input.params == null ? null : JSON.stringify(input.params),
@@ -65156,11 +65691,11 @@ async function listReplayRows(sessionId) {
65156
65691
  });
65157
65692
  return res.rows;
65158
65693
  }
65159
- var import_node_crypto37, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
65694
+ var import_node_crypto38, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
65160
65695
  var init_browser_agent_db = __esm({
65161
65696
  "src/api/browser-agent-db.ts"() {
65162
65697
  "use strict";
65163
- import_node_crypto37 = require("crypto");
65698
+ import_node_crypto38 = require("crypto");
65164
65699
  init_db();
65165
65700
  _ready2 = false;
65166
65701
  _migrationPromise2 = null;
@@ -66302,7 +66837,7 @@ var init_browser_agent_service = __esm({
66302
66837
 
66303
66838
  // src/api/serp-identity-db.ts
66304
66839
  async function createSerpIdentityRow(input) {
66305
- const id = `serpi_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
66840
+ const id = `serpi_${(0, import_node_crypto39.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
66306
66841
  await getDb().execute({
66307
66842
  sql: `INSERT INTO serp_identities
66308
66843
  (id, user_id, name, kernel_profile_name, kernel_proxy_id, proxy_type, country, status)
@@ -66347,11 +66882,11 @@ async function deleteSerpIdentityRow(userId, name) {
66347
66882
  args: [userId, name]
66348
66883
  });
66349
66884
  }
66350
- var import_node_crypto38;
66885
+ var import_node_crypto39;
66351
66886
  var init_serp_identity_db = __esm({
66352
66887
  "src/api/serp-identity-db.ts"() {
66353
66888
  "use strict";
66354
- import_node_crypto38 = require("crypto");
66889
+ import_node_crypto39 = require("crypto");
66355
66890
  init_db();
66356
66891
  }
66357
66892
  });
@@ -66363,8 +66898,8 @@ function kernelClient() {
66363
66898
  return new import_sdk10.default({ apiKey });
66364
66899
  }
66365
66900
  function backendName(userId, name, resource) {
66366
- const digest2 = (0, import_node_crypto39.createHash)("sha256").update(`${userId}:${name}`).digest("hex").slice(0, 16);
66367
- const nonce = (0, import_node_crypto39.randomUUID)().replace(/-/g, "").slice(0, 8);
66901
+ const digest2 = (0, import_node_crypto40.createHash)("sha256").update(`${userId}:${name}`).digest("hex").slice(0, 16);
66902
+ const nonce = (0, import_node_crypto40.randomUUID)().replace(/-/g, "").slice(0, 8);
66368
66903
  return `mcp-serp-${resource}-${digest2}-${nonce}`;
66369
66904
  }
66370
66905
  function isNotFound2(error) {
@@ -66442,11 +66977,11 @@ async function deleteSerpIdentity(userId, name) {
66442
66977
  throw error;
66443
66978
  }
66444
66979
  }
66445
- var import_node_crypto39, import_sdk10, MAX_SERP_IDENTITIES_PER_USER;
66980
+ var import_node_crypto40, import_sdk10, MAX_SERP_IDENTITIES_PER_USER;
66446
66981
  var init_serp_identity_service = __esm({
66447
66982
  "src/api/serp-identity-service.ts"() {
66448
66983
  "use strict";
66449
- import_node_crypto39 = require("crypto");
66984
+ import_node_crypto40 = require("crypto");
66450
66985
  import_sdk10 = __toESM(require("@onkernel/sdk"), 1);
66451
66986
  init_browser_service_env();
66452
66987
  init_serp_identity_db();
@@ -67157,7 +67692,7 @@ function buildBrowserAgentRoutes() {
67157
67692
  }
67158
67693
  const existing = await getExtensionRow(user.id, name);
67159
67694
  if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
67160
- const backendName2 = `u${user.id}_${(0, import_node_crypto40.randomUUID)().replace(/-/g, "")}`;
67695
+ const backendName2 = `u${user.id}_${(0, import_node_crypto41.randomUUID)().replace(/-/g, "")}`;
67161
67696
  try {
67162
67697
  const imported = await importExtensionFromStore(storeUrl, backendName2);
67163
67698
  const row = await createExtensionRow({
@@ -67216,11 +67751,11 @@ function buildBrowserAgentRoutes() {
67216
67751
  });
67217
67752
  return app2;
67218
67753
  }
67219
- var import_node_crypto40, import_hono24, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE, SERP_IDENTITY_NAME_RE;
67754
+ var import_node_crypto41, import_hono24, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE, SERP_IDENTITY_NAME_RE;
67220
67755
  var init_browser_agent_routes = __esm({
67221
67756
  "src/api/browser-agent-routes.ts"() {
67222
67757
  "use strict";
67223
- import_node_crypto40 = require("crypto");
67758
+ import_node_crypto41 = require("crypto");
67224
67759
  import_hono24 = require("hono");
67225
67760
  init_api_auth();
67226
67761
  init_errors();
@@ -67778,7 +68313,7 @@ async function getKeys() {
67778
68313
  const privateKey = await (0, import_jose2.importPKCS8)(pem, "RS256", { extractable: true });
67779
68314
  const full = await (0, import_jose2.exportJWK)(privateKey);
67780
68315
  const publicJwk = { kty: full.kty, n: full.n, e: full.e };
67781
- const kid = (0, import_node_crypto41.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
68316
+ const kid = (0, import_node_crypto42.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
67782
68317
  publicJwk.kid = kid;
67783
68318
  publicJwk.alg = "RS256";
67784
68319
  publicJwk.use = "sig";
@@ -67957,23 +68492,23 @@ async function validateAuthRequest(p) {
67957
68492
  }
67958
68493
  function pkceMatches(verifier, challenge) {
67959
68494
  if (!verifier) return false;
67960
- const computed = (0, import_node_crypto41.createHash)("sha256").update(verifier).digest("base64url");
68495
+ const computed = (0, import_node_crypto42.createHash)("sha256").update(verifier).digest("base64url");
67961
68496
  return computed === challenge;
67962
68497
  }
67963
68498
  async function mintAccessToken(identity, scope, plan, audience) {
67964
68499
  const { privateKey, kid } = await getKeys();
67965
- return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto41.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
68500
+ return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto42.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
67966
68501
  }
67967
68502
  function tokenErrorResponse(c, error, description, status) {
67968
68503
  return c.json({ error, error_description: description }, status);
67969
68504
  }
67970
- var import_hono26, import_cookie, import_node_crypto41, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, OAUTH_DATABASE_TIMEOUT_MS, OAuthDatabaseUnavailableError, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
68505
+ var import_hono26, import_cookie, import_node_crypto42, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, OAUTH_DATABASE_TIMEOUT_MS, OAuthDatabaseUnavailableError, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
67971
68506
  var init_oauth_routes = __esm({
67972
68507
  "src/api/oauth-routes.ts"() {
67973
68508
  "use strict";
67974
68509
  import_hono26 = require("hono");
67975
68510
  import_cookie = require("hono/cookie");
67976
- import_node_crypto41 = require("crypto");
68511
+ import_node_crypto42 = require("crypto");
67977
68512
  import_jose2 = require("jose");
67978
68513
  init_session();
67979
68514
  init_db();
@@ -68080,7 +68615,7 @@ var init_oauth_routes = __esm({
68080
68615
  }
68081
68616
  }
68082
68617
  const clientName = typeof body.client_name === "string" ? body.client_name : null;
68083
- const clientId = `client_${(0, import_node_crypto41.randomBytes)(16).toString("hex")}`;
68618
+ const clientId = `client_${(0, import_node_crypto42.randomBytes)(16).toString("hex")}`;
68084
68619
  await withOAuthDatabaseDeadline("register-client", registerClient(clientId, redirectUris, clientName));
68085
68620
  console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
68086
68621
  return c.json({
@@ -68133,7 +68668,7 @@ var init_oauth_routes = __esm({
68133
68668
  if (action3 === "deny") return redirectWithError(p.redirect_uri, p.state, "access_denied");
68134
68669
  if (action3 !== "approve") return c.text("unsupported action", 400);
68135
68670
  const scope = negotiateScope(p.scope, user, p.resource);
68136
- const code = `code_${(0, import_node_crypto41.randomBytes)(32).toString("base64url")}`;
68671
+ const code = `code_${(0, import_node_crypto42.randomBytes)(32).toString("base64url")}`;
68137
68672
  const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
68138
68673
  await withOAuthDatabaseDeadline("put-authorization-code", putCode({
68139
68674
  code,
@@ -68172,7 +68707,7 @@ var init_oauth_routes = __esm({
68172
68707
  const plan = user ? resolvePlan(user) : "free";
68173
68708
  const audience = record.resource ?? RESOURCE();
68174
68709
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
68175
- const refreshToken = `rt_${(0, import_node_crypto41.randomBytes)(40).toString("base64url")}`;
68710
+ const refreshToken = `rt_${(0, import_node_crypto42.randomBytes)(40).toString("base64url")}`;
68176
68711
  await withOAuthDatabaseDeadline("put-refresh-token", putRefresh({
68177
68712
  refresh_token: refreshToken,
68178
68713
  client_id: clientId,
@@ -68202,7 +68737,7 @@ var init_oauth_routes = __esm({
68202
68737
  const plan = user ? resolvePlan(user) : "free";
68203
68738
  const audience = record.resource ?? RESOURCE();
68204
68739
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
68205
- const nextRefresh = `rt_${(0, import_node_crypto41.randomBytes)(40).toString("base64url")}`;
68740
+ const nextRefresh = `rt_${(0, import_node_crypto42.randomBytes)(40).toString("base64url")}`;
68206
68741
  await withOAuthDatabaseDeadline("rotate-refresh-token", rotateRefresh(refreshToken, {
68207
68742
  refresh_token: nextRefresh,
68208
68743
  client_id: record.client_id,
@@ -69622,7 +70157,7 @@ function quoteUntrusted(value) {
69622
70157
  return clean3.split("\n").map((line) => `> ${line}`).join("\n");
69623
70158
  }
69624
70159
  function shortHash(value, length = 24) {
69625
- return (0, import_node_crypto42.createHash)("sha256").update(value).digest("hex").slice(0, length);
70160
+ return (0, import_node_crypto43.createHash)("sha256").update(value).digest("hex").slice(0, length);
69626
70161
  }
69627
70162
  function safePathPart(value) {
69628
70163
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "contact";
@@ -70110,11 +70645,11 @@ async function captureSupportMessage(memoryKey, email, options) {
70110
70645
  direction: options.direction
70111
70646
  };
70112
70647
  }
70113
- var import_node_crypto42, SUPPORT_TAGS, ISSUE_TAGS;
70648
+ var import_node_crypto43, SUPPORT_TAGS, ISSUE_TAGS;
70114
70649
  var init_resend_support_thread = __esm({
70115
70650
  "src/api/resend-support-thread.ts"() {
70116
70651
  "use strict";
70117
- import_node_crypto42 = require("crypto");
70652
+ import_node_crypto43 = require("crypto");
70118
70653
  init_memory();
70119
70654
  SUPPORT_TAGS = [
70120
70655
  {
@@ -70699,7 +71234,7 @@ async function claimWorkshopRegistration(input) {
70699
71234
  ) < ?
70700
71235
  `,
70701
71236
  args: [
70702
- (0, import_node_crypto43.randomUUID)(),
71237
+ (0, import_node_crypto44.randomUUID)(),
70703
71238
  input.eventSlug,
70704
71239
  input.email,
70705
71240
  input.firstName,
@@ -70716,7 +71251,7 @@ async function claimWorkshopRegistration(input) {
70716
71251
  id, event_slug, email, first_name, last_name, status
70717
71252
  ) VALUES (?, ?, ?, ?, ?, 'waitlisted')
70718
71253
  `,
70719
- args: [(0, import_node_crypto43.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
71254
+ args: [(0, import_node_crypto44.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
70720
71255
  });
70721
71256
  const waitlisted = await getWorkshopRegistration(input.eventSlug, input.email);
70722
71257
  if (!waitlisted) throw new Error("workshop registration could not be claimed");
@@ -70731,7 +71266,7 @@ async function registerWorkshopInterest(input) {
70731
71266
  id, event_slug, email, first_name, last_name, status
70732
71267
  ) VALUES (?, ?, ?, ?, ?, 'interested')
70733
71268
  `,
70734
- args: [(0, import_node_crypto43.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
71269
+ args: [(0, import_node_crypto44.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
70735
71270
  });
70736
71271
  const registration = await getWorkshopRegistration(input.eventSlug, input.email);
70737
71272
  if (!registration) throw new Error("workshop interest could not be recorded");
@@ -70761,11 +71296,11 @@ async function updateWorkshopRegistration(id, patch) {
70761
71296
  args: [...entries.map(([, value]) => value), id]
70762
71297
  });
70763
71298
  }
70764
- var import_node_crypto43;
71299
+ var import_node_crypto44;
70765
71300
  var init_workshop_registration_repository = __esm({
70766
71301
  "src/api/workshop-registration-repository.ts"() {
70767
71302
  "use strict";
70768
- import_node_crypto43 = require("crypto");
71303
+ import_node_crypto44 = require("crypto");
70769
71304
  init_db();
70770
71305
  }
70771
71306
  });
@@ -71338,7 +71873,7 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
71338
71873
  articleCount: articles.length,
71339
71874
  wordCount: totalWordCount,
71340
71875
  bytes,
71341
- sha256: (0, import_node_crypto44.createHash)("sha256").update(html).digest("hex"),
71876
+ sha256: (0, import_node_crypto45.createHash)("sha256").update(html).digest("hex"),
71342
71877
  warnings
71343
71878
  };
71344
71879
  }
@@ -71421,14 +71956,14 @@ ${provenance}`;
71421
71956
  ...rendered,
71422
71957
  html,
71423
71958
  bytes: Buffer.byteLength(html),
71424
- sha256: (0, import_node_crypto44.createHash)("sha256").update(html).digest("hex")
71959
+ sha256: (0, import_node_crypto45.createHash)("sha256").update(html).digest("hex")
71425
71960
  };
71426
71961
  }
71427
- var import_node_crypto44, import_node_fs14, import_node_path21, import_marked, runtimeEntryDir, assetCache;
71962
+ var import_node_crypto45, import_node_fs14, import_node_path21, import_marked, runtimeEntryDir, assetCache;
71428
71963
  var init_render = __esm({
71429
71964
  "src/editorial-reading-room/render.ts"() {
71430
71965
  "use strict";
71431
- import_node_crypto44 = require("crypto");
71966
+ import_node_crypto45 = require("crypto");
71432
71967
  import_node_fs14 = require("fs");
71433
71968
  import_node_path21 = require("path");
71434
71969
  import_marked = require("marked");
@@ -71572,8 +72107,9 @@ var init_editorial_reading_room_routes = __esm({
71572
72107
 
71573
72108
  // src/api/commons-image-store.ts
71574
72109
  async function ensureCommonsImageSchema() {
71575
- if (imageSchemaReady) return;
71576
- await getDb().execute(`
72110
+ const db = getDb();
72111
+ if (imageSchemaReady && imageSchemaDb === db) return;
72112
+ await db.execute(`
71577
72113
  CREATE TABLE IF NOT EXISTS commons_images (
71578
72114
  id TEXT PRIMARY KEY,
71579
72115
  user_id INTEGER NOT NULL,
@@ -71589,8 +72125,9 @@ async function ensureCommonsImageSchema() {
71589
72125
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
71590
72126
  )
71591
72127
  `);
71592
- await getDb().execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
72128
+ await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
71593
72129
  imageSchemaReady = true;
72130
+ imageSchemaDb = db;
71594
72131
  }
71595
72132
  function sniffImageFormat(bytes) {
71596
72133
  if (bytes.length < 12) return null;
@@ -71620,7 +72157,7 @@ async function assertPublicHttpsUrl(rawUrl) {
71620
72157
  try {
71621
72158
  url = new URL(rawUrl);
71622
72159
  } catch {
71623
- throw new CommonsImageError("image_url_invalid", `"${rawUrl}" is not a valid URL.`);
72160
+ throw new CommonsImageError("image_url_invalid", "The image source is not a valid URL.");
71624
72161
  }
71625
72162
  if (url.protocol !== "https:") {
71626
72163
  throw new CommonsImageError("image_url_not_https", "Commons image sources must be https URLs.");
@@ -71628,7 +72165,7 @@ async function assertPublicHttpsUrl(rawUrl) {
71628
72165
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
71629
72166
  const addresses = (0, import_node_net2.isIP)(hostname) ? [{ address: hostname }] : await (0, import_promises16.lookup)(hostname, { all: true }).catch(() => []);
71630
72167
  if (!addresses.length) {
71631
- throw new CommonsImageError("image_host_unresolvable", `Could not resolve ${url.hostname}.`);
72168
+ throw new CommonsImageError("image_host_unresolvable", "The image source host could not be resolved.");
71632
72169
  }
71633
72170
  if (addresses.some((entry) => isBlockedAddress(entry.address))) {
71634
72171
  throw new CommonsImageError("image_host_blocked", "Commons image sources cannot resolve to private or loopback addresses.");
@@ -71639,15 +72176,36 @@ async function downloadImage(sourceUrl) {
71639
72176
  let current = sourceUrl;
71640
72177
  for (let redirect = 0; redirect <= COMMONS_IMAGE_MAX_REDIRECTS; redirect += 1) {
71641
72178
  const url = await assertPublicHttpsUrl(current);
71642
- const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
72179
+ let response;
72180
+ try {
72181
+ response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
72182
+ } catch (error) {
72183
+ if (error instanceof CommonsImageError) throw error;
72184
+ throw new CommonsImageError(
72185
+ "image_source_unavailable",
72186
+ "The image source could not be reached by Commons.",
72187
+ 422,
72188
+ true
72189
+ );
72190
+ }
71643
72191
  if (response.status >= 300 && response.status < 400) {
71644
72192
  const location2 = response.headers.get("location");
71645
72193
  if (!location2) throw new CommonsImageError("image_redirect_invalid", "Image source redirected without a destination.");
71646
- current = new URL(location2, url).toString();
72194
+ try {
72195
+ current = new URL(location2, url).toString();
72196
+ } catch {
72197
+ throw new CommonsImageError("image_redirect_invalid", "Image source redirected to an invalid destination.");
72198
+ }
71647
72199
  continue;
71648
72200
  }
71649
72201
  if (!response.ok) {
71650
- throw new CommonsImageError("image_source_unavailable", `Image source returned HTTP ${response.status}.`, 422);
72202
+ const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
72203
+ throw new CommonsImageError(
72204
+ "image_source_unavailable",
72205
+ `The image source returned HTTP ${response.status} and could not be hosted.`,
72206
+ 422,
72207
+ retryable
72208
+ );
71651
72209
  }
71652
72210
  const declaredLength = Number(response.headers.get("content-length") ?? "0");
71653
72211
  if (declaredLength > COMMONS_IMAGE_MAX_BYTES) {
@@ -71674,8 +72232,35 @@ function commonsImageBlobStore() {
71674
72232
  const token6 = process.env.COMMONS_IMAGE_READ_WRITE_TOKEN?.trim() || process.env.COMMONS_IMAGE_BLOB_READ_WRITE_TOKEN?.trim() || process.env.BLOB_READ_WRITE_TOKEN?.trim();
71675
72233
  return token6 ? new VercelBlobStore(token6) : getBlobStore();
71676
72234
  }
71677
- function isCommonsHostedImageUrl(url) {
71678
- return /^https:\/\/[^/]*(\.public\.blob\.vercel-storage\.com|blob\.vercel-storage\.com)\//i.test(url);
72235
+ function normalizeCommonsImageUrl(rawUrl) {
72236
+ try {
72237
+ const url = new URL(rawUrl.trim());
72238
+ if (url.protocol !== "https:") return null;
72239
+ url.hash = "";
72240
+ return url.toString();
72241
+ } catch {
72242
+ return null;
72243
+ }
72244
+ }
72245
+ async function findCommonsHostedImageByUrl(rawUrl) {
72246
+ const url = normalizeCommonsImageUrl(rawUrl);
72247
+ if (!url) return null;
72248
+ await ensureCommonsImageSchema();
72249
+ const result = await getDb().execute({
72250
+ sql: "SELECT id, url, content_type, bytes, digest, source_url FROM commons_images WHERE url = ? LIMIT 1",
72251
+ args: [url]
72252
+ });
72253
+ const row = result.rows[0];
72254
+ if (!row) return null;
72255
+ return {
72256
+ id: String(row.id),
72257
+ url: String(row.url),
72258
+ contentType: String(row.content_type),
72259
+ bytes: Number(row.bytes),
72260
+ digest: String(row.digest),
72261
+ sourceUrl: row.source_url ? String(row.source_url) : null,
72262
+ reused: true
72263
+ };
71679
72264
  }
71680
72265
  async function hostCommonsImage(input) {
71681
72266
  if (hostOverride) return hostOverride(input);
@@ -71692,7 +72277,7 @@ async function hostCommonsImage(input) {
71692
72277
  415
71693
72278
  );
71694
72279
  }
71695
- const digest2 = (0, import_node_crypto45.createHash)("sha256").update(bytes).digest("hex");
72280
+ const digest2 = (0, import_node_crypto46.createHash)("sha256").update(bytes).digest("hex");
71696
72281
  const existing = await getDb().execute({
71697
72282
  sql: "SELECT id, url, content_type, bytes, source_url FROM commons_images WHERE digest = ? LIMIT 1",
71698
72283
  args: [digest2]
@@ -71746,7 +72331,8 @@ async function hostCommonsImage(input) {
71746
72331
  async function hostEntityImages(input) {
71747
72332
  const rewrite = { mediaUrls: /* @__PURE__ */ new Map(), dropped: [] };
71748
72333
  const featuredUrl = input.featuredImage?.url;
71749
- if (featuredUrl && !isCommonsHostedImageUrl(featuredUrl)) {
72334
+ const registeredFeatured = featuredUrl ? await findCommonsHostedImageByUrl(featuredUrl) : null;
72335
+ if (featuredUrl && !registeredFeatured) {
71750
72336
  const hosted = await hostCommonsImage({
71751
72337
  userId: input.userId,
71752
72338
  sourceUrl: featuredUrl,
@@ -71754,17 +72340,19 @@ async function hostEntityImages(input) {
71754
72340
  license: input.featuredImage?.license,
71755
72341
  attribution: input.featuredImage?.sourceUrl
71756
72342
  }).catch((error) => {
71757
- const message = error instanceof Error ? error.message : String(error);
72343
+ if (error instanceof CommonsImageError) throw error;
71758
72344
  throw new CommonsImageError(
71759
- error instanceof CommonsImageError ? error.code : "featured_image_unhostable",
71760
- `The featured image could not be hosted, so this entity would publish with a link that can break: ${message}`,
72345
+ "featured_image_unhostable",
72346
+ "The featured image could not be hosted, so the entity was not persisted with a link that could break.",
71761
72347
  422
71762
72348
  );
71763
72349
  });
71764
72350
  rewrite.featuredImageUrl = hosted.url;
72351
+ rewrite.mediaUrls.set(featuredUrl, hosted.url);
71765
72352
  }
71766
72353
  for (const item of input.media ?? []) {
71767
- if (!item.url || isCommonsHostedImageUrl(item.url)) continue;
72354
+ if (item.url && rewrite.mediaUrls.has(item.url)) continue;
72355
+ if (!item.url || await findCommonsHostedImageByUrl(item.url)) continue;
71768
72356
  try {
71769
72357
  const hosted = await hostCommonsImage({
71770
72358
  userId: input.userId,
@@ -71780,11 +72368,11 @@ async function hostEntityImages(input) {
71780
72368
  }
71781
72369
  return rewrite;
71782
72370
  }
71783
- var import_node_crypto45, import_promises16, import_node_net2, COMMONS_IMAGE_MAX_BYTES, COMMONS_IMAGE_MAX_REDIRECTS, COMMONS_IMAGE_FETCH_TIMEOUT_MS, CommonsImageError, imageSchemaReady, hostOverride;
72371
+ var import_node_crypto46, import_promises16, import_node_net2, COMMONS_IMAGE_MAX_BYTES, COMMONS_IMAGE_MAX_REDIRECTS, COMMONS_IMAGE_FETCH_TIMEOUT_MS, COMMONS_IMAGE_RETRY_GUIDANCE, CommonsImageError, imageSchemaReady, imageSchemaDb, hostOverride;
71784
72372
  var init_commons_image_store = __esm({
71785
72373
  "src/api/commons-image-store.ts"() {
71786
72374
  "use strict";
71787
- import_node_crypto45 = require("crypto");
72375
+ import_node_crypto46 = require("crypto");
71788
72376
  import_promises16 = require("dns/promises");
71789
72377
  import_node_net2 = require("net");
71790
72378
  init_blob_store();
@@ -71792,17 +72380,23 @@ var init_commons_image_store = __esm({
71792
72380
  COMMONS_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
71793
72381
  COMMONS_IMAGE_MAX_REDIRECTS = 3;
71794
72382
  COMMONS_IMAGE_FETCH_TIMEOUT_MS = 2e4;
72383
+ COMMONS_IMAGE_RETRY_GUIDANCE = "Call commons_host_image with a different stable public HTTPS image URL or imageBase64, replace featuredImage.url with the returned URL, validate again, then retry commons_submit_entity with the same idempotency key.";
71795
72384
  CommonsImageError = class extends Error {
71796
- constructor(code, message, httpStatus = 400) {
72385
+ constructor(code, message, httpStatus = 400, retryable = false, retryGuidance = COMMONS_IMAGE_RETRY_GUIDANCE) {
71797
72386
  super(message);
71798
72387
  this.code = code;
71799
72388
  this.httpStatus = httpStatus;
72389
+ this.retryable = retryable;
72390
+ this.retryGuidance = retryGuidance;
71800
72391
  this.name = "CommonsImageError";
71801
72392
  }
71802
72393
  code;
71803
72394
  httpStatus;
72395
+ retryable;
72396
+ retryGuidance;
71804
72397
  };
71805
72398
  imageSchemaReady = false;
72399
+ imageSchemaDb = null;
71806
72400
  hostOverride = null;
71807
72401
  }
71808
72402
  });
@@ -71909,7 +72503,7 @@ function buildCommonsLinkset(entity, claims) {
71909
72503
  context[claim.predicate] = targets;
71910
72504
  }
71911
72505
  const document2 = { linkset: [context] };
71912
- const etag = `"${(0, import_node_crypto46.createHash)("sha256").update(JSON.stringify(document2)).digest("hex")}"`;
72506
+ const etag = `"${(0, import_node_crypto47.createHash)("sha256").update(JSON.stringify(document2)).digest("hex")}"`;
71913
72507
  return { document: document2, etag, publicUrl, linksetUrl, profile: COMMONS_RELATIONSHIP_PROFILE };
71914
72508
  }
71915
72509
  function commonsLinksetDiscoveryHeader(idOrSlug) {
@@ -71977,11 +72571,11 @@ function normalizeHreflang(value) {
71977
72571
  const languages = [...new Set(value.map((item) => optionalText(item, 80)).filter((item) => Boolean(item)))];
71978
72572
  return languages.length ? languages : void 0;
71979
72573
  }
71980
- var import_node_crypto46, COMMONS_LINKSET_MEDIA_TYPE, COMMONS_RELATIONSHIP_PROFILE, REGISTERED_RELATIONS;
72574
+ var import_node_crypto47, COMMONS_LINKSET_MEDIA_TYPE, COMMONS_RELATIONSHIP_PROFILE, REGISTERED_RELATIONS;
71981
72575
  var init_commons_linksets = __esm({
71982
72576
  "src/api/commons-linksets.ts"() {
71983
72577
  "use strict";
71984
- import_node_crypto46 = require("crypto");
72578
+ import_node_crypto47 = require("crypto");
71985
72579
  COMMONS_LINKSET_MEDIA_TYPE = "application/linkset+json";
71986
72580
  COMMONS_RELATIONSHIP_PROFILE = "https://mcpscraper.dev/commons/profiles/relationships/v1";
71987
72581
  REGISTERED_RELATIONS = /* @__PURE__ */ new Set([
@@ -72024,9 +72618,9 @@ var init_commons_linksets = __esm({
72024
72618
 
72025
72619
  // src/api/commons-repository.ts
72026
72620
  async function ensureCommonsSchema() {
72027
- if (schemaReady) return;
72621
+ if (schemaReady2) return;
72028
72622
  if (await schemaObjectsPresent(COMMONS_SCHEMA_OBJECTS)) {
72029
- schemaReady = true;
72623
+ schemaReady2 = true;
72030
72624
  return;
72031
72625
  }
72032
72626
  const db = getDb();
@@ -72249,7 +72843,7 @@ async function ensureCommonsSchema() {
72249
72843
  args: [COMMONS_SCHEMA_VERSION]
72250
72844
  }
72251
72845
  ], "write");
72252
- schemaReady = true;
72846
+ schemaReady2 = true;
72253
72847
  }
72254
72848
  async function searchCommonsEntities(filters = {}, userId) {
72255
72849
  await ensureCommonsSchema();
@@ -72457,7 +73051,7 @@ async function prepareCommonsEntity(input, user) {
72457
73051
  normalizedEntityType: entityType,
72458
73052
  proposedSlug: slug4 || null,
72459
73053
  publicUrl: slug4 ? publicUrlForSlug(slug4) : null,
72460
- writePath: "commons_prepare_entity -> commons_validate_entity -> commons_submit_entity"
73054
+ writePath: "commons_prepare_entity -> compose the entity -> commons_host_image -> commons_validate_entity -> commons_submit_entity"
72461
73055
  },
72462
73056
  contract,
72463
73057
  billing: {
@@ -72479,8 +73073,8 @@ async function prepareCommonsEntity(input, user) {
72479
73073
  "Search likely duplicates before submitting a new entity. If one matches, edit it with entityId and baseRevision instead of creating another page.",
72480
73074
  "Use the returned profile as a contract, not a rigid empty template: do not include History, Reception, Pricing, Timeline, or similar sections unless source evidence supports them.",
72481
73075
  "Write neutral encyclopedia prose. Do not paste raw scrape Markdown, marketing layout fragments, or empty/filler headings.",
72482
- "Attach a featured image for auto-publish eligibility, preserve original URL/source byline/relCanonical when republishing, and put media assets in the media array.",
72483
- "Call commons_validate_entity with the composed payload before commons_submit_entity.",
73076
+ "For a featured image not already hosted by Commons, call commons_host_image and replace featuredImage.url with its returned permanent URL. Preserve original URL/source byline/relCanonical when republishing, and put media assets in the media array.",
73077
+ "Call commons_validate_entity with the composed, image-ready payload before commons_submit_entity.",
72484
73078
  "Validation is free. Submitting a new entity costs 10 Credits; submitting an edit to an existing entity costs 2 Credits."
72485
73079
  ]
72486
73080
  };
@@ -72495,6 +73089,8 @@ async function validateCommonsEntity(input, user) {
72495
73089
  const contract = commonsEntityProfileFor(input.entityType, input.seo?.schemaOrgType);
72496
73090
  const source = normalizeSource(input.source);
72497
73091
  const featuredImage = normalizeFeaturedImage(input.featuredImage);
73092
+ const normalizedFeaturedImageUrl = featuredImage?.url ? normalizeCommonsImageUrl(featuredImage.url) : null;
73093
+ const registeredFeaturedImage = normalizedFeaturedImageUrl ? await findCommonsHostedImageByUrl(normalizedFeaturedImageUrl) : null;
72498
73094
  const bodyMarkdown = safeCandidateBody(input.bodyMarkdown, input.contentSections);
72499
73095
  const headings = extractMarkdownHeadings(bodyMarkdown);
72500
73096
  const placeholderSections = unsupportedPlaceholderSections(bodyMarkdown);
@@ -72532,15 +73128,44 @@ async function validateCommonsEntity(input, user) {
72532
73128
  if (input.reviewPolicy === "always_review") {
72533
73129
  warnings.push("reviewPolicy is always_review, so the proposal will not auto-publish even if the payload is otherwise valid.");
72534
73130
  }
73131
+ if (featuredImage?.url && !registeredFeaturedImage) {
73132
+ warnings.push("The featured image is not registered in permanent Commons image storage. Call commons_host_image, replace featuredImage.url with its returned URL, and validate again before submitting.");
73133
+ }
72535
73134
  if (user?.id == null) warnings.push("No authenticated user was supplied to validation; submit still requires API-key authenticated user context.");
72536
73135
  const valid = errors.length === 0;
72537
- const autoPublishEligible = valid && input.reviewPolicy !== "always_review";
73136
+ const imageReadyForPublish = Boolean(registeredFeaturedImage);
73137
+ const publishable = valid && imageReadyForPublish;
73138
+ const autoPublishEligible = publishable && input.reviewPolicy !== "always_review";
72538
73139
  const tagVocabulary = await commonsTagVocabulary();
72539
73140
  return {
72540
73141
  ok: true,
72541
73142
  valid,
72542
- publishable: valid,
73143
+ publishable,
72543
73144
  autoPublishEligible,
73145
+ imageDiagnostics: {
73146
+ featuredImage: registeredFeaturedImage ? {
73147
+ state: "commons_hosted",
73148
+ url: registeredFeaturedImage.url,
73149
+ commonsImageId: registeredFeaturedImage.id,
73150
+ readyForPublish: true,
73151
+ submitWillAttemptAutoHost: false,
73152
+ requiredAction: null
73153
+ } : featuredImage?.url ? {
73154
+ state: "external_unhosted",
73155
+ url: normalizedFeaturedImageUrl,
73156
+ commonsImageId: null,
73157
+ readyForPublish: false,
73158
+ submitWillAttemptAutoHost: true,
73159
+ requiredAction: "Call commons_host_image, replace featuredImage.url with its returned permanent URL, and validate again."
73160
+ } : {
73161
+ state: "missing",
73162
+ url: null,
73163
+ commonsImageId: null,
73164
+ readyForPublish: false,
73165
+ submitWillAttemptAutoHost: false,
73166
+ requiredAction: "Add a featured image by calling commons_host_image, then set featuredImage.url to its returned permanent URL."
73167
+ }
73168
+ },
72544
73169
  errors,
72545
73170
  warnings,
72546
73171
  contract,
@@ -72621,7 +73246,7 @@ async function submitCommonsEntity(input, user) {
72621
73246
  const existingClaims = existing && normalized.claims !== void 0 ? await getCommonsClaimsByEntityId(existing.id) : [];
72622
73247
  const safety = evaluatePublishSafety(normalized, existing);
72623
73248
  const shouldApply = safety.safe && normalized.reviewPolicy !== "always_review";
72624
- const proposalId = `commons-proposal-${(0, import_node_crypto47.randomUUID)()}`;
73249
+ const proposalId = `commons-proposal-${(0, import_node_crypto48.randomUUID)()}`;
72625
73250
  const now = (/* @__PURE__ */ new Date()).toISOString();
72626
73251
  const entityId = existing?.id ?? normalized.entityId ?? allocateEntityId();
72627
73252
  const proposalStatus = shouldApply ? "accepted" : "pending_review";
@@ -72676,7 +73301,7 @@ async function submitCommonsEntity(input, user) {
72676
73301
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
72677
73302
  `,
72678
73303
  args: [
72679
- `commons-ledger-${(0, import_node_crypto47.randomUUID)()}`,
73304
+ `commons-ledger-${(0, import_node_crypto48.randomUUID)()}`,
72680
73305
  nextEntity.id,
72681
73306
  proposalId,
72682
73307
  user.id,
@@ -72782,7 +73407,7 @@ async function saveCommonsFilter(input, user) {
72782
73407
  sql: "SELECT id FROM commons_saved_filters WHERE user_id = ? AND name = ? LIMIT 1",
72783
73408
  args: [user.id, name]
72784
73409
  });
72785
- const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${(0, import_node_crypto47.randomUUID)()}`;
73410
+ const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${(0, import_node_crypto48.randomUUID)()}`;
72786
73411
  await getDb().execute({
72787
73412
  sql: `
72788
73413
  INSERT INTO commons_saved_filters (id, user_id, name, description, filter_json, created_at, updated_at)
@@ -73560,7 +74185,7 @@ function commonsIndexDocuments(entity, now) {
73560
74185
  return documents;
73561
74186
  }
73562
74187
  function commonsIndexDocumentId(entityId, documentType, documentKey) {
73563
- return `commons-index-${(0, import_node_crypto47.createHash)("sha256").update(`${entityId}
74188
+ return `commons-index-${(0, import_node_crypto48.createHash)("sha256").update(`${entityId}
73564
74189
  ${documentType}
73565
74190
  ${documentKey}`).digest("hex").slice(0, 32)}`;
73566
74191
  }
@@ -74126,11 +74751,11 @@ function jsonLikeValue(value) {
74126
74751
  function escapeLike(value) {
74127
74752
  return value.replace(/[%_]/g, "");
74128
74753
  }
74129
- var import_node_crypto47, COMMONS_SCHEMA_VERSION, DEFAULT_COMMONS_BASE_URL, DEFAULT_ENTITY_TYPE, COMMONS_ENTITY_PROFILES, schemaReady, COMMONS_SCHEMA_OBJECTS, SEARCH_CANDIDATE_LIMIT, CommonsRepositoryError;
74754
+ var import_node_crypto48, COMMONS_SCHEMA_VERSION, DEFAULT_COMMONS_BASE_URL, DEFAULT_ENTITY_TYPE, COMMONS_ENTITY_PROFILES, schemaReady2, COMMONS_SCHEMA_OBJECTS, SEARCH_CANDIDATE_LIMIT, CommonsRepositoryError;
74130
74755
  var init_commons_repository = __esm({
74131
74756
  "src/api/commons-repository.ts"() {
74132
74757
  "use strict";
74133
- import_node_crypto47 = require("crypto");
74758
+ import_node_crypto48 = require("crypto");
74134
74759
  init_db();
74135
74760
  init_commons_image_store();
74136
74761
  init_commons_embeddings();
@@ -74285,7 +74910,7 @@ var init_commons_repository = __esm({
74285
74910
  ]
74286
74911
  }
74287
74912
  };
74288
- schemaReady = false;
74913
+ schemaReady2 = false;
74289
74914
  COMMONS_SCHEMA_OBJECTS = [
74290
74915
  "schema_migrations",
74291
74916
  "commons_entities",
@@ -74509,7 +75134,7 @@ async function claimCommonsPublication(input, user) {
74509
75134
  }
74510
75135
  const existingName = await getCommonsPublicationBySubdomain(subdomain);
74511
75136
  if (existingName) throw new CommonsPublicationError("publication_name_unavailable", "That publication name is already claimed.", 409);
74512
- const id = `tcpub_${(0, import_node_crypto48.randomUUID)()}`;
75137
+ const id = `tcpub_${(0, import_node_crypto49.randomUUID)()}`;
74513
75138
  const now = (/* @__PURE__ */ new Date()).toISOString();
74514
75139
  try {
74515
75140
  await getDb().execute({
@@ -74561,7 +75186,7 @@ async function publishCommonsEditorial(input, user) {
74561
75186
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
74562
75187
  const rendered = renderEditorialReadingRoom(editionInput);
74563
75188
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
74564
- const editionId = `tced_${(0, import_node_crypto48.randomUUID)()}`;
75189
+ const editionId = `tced_${(0, import_node_crypto49.randomUUID)()}`;
74565
75190
  const revision = (latest?.revision ?? 0) + 1;
74566
75191
  const now = (/* @__PURE__ */ new Date()).toISOString();
74567
75192
  await getDb().batch([
@@ -74585,7 +75210,7 @@ async function publishCommonsEditorial(input, user) {
74585
75210
  JSON.stringify(editionInput.articles),
74586
75211
  html,
74587
75212
  rendered.filename,
74588
- (0, import_node_crypto48.createHash)("sha256").update(html).digest("hex"),
75213
+ (0, import_node_crypto49.createHash)("sha256").update(html).digest("hex"),
74589
75214
  rendered.articleCount,
74590
75215
  rendered.wordCount,
74591
75216
  Buffer.byteLength(html),
@@ -74643,7 +75268,7 @@ async function updateCommonsEditorialArticle(input, user) {
74643
75268
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
74644
75269
  const rendered = renderEditorialReadingRoom(editionInput);
74645
75270
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
74646
- const editionId = `tced_${(0, import_node_crypto48.randomUUID)()}`;
75271
+ const editionId = `tced_${(0, import_node_crypto49.randomUUID)()}`;
74647
75272
  const revision = latest.revision + 1;
74648
75273
  const now = (/* @__PURE__ */ new Date()).toISOString();
74649
75274
  await getDb().batch([
@@ -74667,7 +75292,7 @@ async function updateCommonsEditorialArticle(input, user) {
74667
75292
  JSON.stringify(nextArticles),
74668
75293
  html,
74669
75294
  rendered.filename,
74670
- (0, import_node_crypto48.createHash)("sha256").update(html).digest("hex"),
75295
+ (0, import_node_crypto49.createHash)("sha256").update(html).digest("hex"),
74671
75296
  rendered.articleCount,
74672
75297
  rendered.wordCount,
74673
75298
  Buffer.byteLength(html),
@@ -74872,11 +75497,11 @@ function addPublicMetadata(html, canonicalUrl, publicationTitle) {
74872
75497
  "</head>"
74873
75498
  ].join("\n"));
74874
75499
  }
74875
- var import_node_crypto48, PUBLICATION_ROOT_DOMAIN, RESERVED_SUBDOMAINS, CommonsPublicationError;
75500
+ var import_node_crypto49, PUBLICATION_ROOT_DOMAIN, RESERVED_SUBDOMAINS, CommonsPublicationError;
74876
75501
  var init_commons_publication_repository = __esm({
74877
75502
  "src/api/commons-publication-repository.ts"() {
74878
75503
  "use strict";
74879
- import_node_crypto48 = require("crypto");
75504
+ import_node_crypto49 = require("crypto");
74880
75505
  init_db();
74881
75506
  init_commons_repository();
74882
75507
  init_render();
@@ -74963,6 +75588,18 @@ function repositoryStatus(error) {
74963
75588
  if (error.httpStatus === 409) return 409;
74964
75589
  return 400;
74965
75590
  }
75591
+ function imageErrorResponse(c, error, chargeStatus) {
75592
+ return c.json({
75593
+ ok: false,
75594
+ error: error.code,
75595
+ error_code: error.code,
75596
+ error_type: "invalid_request",
75597
+ retryable: error.retryable,
75598
+ ...chargeStatus ? { charge_status: chargeStatus } : {},
75599
+ message: `${error.message} ${error.retryGuidance}`,
75600
+ details: { retry_guidance: error.retryGuidance }
75601
+ }, error.httpStatus);
75602
+ }
74966
75603
  async function publicationOperation(c, operation, successStatus = 200) {
74967
75604
  try {
74968
75605
  return c.json({ ok: true, data: await operation() }, successStatus);
@@ -75303,7 +75940,7 @@ var init_commons_routes = __esm({
75303
75940
  return c.json({ ok: true, data: hosted }, hosted.reused ? 200 : 201);
75304
75941
  } catch (error) {
75305
75942
  if (error instanceof CommonsImageError) {
75306
- return c.json({ ok: false, error: error.code, message: error.message }, error.httpStatus);
75943
+ return imageErrorResponse(c, error);
75307
75944
  }
75308
75945
  throw error;
75309
75946
  }
@@ -75367,7 +76004,14 @@ var init_commons_routes = __esm({
75367
76004
  }
75368
76005
  return c.json({ ok: true, data: result, billing: publicListingWriteBilling(billing) }, 201);
75369
76006
  } catch (error) {
75370
- if (billing) await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
76007
+ let chargeStatus = billing ? billing.duplicate ? "not_charged" : "refund_pending" : "not_charged";
76008
+ if (billing && !billing.duplicate) {
76009
+ const settlement = await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
76010
+ if (settlement) chargeStatus = "refunded";
76011
+ }
76012
+ if (error instanceof CommonsImageError) {
76013
+ return imageErrorResponse(c, error, chargeStatus);
76014
+ }
75371
76015
  if (error instanceof CommonsRepositoryError) {
75372
76016
  return c.json({ ok: false, error: error.code, message: error.message }, repositoryStatus(error));
75373
76017
  }
@@ -76109,7 +76753,7 @@ async function migrateAnalytics() {
76109
76753
  }
76110
76754
  function normalizeSlug2(value) {
76111
76755
  const slug4 = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
76112
- return slug4 || `site-${(0, import_node_crypto49.randomBytes)(4).toString("hex")}`;
76756
+ return slug4 || `site-${(0, import_node_crypto50.randomBytes)(4).toString("hex")}`;
76113
76757
  }
76114
76758
  function normalizeObservedHostname(origin) {
76115
76759
  if (!origin || origin === "null") return null;
@@ -76122,7 +76766,7 @@ function normalizeObservedHostname(origin) {
76122
76766
  }
76123
76767
  }
76124
76768
  function publicPixelId() {
76125
- return `px_${(0, import_node_crypto49.randomBytes)(18).toString("base64url")}`;
76769
+ return `px_${(0, import_node_crypto50.randomBytes)(18).toString("base64url")}`;
76126
76770
  }
76127
76771
  function mapRows(rows) {
76128
76772
  return rows;
@@ -76154,7 +76798,7 @@ async function requireEditor(client2, siteId, userId) {
76154
76798
  async function createAnalyticsSite(input) {
76155
76799
  const db = getAnalyticsPool();
76156
76800
  const client2 = await db.connect();
76157
- const id = (0, import_node_crypto49.randomUUID)();
76801
+ const id = (0, import_node_crypto50.randomUUID)();
76158
76802
  const baseSlug = normalizeSlug2(input.slug || input.name);
76159
76803
  try {
76160
76804
  await client2.query("BEGIN");
@@ -76170,7 +76814,7 @@ async function createAnalyticsSite(input) {
76170
76814
  } catch (error) {
76171
76815
  const code = error.code;
76172
76816
  if (code !== "23505" || attempt === 2) throw error;
76173
- slug4 = `${baseSlug}-${(0, import_node_crypto49.randomBytes)(2).toString("hex")}`;
76817
+ slug4 = `${baseSlug}-${(0, import_node_crypto50.randomBytes)(2).toString("hex")}`;
76174
76818
  }
76175
76819
  }
76176
76820
  await client2.query(
@@ -76385,7 +77029,7 @@ async function createAnalyticsPixel(input) {
76385
77029
  VALUES ($1, $2, $3, $4, $5)
76386
77030
  RETURNING id, site_id, public_id, name, environment, status, created_at::text, NULL::text AS last_event_at`,
76387
77031
  [
76388
- (0, import_node_crypto49.randomUUID)(),
77032
+ (0, import_node_crypto50.randomUUID)(),
76389
77033
  input.siteId,
76390
77034
  publicPixelId(),
76391
77035
  input.name.trim(),
@@ -76462,7 +77106,7 @@ async function setAnalyticsPixelDomainState(input) {
76462
77106
  SELECT $1, p.id, $4, $5 FROM analytics_pixels p WHERE p.id = $2 AND p.site_id = $3
76463
77107
  ON CONFLICT(pixel_id, hostname) DO UPDATE SET state = EXCLUDED.state, updated_at = now()
76464
77108
  RETURNING id`,
76465
- [(0, import_node_crypto49.randomUUID)(), input.pixelId, input.siteId, hostname, input.state]
77109
+ [(0, import_node_crypto50.randomUUID)(), input.pixelId, input.siteId, hostname, input.state]
76466
77110
  );
76467
77111
  if (!result.rowCount)
76468
77112
  throw new AnalyticsRepositoryError(
@@ -76512,7 +77156,7 @@ function sanitizeAnalyticsProperties(value) {
76512
77156
  }
76513
77157
  async function ingestAnalyticsEvents(input) {
76514
77158
  const db = getAnalyticsPool();
76515
- const requestId = `air_${(0, import_node_crypto49.randomUUID)()}`;
77159
+ const requestId = `air_${(0, import_node_crypto50.randomUUID)()}`;
76516
77160
  const hostname = normalizeObservedHostname(input.origin);
76517
77161
  const pixelResult = await db.query(
76518
77162
  `SELECT p.id, p.site_id, p.status,
@@ -76533,7 +77177,7 @@ async function ingestAnalyticsEvents(input) {
76533
77177
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76534
77178
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76535
77179
  [
76536
- (0, import_node_crypto49.randomUUID)(),
77180
+ (0, import_node_crypto50.randomUUID)(),
76537
77181
  requestId,
76538
77182
  pixel?.site_id ?? null,
76539
77183
  pixel?.id ?? null,
@@ -76558,7 +77202,7 @@ async function ingestAnalyticsEvents(input) {
76558
77202
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76559
77203
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76560
77204
  [
76561
- (0, import_node_crypto49.randomUUID)(),
77205
+ (0, import_node_crypto50.randomUUID)(),
76562
77206
  requestId,
76563
77207
  pixel.site_id,
76564
77208
  pixel.id,
@@ -76583,7 +77227,7 @@ async function ingestAnalyticsEvents(input) {
76583
77227
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, rejected_count, reason_codes)
76584
77228
  VALUES ($1, $2, $3, $4, $5, $6::jsonb)`,
76585
77229
  [
76586
- (0, import_node_crypto49.randomUUID)(),
77230
+ (0, import_node_crypto50.randomUUID)(),
76587
77231
  requestId,
76588
77232
  pixel.site_id,
76589
77233
  pixel.id,
@@ -76607,7 +77251,7 @@ async function ingestAnalyticsEvents(input) {
76607
77251
  ON CONFLICT(pixel_id, hostname) DO UPDATE
76608
77252
  SET last_seen_at = now(), updated_at = now()
76609
77253
  RETURNING state`,
76610
- [(0, import_node_crypto49.randomUUID)(), pixel.id, hostname]
77254
+ [(0, import_node_crypto50.randomUUID)(), pixel.id, hostname]
76611
77255
  );
76612
77256
  if (domainResult.rows[0]?.state !== "approved") {
76613
77257
  await db.query(
@@ -76621,7 +77265,7 @@ async function ingestAnalyticsEvents(input) {
76621
77265
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76622
77266
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76623
77267
  [
76624
- (0, import_node_crypto49.randomUUID)(),
77268
+ (0, import_node_crypto50.randomUUID)(),
76625
77269
  requestId,
76626
77270
  pixel.site_id,
76627
77271
  pixel.id,
@@ -76671,7 +77315,7 @@ async function ingestAnalyticsEvents(input) {
76671
77315
  $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33
76672
77316
  ) ON CONFLICT(site_id, event_id) DO NOTHING`,
76673
77317
  [
76674
- (0, import_node_crypto49.randomUUID)(),
77318
+ (0, import_node_crypto50.randomUUID)(),
76675
77319
  event2.eventId,
76676
77320
  pixel.site_id,
76677
77321
  pixel.id,
@@ -76729,7 +77373,7 @@ async function ingestAnalyticsEvents(input) {
76729
77373
  id, request_id, site_id, pixel_id, hostname, accepted_count, rejected_count, reason_codes
76730
77374
  ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`,
76731
77375
  [
76732
- (0, import_node_crypto49.randomUUID)(),
77376
+ (0, import_node_crypto50.randomUUID)(),
76733
77377
  requestId,
76734
77378
  pixel.site_id,
76735
77379
  pixel.id,
@@ -76761,7 +77405,7 @@ function normalizeGeographyCode(value, max) {
76761
77405
  return normalized && /^[A-Z0-9-]+$/.test(normalized) ? normalized.slice(0, max) : null;
76762
77406
  }
76763
77407
  function pageFingerprint(scope, siteId, filters) {
76764
- return (0, import_node_crypto49.createHash)("sha256").update(JSON.stringify({ scope, siteId, filters })).digest("base64url").slice(0, 18);
77408
+ return (0, import_node_crypto50.createHash)("sha256").update(JSON.stringify({ scope, siteId, filters })).digest("base64url").slice(0, 18);
76765
77409
  }
76766
77410
  function decodePageOffset(cursor, fingerprint2) {
76767
77411
  if (!cursor) return 0;
@@ -76811,7 +77455,7 @@ async function createAnalyticsConversion(input) {
76811
77455
  404
76812
77456
  );
76813
77457
  }
76814
- const id = (0, import_node_crypto49.randomUUID)();
77458
+ const id = (0, import_node_crypto50.randomUUID)();
76815
77459
  const resolvedPerson = input.sessionId ? await db.query(
76816
77460
  `SELECT e.person_id FROM analytics_identity_nodes n JOIN analytics_identity_edges e ON e.identity_node_id = n.id
76817
77461
  WHERE n.site_id = $1 AND n.kind = 'session_id' AND n.value_hmac = $2 ORDER BY e.confidence DESC LIMIT 1`,
@@ -77560,7 +78204,7 @@ async function createAnalyticsCampaignLink(input) {
77560
78204
  404
77561
78205
  );
77562
78206
  }
77563
- const shortCode = (input.shortCode?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-") || (0, import_node_crypto49.randomBytes)(5).toString("base64url").toLowerCase()).slice(0, 48);
78207
+ const shortCode = (input.shortCode?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-") || (0, import_node_crypto50.randomBytes)(5).toString("base64url").toLowerCase()).slice(0, 48);
77564
78208
  try {
77565
78209
  const result = await db.query(
77566
78210
  `INSERT INTO analytics_campaign_links(
@@ -77569,7 +78213,7 @@ async function createAnalyticsCampaignLink(input) {
77569
78213
  ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
77570
78214
  RETURNING *, 0::int AS click_count`,
77571
78215
  [
77572
- (0, import_node_crypto49.randomUUID)(),
78216
+ (0, import_node_crypto50.randomUUID)(),
77573
78217
  input.siteId,
77574
78218
  input.pixelId ?? null,
77575
78219
  input.name.trim(),
@@ -77640,7 +78284,7 @@ async function resolveAnalyticsCampaignLink(shortCode, referrer) {
77640
78284
  if (!row) return null;
77641
78285
  await db.query(
77642
78286
  `INSERT INTO analytics_campaign_clicks(id, link_id, referrer) VALUES ($1, $2, $3)`,
77643
- [(0, import_node_crypto49.randomUUID)(), row.id, normalizeAnalyticsUrl(referrer || void 0)]
78287
+ [(0, import_node_crypto50.randomUUID)(), row.id, normalizeAnalyticsUrl(referrer || void 0)]
77644
78288
  );
77645
78289
  return buildTaggedCampaignUrl(row);
77646
78290
  }
@@ -77658,14 +78302,14 @@ async function createAnalyticsForm(input) {
77658
78302
  404
77659
78303
  );
77660
78304
  const baseSlug = normalizeSlug2(input.name);
77661
- const slug4 = `${baseSlug}-${(0, import_node_crypto49.randomBytes)(3).toString("hex")}`;
77662
- const publicId = `form_${(0, import_node_crypto49.randomBytes)(18).toString("base64url")}`;
78305
+ const slug4 = `${baseSlug}-${(0, import_node_crypto50.randomBytes)(3).toString("hex")}`;
78306
+ const publicId = `form_${(0, import_node_crypto50.randomBytes)(18).toString("base64url")}`;
77663
78307
  const result = await db.query(
77664
78308
  `INSERT INTO analytics_forms(id, public_id, site_id, pixel_id, name, slug, fields, brand, submit_label, success_message, redirect_url, consent_text, status, created_by_user_id)
77665
78309
  VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb,$9,$10,$11,$12,$13,$14)
77666
78310
  RETURNING *, 0::int AS submission_count`,
77667
78311
  [
77668
- (0, import_node_crypto49.randomUUID)(),
78312
+ (0, import_node_crypto50.randomUUID)(),
77669
78313
  publicId,
77670
78314
  input.siteId,
77671
78315
  input.pixelId,
@@ -77710,7 +78354,7 @@ async function getPublicAnalyticsForm(publicId) {
77710
78354
  return result.rows[0] ?? null;
77711
78355
  }
77712
78356
  async function recordAnalyticsFormSubmission(input) {
77713
- const id = (0, import_node_crypto49.randomUUID)();
78357
+ const id = (0, import_node_crypto50.randomUUID)();
77714
78358
  await getAnalyticsPool().query(
77715
78359
  `INSERT INTO analytics_form_submissions(id, form_id, site_id, pixel_id, visitor_id, session_id, crm_person_ref, source, medium, campaign, crm_delivery_status, click_ids)
77716
78360
  VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12::jsonb)`,
@@ -77741,7 +78385,7 @@ function sanitizeClickIds(value) {
77741
78385
  return output;
77742
78386
  }
77743
78387
  function identityHmac(kind, value) {
77744
- return (0, import_node_crypto49.createHmac)("sha256", getSessionSecret()).update(`${kind}:${value.trim().toLowerCase()}`).digest("hex");
78388
+ return (0, import_node_crypto50.createHmac)("sha256", getSessionSecret()).update(`${kind}:${value.trim().toLowerCase()}`).digest("hex");
77745
78389
  }
77746
78390
  async function linkAnalyticsFormIdentity(input) {
77747
78391
  const client2 = await getAnalyticsPool().connect();
@@ -77750,7 +78394,7 @@ async function linkAnalyticsFormIdentity(input) {
77750
78394
  const person = await client2.query(
77751
78395
  `INSERT INTO analytics_people(id, site_id, crm_person_ref) VALUES ($1,$2,$3)
77752
78396
  ON CONFLICT(site_id, crm_person_ref) DO UPDATE SET last_seen_at = now() RETURNING id`,
77753
- [(0, import_node_crypto49.randomUUID)(), input.siteId, input.crmPersonRef]
78397
+ [(0, import_node_crypto50.randomUUID)(), input.siteId, input.crmPersonRef]
77754
78398
  );
77755
78399
  const personId = person.rows[0].id;
77756
78400
  const signals = [];
@@ -77797,7 +78441,7 @@ async function linkAnalyticsFormIdentity(input) {
77797
78441
  `INSERT INTO analytics_identity_nodes(id, site_id, kind, value_hmac) VALUES ($1,$2,$3,$4)
77798
78442
  ON CONFLICT(site_id, kind, value_hmac) DO UPDATE SET last_seen_at = now() RETURNING id`,
77799
78443
  [
77800
- (0, import_node_crypto49.randomUUID)(),
78444
+ (0, import_node_crypto50.randomUUID)(),
77801
78445
  input.siteId,
77802
78446
  signal.kind,
77803
78447
  identityHmac(signal.kind, signal.value)
@@ -77808,7 +78452,7 @@ async function linkAnalyticsFormIdentity(input) {
77808
78452
  VALUES ($1,$2,$3,$4,$5,$6)
77809
78453
  ON CONFLICT(person_id, identity_node_id, evidence_kind) DO UPDATE SET last_seen_at = now(), confidence = greatest(analytics_identity_edges.confidence, EXCLUDED.confidence)`,
77810
78454
  [
77811
- (0, import_node_crypto49.randomUUID)(),
78455
+ (0, import_node_crypto50.randomUUID)(),
77812
78456
  input.siteId,
77813
78457
  personId,
77814
78458
  node.rows[0].id,
@@ -77906,7 +78550,7 @@ async function createAnalyticsCrmImport(input) {
77906
78550
  const db = getAnalyticsPool();
77907
78551
  await requireEditor(db, input.siteId, input.userId);
77908
78552
  const client2 = await db.connect();
77909
- const id = (0, import_node_crypto49.randomUUID)();
78553
+ const id = (0, import_node_crypto50.randomUUID)();
77910
78554
  try {
77911
78555
  await client2.query("BEGIN");
77912
78556
  await client2.query(
@@ -77926,7 +78570,7 @@ async function createAnalyticsCrmImport(input) {
77926
78570
  await client2.query(
77927
78571
  `INSERT INTO analytics_crm_import_rows(id, import_id, crm_person_ref, payload_ciphertext)
77928
78572
  VALUES ($1,$2,$3,$4) ON CONFLICT(import_id, crm_person_ref) DO NOTHING`,
77929
- [(0, import_node_crypto49.randomUUID)(), id, row.crmPersonRef, row.payloadCiphertext]
78573
+ [(0, import_node_crypto50.randomUUID)(), id, row.crmPersonRef, row.payloadCiphertext]
77930
78574
  );
77931
78575
  }
77932
78576
  await client2.query("COMMIT");
@@ -77965,7 +78609,7 @@ async function createAnalyticsActivationDestination(input) {
77965
78609
  `INSERT INTO analytics_activation_destinations(id, site_id, platform, name, connection_ref, external_dataset_id, event_mapping, created_by_user_id)
77966
78610
  VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8) RETURNING *`,
77967
78611
  [
77968
- (0, import_node_crypto49.randomUUID)(),
78612
+ (0, import_node_crypto50.randomUUID)(),
77969
78613
  input.siteId,
77970
78614
  input.platform,
77971
78615
  input.name.trim(),
@@ -78017,7 +78661,7 @@ async function queueAnalyticsActivation(input) {
78017
78661
  `INSERT INTO analytics_activation_jobs(id, destination_id, conversion_id, person_id, payload_ciphertext)
78018
78662
  VALUES ($1,$2,$3,$4,$5) ON CONFLICT(destination_id, conversion_id) DO NOTHING`,
78019
78663
  [
78020
- (0, import_node_crypto49.randomUUID)(),
78664
+ (0, import_node_crypto50.randomUUID)(),
78021
78665
  destination.id,
78022
78666
  input.conversionId,
78023
78667
  input.personId ?? null,
@@ -78029,7 +78673,7 @@ async function queueAnalyticsActivation(input) {
78029
78673
  return queued;
78030
78674
  }
78031
78675
  async function queueAnalyticsFormDelivery(input) {
78032
- const id = (0, import_node_crypto49.randomUUID)();
78676
+ const id = (0, import_node_crypto50.randomUUID)();
78033
78677
  await getAnalyticsPool().query(
78034
78678
  `INSERT INTO analytics_form_delivery_jobs(id, submission_id, owner_user_id, payload_ciphertext, last_error_code)
78035
78679
  VALUES ($1,$2,$3,$4,$5)`,
@@ -78156,7 +78800,7 @@ async function analyticsHealth(siteId, userId) {
78156
78800
  }
78157
78801
  async function refreshAnalyticsDailyRollups(input) {
78158
78802
  const db = getAnalyticsPool();
78159
- const runId = (0, import_node_crypto49.randomUUID)();
78803
+ const runId = (0, import_node_crypto50.randomUUID)();
78160
78804
  await db.query(
78161
78805
  `INSERT INTO analytics_rollup_runs(id, window_start, window_end, status) VALUES ($1, $2, $3, 'running')`,
78162
78806
  [runId, input.start, input.end]
@@ -78353,7 +78997,7 @@ async function createAnalyticsExport(input) {
78353
78997
  `Generated ${(/* @__PURE__ */ new Date()).toISOString()} from the governed ${input.report} report contract.`
78354
78998
  ].join("\n");
78355
78999
  }
78356
- const id = (0, import_node_crypto49.randomUUID)();
79000
+ const id = (0, import_node_crypto50.randomUUID)();
78357
79001
  const inserted = await getAnalyticsPool().query(
78358
79002
  `INSERT INTO analytics_exports(id, site_id, requested_by_user_id, idempotency_key, report, format, filters, content)
78359
79003
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
@@ -78386,11 +79030,11 @@ async function createAnalyticsExport(input) {
78386
79030
  }
78387
79031
  return describeArtifact(replay.rows[0]);
78388
79032
  }
78389
- var import_node_crypto49, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
79033
+ var import_node_crypto50, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
78390
79034
  var init_analytics_repository = __esm({
78391
79035
  "src/api/analytics-repository.ts"() {
78392
79036
  "use strict";
78393
- import_node_crypto49 = require("crypto");
79037
+ import_node_crypto50 = require("crypto");
78394
79038
  import_pg = require("pg");
78395
79039
  init_session();
78396
79040
  init_analytics_attribution();
@@ -78629,8 +79273,8 @@ function dashboardCallbackUrl() {
78629
79273
  }
78630
79274
  async function createThorbitConnectUrl(userId) {
78631
79275
  if (!bridgeConfigured()) throw new Error("Thorbit X-Ray account bridge is not configured");
78632
- const state = (0, import_node_crypto50.randomBytes)(32).toString("base64url");
78633
- const stateHash = (0, import_node_crypto50.createHash)("sha256").update(state).digest("hex");
79276
+ const state = (0, import_node_crypto51.randomBytes)(32).toString("base64url");
79277
+ const stateHash = (0, import_node_crypto51.createHash)("sha256").update(state).digest("hex");
78634
79278
  await getAnalyticsPool().query(
78635
79279
  `INSERT INTO analytics_thorbit_connect_states(state_hash,user_id,expires_at)
78636
79280
  VALUES ($1,$2,now()+interval '10 minutes')
@@ -78645,9 +79289,9 @@ async function createThorbitConnectUrl(userId) {
78645
79289
  function verifyThorbitAssertion(token6) {
78646
79290
  const [encoded, signature] = token6.split(".");
78647
79291
  if (!encoded || !signature || !bridgeConfigured()) throw new Error("Invalid Thorbit assertion");
78648
- const expected = (0, import_node_crypto50.createHmac)("sha256", bridgeSecret()).update(encoded).digest();
79292
+ const expected = (0, import_node_crypto51.createHmac)("sha256", bridgeSecret()).update(encoded).digest();
78649
79293
  const actual = Buffer.from(signature, "base64url");
78650
- if (actual.length !== expected.length || !(0, import_node_crypto50.timingSafeEqual)(actual, expected)) {
79294
+ if (actual.length !== expected.length || !(0, import_node_crypto51.timingSafeEqual)(actual, expected)) {
78651
79295
  throw new Error("Invalid Thorbit assertion signature");
78652
79296
  }
78653
79297
  const parsed = AssertionSchema.safeParse(
@@ -78659,7 +79303,7 @@ function verifyThorbitAssertion(token6) {
78659
79303
  return parsed.data.entitlement;
78660
79304
  }
78661
79305
  async function consumeThorbitConnectCallback(input) {
78662
- const stateHash = (0, import_node_crypto50.createHash)("sha256").update(input.state).digest("hex");
79306
+ const stateHash = (0, import_node_crypto51.createHash)("sha256").update(input.state).digest("hex");
78663
79307
  const client2 = await getAnalyticsPool().connect();
78664
79308
  try {
78665
79309
  await client2.query("BEGIN");
@@ -78699,11 +79343,11 @@ async function disconnectAnalyticsEntitlement(userId) {
78699
79343
  [userId]
78700
79344
  );
78701
79345
  }
78702
- var import_node_crypto50, import_zod55, THORBIT_PRODUCT_URL, REFRESH_INTERVAL_MS, ELIGIBLE_GRACE_MS, TRIAL_LENGTH_MS, ThorbitEntitlementSchema, AssertionSchema;
79346
+ var import_node_crypto51, import_zod55, THORBIT_PRODUCT_URL, REFRESH_INTERVAL_MS, ELIGIBLE_GRACE_MS, TRIAL_LENGTH_MS, ThorbitEntitlementSchema, AssertionSchema;
78703
79347
  var init_analytics_entitlement = __esm({
78704
79348
  "src/api/analytics-entitlement.ts"() {
78705
79349
  "use strict";
78706
- import_node_crypto50 = require("crypto");
79350
+ import_node_crypto51 = require("crypto");
78707
79351
  import_zod55 = require("zod");
78708
79352
  init_analytics_repository();
78709
79353
  THORBIT_PRODUCT_URL = "https://thorbit.ai";
@@ -78843,14 +79487,14 @@ function renderPublicForm(form, placementUrl) {
78843
79487
  const brand = form.brand;
78844
79488
  return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>:root{font-family:ui-sans-serif,system-ui;color:${escapeHtml8(brand.textColor)};background:${escapeHtml8(brand.backgroundColor)}}*{box-sizing:border-box}body{margin:0;padding:18px}form{display:grid;gap:14px}label{display:grid;gap:6px;font-size:13px;font-weight:650}.check{grid-template-columns:auto 1fr;align-items:center}.check span{grid-column:2}.check input{grid-column:1;grid-row:1}input,textarea,select{width:100%;min-height:44px;padding:10px 12px;border:1px solid #ccd3df;border-radius:${brand.radius}px;background:white;color:inherit;font:inherit}textarea{min-height:110px;resize:vertical}button{min-height:46px;border:0;border-radius:${brand.radius}px;color:white;background:${escapeHtml8(brand.primaryColor)};font:inherit;font-weight:750;cursor:pointer}.consent{margin:0;color:#667085;font-size:11px;line-height:1.45}.notice{display:none;padding:12px;border-radius:${brand.radius}px;background:#edf8f2;color:#17613c}.hp{position:absolute!important;left:-9999px!important}</style></head><body><form id="mcp-form">${fields}<label class="hp">Website<input name="website" autocomplete="off" tabindex="-1"></label>${form.consent_text ? `<p class="consent">${escapeHtml8(form.consent_text)}</p>` : ""}<button>${escapeHtml8(form.submit_label)}</button><div class="notice" role="status"></div></form><script>(()=>{const form=document.querySelector('#mcp-form'),notice=form.querySelector('.notice'),q=new URLSearchParams(location.search);form.addEventListener('submit',async event=>{event.preventDefault();const button=form.querySelector('button');button.disabled=true;const raw=Object.fromEntries(new FormData(form).entries()),website=String(raw.website||''),clickIds={};delete raw.website;for(const k of ['fbclid','gclid','gbraid','wbraid','ttclid','rdt_cid','msclkid']){const v=q.get(k);if(v)clickIds[k]=v}try{const response=await fetch('/analytics/forms/${escapeHtml8(form.public_id)}/submissions',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({data:raw,website,placementUrl:${JSON.stringify(placementUrl)},visitorId:q.get('visitor')||undefined,sessionId:q.get('session')||undefined,source:q.get('source')||undefined,medium:q.get('medium')||undefined,campaign:q.get('campaign')||undefined,clickIds})});if(!response.ok)throw new Error('submit');notice.textContent=${JSON.stringify(form.success_message)};notice.style.display='block';form.reset();${form.redirect_url ? `setTimeout(()=>{top.location.href=${JSON.stringify(form.redirect_url)}},700);` : ""}}catch{notice.textContent='Your response could not be submitted. Please try again.';notice.style.display='block'}finally{button.disabled=false}})})()</script></body></html>`;
78845
79489
  }
78846
- var import_hono34, import_factory6, import_zod56, import_node_crypto51, import_papaparse6, analyticsApp, auth3, entitlementGuard, ThorbitApiKeySchema, ThorbitCallbackSchema, SiteInputSchema, PixelInputSchema, PixelUpdateSchema, EventSchema, IngestionSchema, ConversionSchema, ExportSchema2, CampaignLinkSchema, FormFieldSchema, FormInputSchema, FormSubmissionSchema, CrmImportSchema, ActivationDestinationSchema, BusinessModelSchema, AdSpendSchema;
79490
+ var import_hono34, import_factory6, import_zod56, import_node_crypto52, import_papaparse6, analyticsApp, auth3, entitlementGuard, ThorbitApiKeySchema, ThorbitCallbackSchema, SiteInputSchema, PixelInputSchema, PixelUpdateSchema, EventSchema, IngestionSchema, ConversionSchema, ExportSchema2, CampaignLinkSchema, FormFieldSchema, FormInputSchema, FormSubmissionSchema, CrmImportSchema, ActivationDestinationSchema, BusinessModelSchema, AdSpendSchema;
78847
79491
  var init_analytics_routes = __esm({
78848
79492
  "src/api/analytics-routes.ts"() {
78849
79493
  "use strict";
78850
79494
  import_hono34 = require("hono");
78851
79495
  import_factory6 = require("hono/factory");
78852
79496
  import_zod56 = require("zod");
78853
- import_node_crypto51 = require("crypto");
79497
+ import_node_crypto52 = require("crypto");
78854
79498
  import_papaparse6 = __toESM(require("papaparse"), 1);
78855
79499
  init_api_auth();
78856
79500
  init_db();
@@ -79181,7 +79825,7 @@ var init_analytics_routes = __esm({
79181
79825
  const lastName = typeof data.last_name === "string" ? data.last_name.trim() : "";
79182
79826
  const fullName = [firstName, lastName].filter(Boolean).join(" ") || (typeof data.name === "string" ? data.name.trim() : "") || email || "Website lead";
79183
79827
  const identitySeed = email || `${fullName}:${Date.now()}`;
79184
- const suffix2 = (0, import_node_crypto51.createHash)("sha256").update(identitySeed).digest("hex").slice(0, 12);
79828
+ const suffix2 = (0, import_node_crypto52.createHash)("sha256").update(identitySeed).digest("hex").slice(0, 12);
79185
79829
  const path6 = `Leads/person-${suffix2}`;
79186
79830
  const { key: memoryKey, error: memoryKeyError } = await getOrCreateUserMemoryKey(user);
79187
79831
  const existing = memoryKey ? await memoryCall("getTool", { vault: "People", path: path6 }, memoryKey) : { ok: false, error: memoryKeyError || "memory_credential_unavailable" };
@@ -79280,9 +79924,9 @@ Submitted the published form.
79280
79924
  occurredAt: (/* @__PURE__ */ new Date()).toISOString()
79281
79925
  });
79282
79926
  const match = {
79283
- ...email ? { emailSha256: (0, import_node_crypto51.createHash)("sha256").update(email).digest("hex") } : {},
79927
+ ...email ? { emailSha256: (0, import_node_crypto52.createHash)("sha256").update(email).digest("hex") } : {},
79284
79928
  ...phone ? {
79285
- phoneSha256: (0, import_node_crypto51.createHash)("sha256").update(phone.replace(/\D/g, "")).digest("hex")
79929
+ phoneSha256: (0, import_node_crypto52.createHash)("sha256").update(phone.replace(/\D/g, "")).digest("hex")
79286
79930
  } : {}
79287
79931
  };
79288
79932
  const activationQueued = await queueAnalyticsActivation({
@@ -79710,7 +80354,7 @@ Submitted the published form.
79710
80354
  rejectedCount += 1;
79711
80355
  continue;
79712
80356
  }
79713
- const digest2 = (0, import_node_crypto51.createHash)("sha256").update(
80357
+ const digest2 = (0, import_node_crypto52.createHash)("sha256").update(
79714
80358
  `${parsed.data.sourceSystem}:${externalId || email || phone || `${fullName}:${index}`}`
79715
80359
  ).digest("hex").slice(0, 16);
79716
80360
  const path6 = `Leads/person-${digest2}`;
@@ -80168,19 +80812,19 @@ var init_analytics_delivery = __esm({
80168
80812
 
80169
80813
  // src/api/scheduled-artifact-owner.ts
80170
80814
  function scheduledArtifactOwnerIdForApiKey(apiKey) {
80171
- return (0, import_node_crypto52.createHash)("sha256").update(apiKey).digest("hex").slice(0, 24);
80815
+ return (0, import_node_crypto53.createHash)("sha256").update(apiKey).digest("hex").slice(0, 24);
80172
80816
  }
80173
- var import_node_crypto52;
80817
+ var import_node_crypto53;
80174
80818
  var init_scheduled_artifact_owner = __esm({
80175
80819
  "src/api/scheduled-artifact-owner.ts"() {
80176
80820
  "use strict";
80177
- import_node_crypto52 = require("crypto");
80821
+ import_node_crypto53 = require("crypto");
80178
80822
  }
80179
80823
  });
80180
80824
 
80181
80825
  // src/api/scheduled-run-view-links.ts
80182
80826
  async function ensureScheduledRunViewLinksSchema() {
80183
- if (schemaReady2) return;
80827
+ if (schemaReady3) return;
80184
80828
  const db = getDb();
80185
80829
  await db.execute(`
80186
80830
  CREATE TABLE IF NOT EXISTS scheduled_run_view_links (
@@ -80228,10 +80872,10 @@ async function ensureScheduledRunViewLinksSchema() {
80228
80872
  CREATE INDEX IF NOT EXISTS scheduled_run_view_links_expiry
80229
80873
  ON scheduled_run_view_links(expires_at, revoked_at)
80230
80874
  `);
80231
- schemaReady2 = true;
80875
+ schemaReady3 = true;
80232
80876
  }
80233
80877
  function tokenHash2(token6) {
80234
- return (0, import_node_crypto53.createHash)("sha256").update(token6).digest("hex");
80878
+ return (0, import_node_crypto54.createHash)("sha256").update(token6).digest("hex");
80235
80879
  }
80236
80880
  function mapRow(row) {
80237
80881
  return {
@@ -80251,9 +80895,9 @@ function mapRow(row) {
80251
80895
  async function createScheduledRunViewLink(input) {
80252
80896
  await ensureScheduledRunViewLinksSchema();
80253
80897
  const now = input.now ?? /* @__PURE__ */ new Date();
80254
- const token6 = (0, import_node_crypto53.randomBytes)(32).toString("base64url");
80898
+ const token6 = (0, import_node_crypto54.randomBytes)(32).toString("base64url");
80255
80899
  const record = {
80256
- shareId: (0, import_node_crypto53.randomUUID)(),
80900
+ shareId: (0, import_node_crypto54.randomUUID)(),
80257
80901
  ownerId: input.ownerId,
80258
80902
  runId: input.runId,
80259
80903
  artifactId: input.artifactId,
@@ -80316,13 +80960,13 @@ async function revokeScheduledRunViewLink(ownerId2, runId, shareId, now = /* @__
80316
80960
  });
80317
80961
  return result.rowsAffected > 0;
80318
80962
  }
80319
- var import_node_crypto53, schemaReady2;
80963
+ var import_node_crypto54, schemaReady3;
80320
80964
  var init_scheduled_run_view_links = __esm({
80321
80965
  "src/api/scheduled-run-view-links.ts"() {
80322
80966
  "use strict";
80323
- import_node_crypto53 = require("crypto");
80967
+ import_node_crypto54 = require("crypto");
80324
80968
  init_db();
80325
- schemaReady2 = false;
80969
+ schemaReady3 = false;
80326
80970
  }
80327
80971
  });
80328
80972
 
@@ -80386,15 +81030,15 @@ ${section(flags.finalCta && Boolean(finalCta), `<section class="section cta" id=
80386
81030
  html,
80387
81031
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
80388
81032
  bytes,
80389
- sha256: (0, import_node_crypto54.createHash)("sha256").update(html).digest("hex"),
81033
+ sha256: (0, import_node_crypto55.createHash)("sha256").update(html).digest("hex"),
80390
81034
  generatedAt: generatedAtIso
80391
81035
  };
80392
81036
  }
80393
- var import_node_crypto54;
81037
+ var import_node_crypto55;
80394
81038
  var init_render2 = __esm({
80395
81039
  "src/personal-authority/render.ts"() {
80396
81040
  "use strict";
80397
- import_node_crypto54 = require("crypto");
81041
+ import_node_crypto55 = require("crypto");
80398
81042
  }
80399
81043
  });
80400
81044
 
@@ -80497,15 +81141,15 @@ ${section2(flags.finalCta && Boolean(finalCta), `<section class="section cta" id
80497
81141
  html,
80498
81142
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
80499
81143
  bytes,
80500
- sha256: (0, import_node_crypto55.createHash)("sha256").update(html).digest("hex"),
81144
+ sha256: (0, import_node_crypto56.createHash)("sha256").update(html).digest("hex"),
80501
81145
  generatedAt: generatedAtIso
80502
81146
  };
80503
81147
  }
80504
- var import_node_crypto55, FONT_STACKS;
81148
+ var import_node_crypto56, FONT_STACKS;
80505
81149
  var init_render_v2 = __esm({
80506
81150
  "src/personal-authority/render-v2.ts"() {
80507
81151
  "use strict";
80508
- import_node_crypto55 = require("crypto");
81152
+ import_node_crypto56 = require("crypto");
80509
81153
  init_contracts();
80510
81154
  FONT_STACKS = Object.freeze({
80511
81155
  "editorial-serif": "Iowan Old Style, Baskerville, Times New Roman, serif",
@@ -80621,15 +81265,15 @@ body{overflow-x:hidden}.lead-story__body,.lead-support__item,.story-card__body{m
80621
81265
  @media(max-width:620px){.desk-section__grid--panorama,.desk-section__grid--mosaic,.desk-section__grid--tiles{grid-template-columns:1fr}.story-card--panorama{display:block}.story-card--panorama h3,.story-card--mosaic-lead h3{font-size:34px}.story-card--mosaic-lead{grid-column:auto;grid-row:auto}.desk-section__grid--tiles>.story-card:nth-child(3n+2){margin-top:0}}
80622
81266
  </style></head><body data-theme="${escapeHtml11(config.theme)}" id="top" data-scheduled-renderer="newsroom_publisher_v1">${ticker}${masthead}${navigation}<main>${leadGrid}${latest}${sections}${newsletter}${pressRoom}${trustCenter}</main>${trustFooter}</body></html>`;
80623
81267
  const bytes = Buffer.byteLength(html);
80624
- const sha2565 = (0, import_node_crypto56.createHash)("sha256").update(html).digest("hex");
81268
+ const sha2565 = (0, import_node_crypto57.createHash)("sha256").update(html).digest("hex");
80625
81269
  const filename2 = `${brand.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "newsroom"}-news-site.html`;
80626
81270
  return { html, filename: filename2, bytes, sha256: sha2565, generatedAt: generatedAtIso };
80627
81271
  }
80628
- var import_node_crypto56;
81272
+ var import_node_crypto57;
80629
81273
  var init_render3 = __esm({
80630
81274
  "src/newsroom-publisher/render.ts"() {
80631
81275
  "use strict";
80632
- import_node_crypto56 = require("crypto");
81276
+ import_node_crypto57 = require("crypto");
80633
81277
  }
80634
81278
  });
80635
81279
 
@@ -80721,7 +81365,7 @@ function renderBlogArticleV1(input, config, generatedAt) {
80721
81365
  :root{--ink:#10213d;--ink2:#1c355a;--blue:#2563eb;--blue2:#1749b6;--mint:#45d5aa;--paper:#f7f5f0;--line:#dce4ef;--card:#fff;--body:#475569;--serif:Iowan Old Style,Palatino Linotype,Palatino,Georgia,serif;--sans:Inter,Avenir Next,ui-sans-serif,system-ui,sans-serif}body[data-theme="slate"]{--ink:#1e293b;--ink2:#334155;--blue:#475569;--blue2:#1e293b;--mint:#94a3b8;--paper:#f8fafc}body[data-theme="forest"]{--ink:#12352e;--ink2:#23584c;--blue:#147d64;--blue2:#0c5b48;--mint:#79d8bd;--paper:#f4f8f3}*{box-sizing:border-box}html{scroll-behavior:smooth;background:var(--paper)}body{margin:0;color:#252a33;background:var(--paper);font-family:var(--sans);line-height:1.6}a{color:inherit}button{font:inherit}button:focus-visible,a:focus-visible{outline:3px solid var(--mint);outline-offset:3px}.progress{position:fixed;z-index:50;top:0;left:0;height:4px;width:0;background:var(--mint)}.hero{position:relative;overflow:hidden;color:#fff;background:var(--ink)}.hero:after{position:absolute;right:-110px;top:-150px;width:420px;height:420px;border:1px solid #ffffff1a;border-radius:50%;content:""}.hero-inner{position:relative;z-index:1;max-width:1240px;margin:auto;padding:58px 42px 126px}.breadcrumb{display:flex;gap:8px;margin-bottom:34px;color:#dbeafecc;font-size:14px}.eyebrow{display:block;color:var(--blue);font-size:12px;font-weight:800;letter-spacing:.16em;text-transform:uppercase}.hero .eyebrow{color:var(--mint)}h1,h2,h3,p{margin-top:0}.hero h1{max-width:1000px;margin:14px 0 24px;font:700 clamp(44px,6vw,78px)/.98 var(--serif);letter-spacing:-.035em}.hero .dek{max-width:780px;color:#dbeafe;font-size:20px}.contributors{margin-top:30px;color:#eff6ff;font-size:14px}.contributors a{font-weight:800}.meta{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:#dbeafecc}.layout{position:relative;z-index:2;display:grid;grid-template-columns:minmax(0,760px) 290px;gap:64px;max-width:1240px;margin:-78px auto 0;padding:0 42px 80px}.main{min-width:0}.takeaways{padding:38px 44px;border-radius:24px;background:#fff;box-shadow:0 24px 70px #10213d24}.takeaways h2{margin:5px 0 20px;font:700 32px/1.1 var(--serif);color:var(--ink)}.takeaways ul{display:grid;gap:16px;margin:0;padding:0;list-style:none}.takeaways li{position:relative;padding-left:26px;color:var(--body);font-size:17px}.takeaways li:before{position:absolute;left:2px;top:.7em;width:8px;height:8px;border-radius:50%;background:var(--blue);content:""}.intro{padding:40px 0 4px}.intro p,.article-section>p{margin-bottom:22px;color:var(--body);font:400 18px/1.75 var(--sans)}.article-section{scroll-margin-top:28px;padding-top:45px}.article-section h2,.faq h2{margin:6px 0 20px;color:var(--ink);font:700 38px/1.08 var(--serif);letter-spacing:-.02em}.article-section ul{display:grid;gap:10px;margin:22px 0;padding-left:24px;color:var(--body);font-size:17px}.article-section li::marker{color:var(--blue)}.callout{margin:34px 0;padding:24px 28px;border-left:4px solid var(--blue);border-radius:0 16px 16px 0;background:#fff}.callout span{color:var(--blue);font-size:11px;font-weight:800;letter-spacing:.13em;text-transform:uppercase}.callout h3{margin:5px 0 8px;color:var(--ink);font:700 24px/1.15 var(--serif)}.callout p{margin:0;color:var(--body)}.rail{padding-top:0}.sidebar-media{overflow:hidden;margin:0 0 18px;border:1px solid #dbeafe;border-radius:16px;background:#fff}.sidebar-media img,.sidebar-media svg{display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.sidebar-media figcaption{padding:10px 13px;color:#64748b;font-size:11px}.disclosure{margin-bottom:28px;border-top:1px solid #dbeafe;border-bottom:1px solid #dbeafe}.disclosure button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:14px;padding:15px 0;border:0;color:#64748b;background:transparent;text-align:left;font-size:12px;cursor:pointer}.disclosure button b{color:var(--blue);font-size:18px;transition:transform .2s}.disclosure button[aria-expanded="true"] b{transform:rotate(180deg)}.disclosure p{padding:0 0 16px;margin:0;color:#64748b;font-size:12px}.toc{position:sticky;top:30px;padding:22px;border:1px solid var(--line);border-radius:16px;background:#fff;box-shadow:0 2px 5px #10213d12}.toc header{display:flex;align-items:center;justify-content:space-between}.toc header strong{color:var(--ink);font:700 21px/1 var(--serif)}.toc header button{width:34px;height:34px;border:0;border-radius:50%;color:var(--blue);background:transparent;cursor:pointer}.toc nav{display:grid;margin-top:14px}.toc nav a{display:flex;align-items:flex-start;gap:11px;padding:8px 0;color:#64748b;text-decoration:none;font-size:13px;line-height:1.35}.toc nav i{width:8px;height:8px;margin-top:5px;border:2px solid #bfdbfe;border-radius:50%}.toc nav a[aria-current="location"]{color:var(--ink);font-weight:800}.toc nav a[aria-current="location"] i{border-color:var(--blue);background:var(--blue)}.back-top{width:100%;margin-top:18px;padding:16px 0 0;border:0;border-top:1px solid var(--line);color:var(--blue);background:transparent;text-align:right;font-size:13px;font-weight:800;cursor:pointer}.faq{scroll-margin-top:28px;padding-top:64px}.faq>div{border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.faq article+article{border-top:1px solid var(--line)}.faq h3{margin:0}.faq h3 button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:18px;padding:19px 0;border:0;color:var(--ink);background:transparent;text-align:left;font-weight:800;cursor:pointer}.faq h3 b{color:var(--blue);font-size:22px}.faq article>p{padding:0 0 20px;margin:0;color:var(--body);font-size:16px}.author-card{margin-top:64px;padding:32px 36px;border-radius:18px;background:#fff}.article-actions{display:flex;flex-wrap:wrap;justify-content:space-between;gap:14px}.article-actions button{display:inline-flex;min-height:46px;align-items:center;justify-content:center;gap:8px;padding:8px 18px;border:2px solid var(--blue);border-radius:999px;color:var(--ink);background:#fff;font-weight:800;cursor:pointer}.article-actions button:hover{color:#fff;background:var(--blue)}.action-status{min-height:20px;margin:8px 0 0;color:var(--blue);text-align:right;font-size:13px;font-weight:700}.author-grid{display:grid;grid-template-columns:auto 1fr auto;gap:22px;align-items:center;margin-top:24px}.author-portrait{width:88px;height:88px;border-radius:50%;object-fit:cover}.author-initials{display:grid;place-items:center;color:var(--ink);background:#fecdd3;font-size:22px;font-weight:900}.author-grid span{color:#64748b;font-size:13px}.author-grid h2{margin:2px 0;color:var(--ink);font:700 27px/1 var(--serif)}.author-grid h2 a{text-decoration:none}.author-grid p{margin:8px 0;color:#64748b;font-size:14px}.author-grid nav{display:flex;flex-wrap:wrap;gap:14px}.author-grid nav a{color:#64748b;font-size:12px}.author-more{display:inline-flex;min-height:48px;align-items:center;justify-content:center;padding:10px 24px;border:2px solid var(--blue);color:var(--blue);text-align:center;text-decoration:none;font-size:13px;font-weight:800}.author-bio{margin:24px 0 0!important;padding-top:20px;border-top:1px solid var(--line);color:var(--body);font-size:16px}dialog{width:min(720px,calc(100% - 30px));max-height:calc(100dvh - 30px);padding:0;border:0;border-radius:18px;color:var(--ink);background:#fff;box-shadow:0 24px 70px #10213d38}dialog::backdrop{background:#0f172a7a;backdrop-filter:blur(2px)}dialog>div{position:relative;padding:30px}dialog h2{margin:0 50px 2px 0;font:700 30px/1.1 var(--serif)}dialog>div>p{color:#64748b}.dialog-close{position:absolute;top:13px;right:13px;width:44px;height:44px;border:0;border-radius:50%;color:var(--blue);background:transparent;font-size:29px;cursor:pointer}dialog section{border-top:1px solid var(--line)}dialog section button{display:block;width:100%;padding:17px 5px;border:0;border-bottom:1px solid var(--line);color:var(--body);background:#fff;text-align:left;line-height:1.65;cursor:pointer}.citation-status{min-height:22px;margin:14px 0 0!important;color:var(--blue)!important;font-size:13px;font-weight:800}.generated{max-width:1156px;margin:-52px auto 50px;padding:0 42px;color:#94a3b8;font-size:11px}@media(max-width:900px){.layout{display:block}.rail{display:none}.hero-inner{padding-bottom:116px}}@media(max-width:620px){.hero-inner{padding:36px 20px 104px}.hero h1{font-size:46px}.hero .dek{font-size:17px}.layout{margin-top:-70px;padding:0 16px 54px}.takeaways{padding:28px 24px}.article-section h2,.faq h2{font-size:32px}.author-card{padding:24px}.article-actions{display:grid;justify-content:start}.author-grid{grid-template-columns:1fr;gap:12px}.author-more{width:100%}dialog>div{padding:24px}.generated{padding:0 20px}}
80722
81366
  </style></head><body data-theme="${escapeHtml12(config.theme)}" data-scheduled-renderer="blog_article_v1"><div class="progress" aria-hidden="true"></div><header class="hero"><div class="hero-inner"><nav class="breadcrumb" aria-label="Breadcrumb"><a href="${safeHref4(input.canonicalUrl)}">${escapeHtml12(input.category)}</a><span>/</span><span>Article</span></nav><span class="eyebrow">${escapeHtml12(input.eyebrow)}</span><h1>${escapeHtml12(input.title)}</h1>${input.dek ? `<p class="dek">${escapeHtml12(input.dek)}</p>` : ""}<div class="contributors">${contributorText}<div class="meta"><time datetime="${escapeHtml12(articleDate)}">Updated ${formatDate2(articleDate)}</time><span>\u2022</span><span>${input.readTimeMinutes} min read</span>${input.reviewer ? "<span>\u2022</span><strong>Expert reviewed</strong>" : ""}</div></div></div></header><main class="layout"><article class="main"><section class="takeaways"><span class="eyebrow">The short answer</span><h2>Key takeaways</h2><ul>${input.keyTakeaways.map((item) => `<li>${escapeHtml12(item)}</li>`).join("")}</ul></section><section class="intro">${input.introduction.map((paragraph) => `<p>${escapeHtml12(paragraph)}</p>`).join("")}</section>${sections}${faq}${author}</article><div class="rail">${media}${disclosure}${toc}</div></main>${config.showGeneratedAt ? `<p class="generated">Rendered ${formatDate2(generatedAtIso)} by MCP Scraper.</p>` : ""}${citationDialog}<script>(()=>{const citations=${jsonForScript(citationValues)};const canonical=${jsonForScript(input.canonicalUrl)};const title=${jsonForScript(input.title)};const progress=document.querySelector('.progress');const tocLinks=[...document.querySelectorAll('.toc nav a')];const targets=tocLinks.map(link=>document.querySelector(link.hash)).filter(Boolean);const update=()=>{const max=document.documentElement.scrollHeight-innerHeight;if(progress)progress.style.width=(max>0?scrollY/max*100:0)+'%';let active=targets[0]?.id;for(const target of targets){if(target.getBoundingClientRect().top<=innerHeight*.55)active=target.id}tocLinks.forEach(link=>link.toggleAttribute('aria-current',link.hash==='#'+active));tocLinks.forEach(link=>{if(link.hash==='#'+active)link.setAttribute('aria-current','location');else link.removeAttribute('aria-current')})};addEventListener('scroll',update,{passive:true});addEventListener('resize',update);update();document.querySelectorAll('.faq h3 button').forEach(button=>button.addEventListener('click',()=>{const panel=document.getElementById(button.getAttribute('aria-controls'));const open=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!open));panel.hidden=open;button.querySelector('b').textContent=open?'+':'\u2212'}));const disclosure=document.querySelector('.disclosure button');disclosure?.addEventListener('click',()=>{const panel=document.getElementById(disclosure.getAttribute('aria-controls'));const open=disclosure.getAttribute('aria-expanded')==='true';disclosure.setAttribute('aria-expanded',String(!open));panel.hidden=open});const tocToggle=document.querySelector('.toc header button');tocToggle?.addEventListener('click',()=>{const nav=document.getElementById('toc-links');const open=tocToggle.getAttribute('aria-expanded')==='true';tocToggle.setAttribute('aria-expanded',String(!open));nav.hidden=open;tocToggle.textContent=open?'\u2304':'\u2303'});document.querySelector('.back-top')?.addEventListener('click',()=>scrollTo({top:0,behavior:'smooth'}));const copy=async text=>{try{await navigator.clipboard.writeText(text)}catch{const area=document.createElement('textarea');area.value=text;area.style.position='fixed';area.style.opacity='0';document.body.append(area);area.select();document.execCommand('copy');area.remove()}};const dialog=document.getElementById('citation-dialog');document.querySelector('[data-cite]')?.addEventListener('click',()=>dialog.showModal());document.querySelector('.dialog-close')?.addEventListener('click',()=>dialog.close());dialog?.addEventListener('click',event=>{if(event.target===dialog)dialog.close()});document.querySelectorAll('[data-citation]').forEach(button=>button.addEventListener('click',async()=>{const style=button.dataset.citation;await copy(citations[style]);document.querySelector('.citation-status').textContent=style+' citation copied to clipboard.'}));document.querySelector('[data-share]')?.addEventListener('click',async()=>{const status=document.querySelector('.action-status');if(navigator.share){try{await navigator.share({title,url:canonical});status.textContent='Sharing options opened.';return}catch(error){if(error.name==='AbortError')return}}await copy(canonical);status.textContent='Article link copied to clipboard.'})})()</script></body></html>`;
80723
81367
  const bytes = Buffer.byteLength(html);
80724
- const sha2565 = (0, import_node_crypto57.createHash)("sha256").update(html).digest("hex");
81368
+ const sha2565 = (0, import_node_crypto58.createHash)("sha256").update(html).digest("hex");
80725
81369
  return {
80726
81370
  html,
80727
81371
  filename: "blog-article.html",
@@ -80731,11 +81375,11 @@ function renderBlogArticleV1(input, config, generatedAt) {
80731
81375
  generatedAt: generatedAtIso
80732
81376
  };
80733
81377
  }
80734
- var import_node_crypto57;
81378
+ var import_node_crypto58;
80735
81379
  var init_render4 = __esm({
80736
81380
  "src/blog-article/render.ts"() {
80737
81381
  "use strict";
80738
- import_node_crypto57 = require("crypto");
81382
+ import_node_crypto58 = require("crypto");
80739
81383
  }
80740
81384
  });
80741
81385
 
@@ -81066,7 +81710,7 @@ function policy6() {
81066
81710
  };
81067
81711
  }
81068
81712
  function runStorageSegment(runId) {
81069
- return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${(0, import_node_crypto58.createHash)("sha256").update(runId).digest("hex").slice(0, 32)}`;
81713
+ return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${(0, import_node_crypto59.createHash)("sha256").update(runId).digest("hex").slice(0, 32)}`;
81070
81714
  }
81071
81715
  async function createScheduledRunArtifact(args) {
81072
81716
  if (args.rendered.bytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) {
@@ -81107,12 +81751,12 @@ async function readScheduledRunArtifact(args) {
81107
81751
  if (!window2 || window2.nextOffset !== null || window2.totalBytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) return null;
81108
81752
  return window2.text;
81109
81753
  }
81110
- var import_node_crypto58, SCHEDULED_RUN_ARTIFACT_PREFIX, SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS, SCHEDULED_RUN_ARTIFACT_MAX_BYTES;
81754
+ var import_node_crypto59, SCHEDULED_RUN_ARTIFACT_PREFIX, SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS, SCHEDULED_RUN_ARTIFACT_MAX_BYTES;
81111
81755
  var init_scheduled_run_artifact_store = __esm({
81112
81756
  "src/scheduled-artifacts/scheduled-run-artifact-store.ts"() {
81113
81757
  "use strict";
81114
81758
  init_private_artifacts();
81115
- import_node_crypto58 = require("crypto");
81759
+ import_node_crypto59 = require("crypto");
81116
81760
  SCHEDULED_RUN_ARTIFACT_PREFIX = "scheduled-run-artifacts/";
81117
81761
  SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
81118
81762
  SCHEDULED_RUN_ARTIFACT_MAX_BYTES = 2e6;
@@ -81190,357 +81834,6 @@ var init_public_scheduled_result_routes = __esm({
81190
81834
  }
81191
81835
  });
81192
81836
 
81193
- // src/api/service-connections.ts
81194
- function ensureServiceConnectionsSchema() {
81195
- const currentDb = getDb();
81196
- if (schemaReady3 && schemaDb6 === currentDb) return schemaReady3;
81197
- schemaDb6 = currentDb;
81198
- schemaReady3 = (async () => {
81199
- const db = currentDb;
81200
- await db.execute(`
81201
- CREATE TABLE IF NOT EXISTS service_connections (
81202
- id TEXT PRIMARY KEY,
81203
- user_id INTEGER NOT NULL REFERENCES users(id),
81204
- provider_config_key TEXT NOT NULL,
81205
- provider TEXT,
81206
- transport TEXT NOT NULL,
81207
- upstream_connection_id TEXT,
81208
- label TEXT,
81209
- user_label TEXT,
81210
- provider_account_id TEXT,
81211
- provider_account_email TEXT,
81212
- provider_account_name TEXT,
81213
- provider_identity_checked_at TEXT,
81214
- provider_identity_source_updated_at TEXT,
81215
- lifecycle_status TEXT NOT NULL DEFAULT 'pending',
81216
- operational_status TEXT NOT NULL DEFAULT 'unknown',
81217
- reconnect_required INTEGER NOT NULL DEFAULT 0,
81218
- actions_enabled INTEGER NOT NULL DEFAULT 0,
81219
- read_tools_json TEXT NOT NULL DEFAULT '[]',
81220
- action_tools_json TEXT NOT NULL DEFAULT '[]',
81221
- tool_revision TEXT,
81222
- last_checked_at TEXT,
81223
- last_successful_call_at TEXT,
81224
- last_failure_at TEXT,
81225
- last_failure_code TEXT,
81226
- last_failure_retryable INTEGER,
81227
- consecutive_failures INTEGER NOT NULL DEFAULT 0,
81228
- vault_name TEXT,
81229
- table_name TEXT,
81230
- legacy_owner_identity TEXT,
81231
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
81232
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
81233
- UNIQUE(user_id, provider_config_key, upstream_connection_id)
81234
- )
81235
- `);
81236
- try {
81237
- await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
81238
- } catch {
81239
- }
81240
- try {
81241
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
81242
- } catch {
81243
- }
81244
- try {
81245
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
81246
- } catch {
81247
- }
81248
- try {
81249
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
81250
- } catch {
81251
- }
81252
- try {
81253
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
81254
- } catch {
81255
- }
81256
- try {
81257
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
81258
- } catch {
81259
- }
81260
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
81261
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
81262
- await db.execute(`
81263
- CREATE TABLE IF NOT EXISTS service_connection_health_events (
81264
- id TEXT PRIMARY KEY,
81265
- connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
81266
- operational_status TEXT NOT NULL,
81267
- failure_code TEXT,
81268
- retryable INTEGER,
81269
- evidence_source TEXT NOT NULL,
81270
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
81271
- )
81272
- `);
81273
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
81274
- await db.execute(`
81275
- CREATE TABLE IF NOT EXISTS service_connection_action_audit (
81276
- id TEXT PRIMARY KEY,
81277
- connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
81278
- user_id INTEGER NOT NULL REFERENCES users(id),
81279
- tool TEXT NOT NULL,
81280
- request_id TEXT NOT NULL,
81281
- status TEXT NOT NULL,
81282
- error_code TEXT,
81283
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
81284
- UNIQUE(user_id, request_id)
81285
- )
81286
- `);
81287
- try {
81288
- await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
81289
- } catch {
81290
- }
81291
- try {
81292
- await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
81293
- } catch {
81294
- }
81295
- })().catch((error) => {
81296
- schemaReady3 = null;
81297
- schemaDb6 = null;
81298
- throw error;
81299
- });
81300
- return schemaReady3;
81301
- }
81302
- function parseTools(value) {
81303
- if (typeof value !== "string") return [];
81304
- try {
81305
- const parsed = JSON.parse(value);
81306
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
81307
- } catch {
81308
- return [];
81309
- }
81310
- }
81311
- function rowToRecord(row) {
81312
- return {
81313
- id: String(row.id),
81314
- userId: Number(row.user_id),
81315
- providerConfigKey: String(row.provider_config_key),
81316
- provider: typeof row.provider === "string" ? row.provider : null,
81317
- transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
81318
- upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
81319
- label: typeof row.label === "string" ? row.label : null,
81320
- userLabel: typeof row.user_label === "string" ? row.user_label : null,
81321
- providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
81322
- providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
81323
- providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
81324
- providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
81325
- providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
81326
- lifecycleStatus: String(row.lifecycle_status),
81327
- operationalStatus: String(row.operational_status),
81328
- reconnectRequired: Number(row.reconnect_required) === 1,
81329
- actionsEnabled: Number(row.actions_enabled) === 1,
81330
- readTools: parseTools(row.read_tools_json),
81331
- actionTools: parseTools(row.action_tools_json),
81332
- toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
81333
- lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
81334
- lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
81335
- lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
81336
- lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
81337
- lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
81338
- consecutiveFailures: Number(row.consecutive_failures ?? 0),
81339
- vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
81340
- tableName: typeof row.table_name === "string" ? row.table_name : null,
81341
- createdAt: String(row.created_at),
81342
- updatedAt: String(row.updated_at)
81343
- };
81344
- }
81345
- async function userIdForIdentity(identity) {
81346
- const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
81347
- if (!user) throw new Error("service_connection_user_not_found");
81348
- return Number(user.id);
81349
- }
81350
- async function reconcileDiscoveredNangoConnections(identity, discovered) {
81351
- await ensureServiceConnectionsSchema();
81352
- const db = getDb();
81353
- const userId = await userIdForIdentity(identity);
81354
- for (const connection of discovered) {
81355
- await db.execute({
81356
- sql: `
81357
- INSERT INTO service_connections (
81358
- id, user_id, provider_config_key, provider, transport, upstream_connection_id,
81359
- label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
81360
- ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
81361
- ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
81362
- provider = excluded.provider,
81363
- label = COALESCE(excluded.label, service_connections.label),
81364
- lifecycle_status = excluded.lifecycle_status,
81365
- reconnect_required = excluded.reconnect_required,
81366
- updated_at = excluded.updated_at
81367
- `,
81368
- args: [
81369
- (0, import_node_crypto59.randomUUID)(),
81370
- userId,
81371
- connection.providerConfigKey,
81372
- connection.provider,
81373
- connection.upstreamConnectionId,
81374
- connection.label,
81375
- connection.lifecycleStatus,
81376
- connection.reconnectRequired ? 1 : 0,
81377
- identity,
81378
- connection.createdAt,
81379
- connection.updatedAt
81380
- ]
81381
- });
81382
- }
81383
- return listServiceConnections(identity, "nango");
81384
- }
81385
- async function listServiceConnections(identity, transport) {
81386
- await ensureServiceConnectionsSchema();
81387
- const userId = await userIdForIdentity(identity);
81388
- const result = await getDb().execute({
81389
- sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
81390
- args: transport ? [userId, transport] : [userId]
81391
- });
81392
- return result.rows.map((row) => rowToRecord(row));
81393
- }
81394
- async function getOwnedServiceConnection(identity, connectionId) {
81395
- await ensureServiceConnectionsSchema();
81396
- const userId = await userIdForIdentity(identity);
81397
- const result = await getDb().execute({
81398
- sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
81399
- args: [connectionId, userId]
81400
- });
81401
- const row = result.rows[0];
81402
- return row ? rowToRecord(row) : null;
81403
- }
81404
- async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
81405
- await ensureServiceConnectionsSchema();
81406
- await getDb().execute({
81407
- sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
81408
- args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
81409
- });
81410
- }
81411
- async function setServiceConnectionProviderIdentity(args) {
81412
- await ensureServiceConnectionsSchema();
81413
- await getDb().execute({
81414
- sql: `
81415
- UPDATE service_connections SET
81416
- provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
81417
- provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
81418
- updated_at = datetime('now')
81419
- WHERE id = ?
81420
- `,
81421
- args: [
81422
- args.providerAccountId ?? null,
81423
- args.providerAccountEmail?.toLowerCase() ?? null,
81424
- args.providerAccountName ?? null,
81425
- args.sourceUpdatedAt,
81426
- args.connectionId
81427
- ]
81428
- });
81429
- }
81430
- async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
81431
- const connection = await getOwnedServiceConnection(identity, connectionId);
81432
- if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
81433
- await getDb().execute({
81434
- sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
81435
- args: [userLabel, connectionId, connection.userId]
81436
- });
81437
- return userLabel;
81438
- }
81439
- async function recordServiceConnectionHealth(args) {
81440
- await ensureServiceConnectionsSchema();
81441
- const now = (/* @__PURE__ */ new Date()).toISOString();
81442
- await getDb().execute({
81443
- sql: `
81444
- UPDATE service_connections SET
81445
- operational_status = ?, last_checked_at = ?,
81446
- last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
81447
- last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
81448
- last_failure_code = ?, last_failure_retryable = ?,
81449
- consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
81450
- updated_at = datetime('now')
81451
- WHERE id = ?
81452
- `,
81453
- args: [
81454
- args.operationalStatus,
81455
- now,
81456
- args.operationalStatus,
81457
- now,
81458
- args.operationalStatus,
81459
- now,
81460
- args.failureCode ?? null,
81461
- args.retryable == null ? null : args.retryable ? 1 : 0,
81462
- args.operationalStatus,
81463
- args.connectionId
81464
- ]
81465
- });
81466
- await getDb().execute({
81467
- sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
81468
- args: [(0, import_node_crypto59.randomUUID)(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
81469
- });
81470
- }
81471
- async function setServiceConnectionActions(identity, connectionId, enabled) {
81472
- const connection = await getOwnedServiceConnection(identity, connectionId);
81473
- if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
81474
- await getDb().execute({
81475
- sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
81476
- args: [enabled ? 1 : 0, connectionId, connection.userId]
81477
- });
81478
- return enabled;
81479
- }
81480
- async function markServiceConnectionDisconnected(identity, connectionId) {
81481
- const connection = await getOwnedServiceConnection(identity, connectionId);
81482
- if (!connection) throw new Error("service_connection_not_found");
81483
- await getDb().execute({
81484
- sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
81485
- args: [connectionId, connection.userId]
81486
- });
81487
- const remaining = await getDb().execute({
81488
- sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
81489
- args: [connection.userId]
81490
- });
81491
- return Number(remaining.rows[0]?.count ?? 0);
81492
- }
81493
- async function recordServiceConnectionAction(args) {
81494
- await ensureServiceConnectionsSchema();
81495
- const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
81496
- if (!connection) throw new Error("service_connection_not_found");
81497
- await getDb().execute({
81498
- sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
81499
- args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
81500
- });
81501
- }
81502
- async function claimServiceConnectionAction(args) {
81503
- await ensureServiceConnectionsSchema();
81504
- const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
81505
- if (!connection) throw new Error("service_connection_not_found");
81506
- const inserted = await getDb().execute({
81507
- sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
81508
- args: [(0, import_node_crypto59.randomUUID)(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
81509
- });
81510
- if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
81511
- const existing = await getDb().execute({
81512
- sql: `SELECT connection_id, tool, status, error_code, request_digest, result_json FROM service_connection_action_audit WHERE user_id = ? AND request_id = ? LIMIT 1`,
81513
- args: [connection.userId, args.requestId]
81514
- });
81515
- const row = existing.rows[0];
81516
- const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
81517
- let result = void 0;
81518
- if (!conflict && typeof row?.result_json === "string") {
81519
- try {
81520
- result = JSON.parse(row.result_json);
81521
- } catch {
81522
- result = void 0;
81523
- }
81524
- }
81525
- return {
81526
- claimed: false,
81527
- status: String(row?.status ?? "unknown"),
81528
- errorCode: typeof row?.error_code === "string" ? row.error_code : null,
81529
- conflict,
81530
- ...result !== void 0 ? { result } : {}
81531
- };
81532
- }
81533
- var import_node_crypto59, schemaReady3, schemaDb6;
81534
- var init_service_connections = __esm({
81535
- "src/api/service-connections.ts"() {
81536
- "use strict";
81537
- import_node_crypto59 = require("crypto");
81538
- init_db();
81539
- schemaReady3 = null;
81540
- schemaDb6 = null;
81541
- }
81542
- });
81543
-
81544
81837
  // src/api/scheduler-integration-auth.ts
81545
81838
  function signingSecret() {
81546
81839
  const secret2 = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();
@@ -84444,6 +84737,15 @@ function retryAfterMsFromText(value) {
84444
84737
  function normalizeNangoClientError(error) {
84445
84738
  if (error instanceof MainNangoTransportError) return error;
84446
84739
  const detail = nangoClientErrorText(error);
84740
+ const structured = error && typeof error === "object" ? error : null;
84741
+ if (structured?.code === -32001 || structured?.name === "RequestTimeout" || /Request timed out|RequestTimeout|Maximum total timeout exceeded/i.test(detail)) {
84742
+ return new MainNangoTransportError(
84743
+ "The connected service operation timed out before returning a result. Retry failed items or use a smaller batch.",
84744
+ 503,
84745
+ "mcp_request_timeout",
84746
+ true
84747
+ );
84748
+ }
84447
84749
  if (/SERVICE_DISABLED|accessNotConfigured/i.test(detail)) {
84448
84750
  return new MainNangoTransportError(
84449
84751
  "The provider API required by this connection is disabled. Enable it in the provider project, wait for propagation, then retry.",
@@ -84474,6 +84776,12 @@ function normalizeNangoClientError(error) {
84474
84776
  }
84475
84777
  return new MainNangoTransportError("The service connection is temporarily unavailable.", 503, "connection_transport_unavailable");
84476
84778
  }
84779
+ async function callNangoToolWithBudget(client2, tool, input) {
84780
+ return client2.callTool(
84781
+ { name: tool, arguments: input },
84782
+ { timeout: NANGO_TOOL_CALL_TIMEOUT_MS }
84783
+ );
84784
+ }
84477
84785
  function asRecord2(value) {
84478
84786
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
84479
84787
  }
@@ -84917,7 +85225,7 @@ async function callNangoToolDirect(args) {
84917
85225
  let result;
84918
85226
  let providerError;
84919
85227
  try {
84920
- result = await withNangoClient(connection, (client2) => client2.callTool({ name: args.tool, arguments: args.input }));
85228
+ result = await withNangoClient(connection, (client2) => callNangoToolWithBudget(client2, args.tool, args.input));
84921
85229
  } catch (error) {
84922
85230
  providerError = error;
84923
85231
  }
@@ -84982,7 +85290,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
84982
85290
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
84983
85291
  };
84984
85292
  }
84985
- var import_node_crypto68, import_client13, DEFAULT_NANGO_MCP_URL, NANGO_TIMEOUT_MS, NANGO_CONNECTION_PAGE_SIZE, NANGO_CONNECTION_MAX_PAGES, PROVIDER_IDENTITY_RETRY_MS, MAIN_INTEGRATION_CONTRACT_VERSION, MAIN_INTEGRATION_CONTRACT_HASH, MainNangoTransportError, IDENTITY_SUPPORTED_PROVIDERS;
85293
+ var import_node_crypto68, import_client13, DEFAULT_NANGO_MCP_URL, NANGO_TIMEOUT_MS, NANGO_TOOL_CALL_TIMEOUT_MS, NANGO_CONNECTION_PAGE_SIZE, NANGO_CONNECTION_MAX_PAGES, PROVIDER_IDENTITY_RETRY_MS, MAIN_INTEGRATION_CONTRACT_VERSION, MAIN_INTEGRATION_CONTRACT_HASH, MainNangoTransportError, IDENTITY_SUPPORTED_PROVIDERS;
84986
85294
  var init_main_nango_transport = __esm({
84987
85295
  "src/api/main-nango-transport.ts"() {
84988
85296
  "use strict";
@@ -84994,6 +85302,7 @@ var init_main_nango_transport = __esm({
84994
85302
  init_connected_tool_policy();
84995
85303
  DEFAULT_NANGO_MCP_URL = "https://api.nango.dev/mcp";
84996
85304
  NANGO_TIMEOUT_MS = 2e4;
85305
+ NANGO_TOOL_CALL_TIMEOUT_MS = 24e4;
84997
85306
  NANGO_CONNECTION_PAGE_SIZE = 100;
84998
85307
  NANGO_CONNECTION_MAX_PAGES = 20;
84999
85308
  PROVIDER_IDENTITY_RETRY_MS = 6 * 60 * 60 * 1e3;