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
@@ -10,13 +10,13 @@ import {
10
10
  queuedCommonsDocumentCount,
11
11
  semanticCommonsEntityScores,
12
12
  typeFromMime
13
- } from "./chunk-DBWWIK7O.js";
13
+ } from "./chunk-EVOBJYBE.js";
14
14
  import {
15
15
  REQUIRED_LOCATION_STATE_CODES,
16
16
  importHostedCensusPlacesCsv,
17
17
  importHostedZipGroupsCsv,
18
18
  listActiveHostedLocationDatasets
19
- } from "./chunk-PMCT2YQM.js";
19
+ } from "./chunk-HBZENASD.js";
20
20
  import {
21
21
  cancelPendingLeadListEnrichmentRows,
22
22
  claimLeadListEnrichmentOutbox,
@@ -43,7 +43,7 @@ import {
43
43
  saveLeadListEnrichmentRowResult,
44
44
  setInitializingLeadListEnrichmentConcurrencyLock,
45
45
  stableCanonicalJson
46
- } from "./chunk-ABYBPODM.js";
46
+ } from "./chunk-JZPSG4NR.js";
47
47
  import {
48
48
  ANALYTICS_CONTENT_SORTS,
49
49
  AnalyticsRepositoryError,
@@ -108,7 +108,7 @@ import {
108
108
  harvestProblemResponse,
109
109
  publicErrorBoundary,
110
110
  serializeHarvestProblem
111
- } from "./chunk-CETDPNMZ.js";
111
+ } from "./chunk-LVXRWJJJ.js";
112
112
  import "./chunk-OZJMVCDK.js";
113
113
  import {
114
114
  BACKUP_LAST_RESORT_ATTEMPTS,
@@ -127,7 +127,7 @@ import {
127
127
  resolveKernelProxyId,
128
128
  serpArrivalUrl,
129
129
  withRuntimeUserAgent
130
- } from "./chunk-K7Y27ND3.js";
130
+ } from "./chunk-CGOMGGOF.js";
131
131
  import {
132
132
  ApiErrorResponseSchema,
133
133
  ArtifactTemplatePresetKeySchema,
@@ -199,7 +199,7 @@ import {
199
199
  resolveDeploymentProfile,
200
200
  resolveLocalSourcebookSchemaType,
201
201
  transcribeMediaUrl
202
- } from "./chunk-OJFWURGD.js";
202
+ } from "./chunk-Z5QQEWXA.js";
203
203
  import {
204
204
  auditImageUrls,
205
205
  auditImages,
@@ -210,7 +210,7 @@ import {
210
210
  sanitizeAttempts,
211
211
  sanitizeHarvestResult,
212
212
  sanitizeOutboundDiagnostics
213
- } from "./chunk-3EYALCYK.js";
213
+ } from "./chunk-F434YJPV.js";
214
214
  import {
215
215
  browserServiceApiKey,
216
216
  browserServiceProfileName,
@@ -220,7 +220,7 @@ import {
220
220
  recordKernelSession,
221
221
  recordVendorUsage,
222
222
  runWithCostContext
223
- } from "./chunk-3TKKJQB4.js";
223
+ } from "./chunk-U2UHJWFW.js";
224
224
  import {
225
225
  listWorkflowDefinitions,
226
226
  runWorkflow,
@@ -256,7 +256,7 @@ import {
256
256
  } from "./chunk-GGZEC22A.js";
257
257
  import {
258
258
  PACKAGE_VERSION
259
- } from "./chunk-RSFMR7O7.js";
259
+ } from "./chunk-7KB5QTHP.js";
260
260
  import {
261
261
  SITE_EXTRACT_ARTIFACT_PREFIX,
262
262
  abandonExtractSettlement,
@@ -287,7 +287,7 @@ import {
287
287
  setExtractJobTotal,
288
288
  settleExtractJob,
289
289
  terminalExtractJobStatus
290
- } from "./chunk-KXRWY7DZ.js";
290
+ } from "./chunk-HWGQVP3Q.js";
291
291
  import {
292
292
  createPrivateArtifact,
293
293
  privateArtifactOwnerId,
@@ -451,7 +451,7 @@ import {
451
451
  softDeleteInboxMessage,
452
452
  upsertPageSnapshot,
453
453
  verifyPassword
454
- } from "./chunk-55B3T5L2.js";
454
+ } from "./chunk-HZDJ5UZ4.js";
455
455
 
456
456
  // src/blog/registry.ts
457
457
  var posts = [
@@ -9545,7 +9545,7 @@ var siteExtractFn = inngest.createFunction(
9545
9545
  });
9546
9546
  }) : null;
9547
9547
  const finalized = await step.run("finalize", async () => {
9548
- const { assembleExtractArtifacts } = await import("./extract-bundle-EA5URLXN.js");
9548
+ const { assembleExtractArtifacts } = await import("./extract-bundle-RW7CVIRF.js");
9549
9549
  const current = await getExtractJob(jobId2);
9550
9550
  if (!current) throw new Error("extract job disappeared before finalization");
9551
9551
  const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
@@ -10505,7 +10505,7 @@ async function resolveUsZipsPath(requestedPath) {
10505
10505
  async function loadZipGroups(stateAbbr, requestedPath, warnings, sourceMode) {
10506
10506
  const mode = resolvedLocationDataMode({ zipSource: sourceMode });
10507
10507
  if (mode === "hosted") {
10508
- const { getHostedZipGroups } = await import("./location-data-repository-WHHQUCUI.js");
10508
+ const { getHostedZipGroups } = await import("./location-data-repository-EPCNGMOM.js");
10509
10509
  const hosted = await getHostedZipGroups(stateAbbr);
10510
10510
  if (!hosted.dataset) {
10511
10511
  throw new DirectoryLocationError(
@@ -10568,7 +10568,7 @@ async function resolveDirectoryMarkets(options, config = {}) {
10568
10568
  const sourceUrl = censusStateUrl(state.fips);
10569
10569
  const warnings = [];
10570
10570
  if (resolvedLocationDataMode(config) === "hosted") {
10571
- const { queryHostedLocationMarkets } = await import("./location-data-repository-WHHQUCUI.js");
10571
+ const { queryHostedLocationMarkets } = await import("./location-data-repository-EPCNGMOM.js");
10572
10572
  const hosted = await queryHostedLocationMarkets({
10573
10573
  state: state.abbr,
10574
10574
  minPopulation: options.minPopulation,
@@ -12055,7 +12055,7 @@ function summarizePersistedLeadRows(rows, warnings = []) {
12055
12055
  async function settlePersistedLeadListEnrichment(jobId2) {
12056
12056
  const jobs = await listLeadListEnrichmentJobsNeedingSettlement(100);
12057
12057
  const job = jobs.find((candidate) => candidate.id === jobId2);
12058
- const { getLeadListEnrichmentJob: getLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-IBXNWM6H.js");
12058
+ const { getLeadListEnrichmentJob: getLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-PZYE6TDP.js");
12059
12059
  const current = job ?? await getLeadListEnrichmentJob2(jobId2);
12060
12060
  if (!current) return null;
12061
12061
  const rows = await listLeadListEnrichmentRows(jobId2);
@@ -12111,7 +12111,7 @@ async function reconcileLeadListEnrichmentSettlements(limit = 25) {
12111
12111
  const persisted = job.result ?? summarizePersistedLeadRows(rows, job.warnings);
12112
12112
  const status = job.cancelRequestedAt ? "cancelled" : persisted.failedRows > 0 && persisted.completeRows > 0 ? "partial" : persisted.failedRows > 0 ? "cancelled" : persisted.rowsWithContacts > 0 ? "complete" : "empty";
12113
12113
  if (persisted.failedRows > 0 && persisted.completeRows === 0 && !job.cancelRequestedAt) {
12114
- const { failLeadListEnrichmentJob: failLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-IBXNWM6H.js");
12114
+ const { failLeadListEnrichmentJob: failLeadListEnrichmentJob2 } = await import("./lead-list-enrichment-repository-PZYE6TDP.js");
12115
12115
  await failLeadListEnrichmentJob2({
12116
12116
  jobId: job.id,
12117
12117
  errorCode: "lead_list_all_rows_failed",
@@ -16505,6 +16505,350 @@ function findPreferredEntitledSubscription(subscriptions, predicate) {
16505
16505
  return void 0;
16506
16506
  }
16507
16507
 
16508
+ // src/api/service-connections.ts
16509
+ import { randomUUID as randomUUID6 } from "crypto";
16510
+ var schemaReady = null;
16511
+ var schemaDb3 = null;
16512
+ function ensureServiceConnectionsSchema() {
16513
+ const currentDb = getDb();
16514
+ if (schemaReady && schemaDb3 === currentDb) return schemaReady;
16515
+ schemaDb3 = currentDb;
16516
+ schemaReady = (async () => {
16517
+ const db = currentDb;
16518
+ await db.execute(`
16519
+ CREATE TABLE IF NOT EXISTS service_connections (
16520
+ id TEXT PRIMARY KEY,
16521
+ user_id INTEGER NOT NULL REFERENCES users(id),
16522
+ provider_config_key TEXT NOT NULL,
16523
+ provider TEXT,
16524
+ transport TEXT NOT NULL,
16525
+ upstream_connection_id TEXT,
16526
+ label TEXT,
16527
+ user_label TEXT,
16528
+ provider_account_id TEXT,
16529
+ provider_account_email TEXT,
16530
+ provider_account_name TEXT,
16531
+ provider_identity_checked_at TEXT,
16532
+ provider_identity_source_updated_at TEXT,
16533
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending',
16534
+ operational_status TEXT NOT NULL DEFAULT 'unknown',
16535
+ reconnect_required INTEGER NOT NULL DEFAULT 0,
16536
+ actions_enabled INTEGER NOT NULL DEFAULT 0,
16537
+ read_tools_json TEXT NOT NULL DEFAULT '[]',
16538
+ action_tools_json TEXT NOT NULL DEFAULT '[]',
16539
+ tool_revision TEXT,
16540
+ last_checked_at TEXT,
16541
+ last_successful_call_at TEXT,
16542
+ last_failure_at TEXT,
16543
+ last_failure_code TEXT,
16544
+ last_failure_retryable INTEGER,
16545
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
16546
+ vault_name TEXT,
16547
+ table_name TEXT,
16548
+ legacy_owner_identity TEXT,
16549
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
16550
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
16551
+ UNIQUE(user_id, provider_config_key, upstream_connection_id)
16552
+ )
16553
+ `);
16554
+ try {
16555
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
16556
+ } catch {
16557
+ }
16558
+ try {
16559
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
16560
+ } catch {
16561
+ }
16562
+ try {
16563
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
16564
+ } catch {
16565
+ }
16566
+ try {
16567
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
16568
+ } catch {
16569
+ }
16570
+ try {
16571
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
16572
+ } catch {
16573
+ }
16574
+ try {
16575
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
16576
+ } catch {
16577
+ }
16578
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
16579
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
16580
+ await db.execute(`
16581
+ CREATE TABLE IF NOT EXISTS service_connection_health_events (
16582
+ id TEXT PRIMARY KEY,
16583
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
16584
+ operational_status TEXT NOT NULL,
16585
+ failure_code TEXT,
16586
+ retryable INTEGER,
16587
+ evidence_source TEXT NOT NULL,
16588
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
16589
+ )
16590
+ `);
16591
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
16592
+ await db.execute(`
16593
+ CREATE TABLE IF NOT EXISTS service_connection_action_audit (
16594
+ id TEXT PRIMARY KEY,
16595
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
16596
+ user_id INTEGER NOT NULL REFERENCES users(id),
16597
+ tool TEXT NOT NULL,
16598
+ request_id TEXT NOT NULL,
16599
+ status TEXT NOT NULL,
16600
+ error_code TEXT,
16601
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
16602
+ UNIQUE(user_id, request_id)
16603
+ )
16604
+ `);
16605
+ try {
16606
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
16607
+ } catch {
16608
+ }
16609
+ try {
16610
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
16611
+ } catch {
16612
+ }
16613
+ })().catch((error) => {
16614
+ schemaReady = null;
16615
+ schemaDb3 = null;
16616
+ throw error;
16617
+ });
16618
+ return schemaReady;
16619
+ }
16620
+ function parseTools(value) {
16621
+ if (typeof value !== "string") return [];
16622
+ try {
16623
+ const parsed = JSON.parse(value);
16624
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
16625
+ } catch {
16626
+ return [];
16627
+ }
16628
+ }
16629
+ function rowToRecord(row) {
16630
+ return {
16631
+ id: String(row.id),
16632
+ userId: Number(row.user_id),
16633
+ providerConfigKey: String(row.provider_config_key),
16634
+ provider: typeof row.provider === "string" ? row.provider : null,
16635
+ transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
16636
+ upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
16637
+ label: typeof row.label === "string" ? row.label : null,
16638
+ userLabel: typeof row.user_label === "string" ? row.user_label : null,
16639
+ providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
16640
+ providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
16641
+ providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
16642
+ providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
16643
+ providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
16644
+ lifecycleStatus: String(row.lifecycle_status),
16645
+ operationalStatus: String(row.operational_status),
16646
+ reconnectRequired: Number(row.reconnect_required) === 1,
16647
+ actionsEnabled: Number(row.actions_enabled) === 1,
16648
+ readTools: parseTools(row.read_tools_json),
16649
+ actionTools: parseTools(row.action_tools_json),
16650
+ toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
16651
+ lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
16652
+ lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
16653
+ lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
16654
+ lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
16655
+ lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
16656
+ consecutiveFailures: Number(row.consecutive_failures ?? 0),
16657
+ vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
16658
+ tableName: typeof row.table_name === "string" ? row.table_name : null,
16659
+ createdAt: String(row.created_at),
16660
+ updatedAt: String(row.updated_at)
16661
+ };
16662
+ }
16663
+ async function userIdForIdentity(identity) {
16664
+ const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
16665
+ if (!user) throw new Error("service_connection_user_not_found");
16666
+ return Number(user.id);
16667
+ }
16668
+ async function reconcileDiscoveredNangoConnections(identity, discovered) {
16669
+ await ensureServiceConnectionsSchema();
16670
+ const db = getDb();
16671
+ const userId = await userIdForIdentity(identity);
16672
+ for (const connection of discovered) {
16673
+ await db.execute({
16674
+ sql: `
16675
+ INSERT INTO service_connections (
16676
+ id, user_id, provider_config_key, provider, transport, upstream_connection_id,
16677
+ label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
16678
+ ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
16679
+ ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
16680
+ provider = excluded.provider,
16681
+ label = COALESCE(excluded.label, service_connections.label),
16682
+ lifecycle_status = excluded.lifecycle_status,
16683
+ reconnect_required = excluded.reconnect_required,
16684
+ updated_at = excluded.updated_at
16685
+ `,
16686
+ args: [
16687
+ randomUUID6(),
16688
+ userId,
16689
+ connection.providerConfigKey,
16690
+ connection.provider,
16691
+ connection.upstreamConnectionId,
16692
+ connection.label,
16693
+ connection.lifecycleStatus,
16694
+ connection.reconnectRequired ? 1 : 0,
16695
+ identity,
16696
+ connection.createdAt,
16697
+ connection.updatedAt
16698
+ ]
16699
+ });
16700
+ }
16701
+ return listServiceConnections(identity, "nango");
16702
+ }
16703
+ async function listServiceConnections(identity, transport) {
16704
+ await ensureServiceConnectionsSchema();
16705
+ const userId = await userIdForIdentity(identity);
16706
+ const result = await getDb().execute({
16707
+ sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
16708
+ args: transport ? [userId, transport] : [userId]
16709
+ });
16710
+ return result.rows.map((row) => rowToRecord(row));
16711
+ }
16712
+ async function getOwnedServiceConnection(identity, connectionId) {
16713
+ await ensureServiceConnectionsSchema();
16714
+ const userId = await userIdForIdentity(identity);
16715
+ const result = await getDb().execute({
16716
+ sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
16717
+ args: [connectionId, userId]
16718
+ });
16719
+ const row = result.rows[0];
16720
+ return row ? rowToRecord(row) : null;
16721
+ }
16722
+ async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
16723
+ await ensureServiceConnectionsSchema();
16724
+ await getDb().execute({
16725
+ sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
16726
+ args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
16727
+ });
16728
+ }
16729
+ async function setServiceConnectionProviderIdentity(args) {
16730
+ await ensureServiceConnectionsSchema();
16731
+ await getDb().execute({
16732
+ sql: `
16733
+ UPDATE service_connections SET
16734
+ provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
16735
+ provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
16736
+ updated_at = datetime('now')
16737
+ WHERE id = ?
16738
+ `,
16739
+ args: [
16740
+ args.providerAccountId ?? null,
16741
+ args.providerAccountEmail?.toLowerCase() ?? null,
16742
+ args.providerAccountName ?? null,
16743
+ args.sourceUpdatedAt,
16744
+ args.connectionId
16745
+ ]
16746
+ });
16747
+ }
16748
+ async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
16749
+ const connection = await getOwnedServiceConnection(identity, connectionId);
16750
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
16751
+ await getDb().execute({
16752
+ sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
16753
+ args: [userLabel, connectionId, connection.userId]
16754
+ });
16755
+ return userLabel;
16756
+ }
16757
+ async function recordServiceConnectionHealth(args) {
16758
+ await ensureServiceConnectionsSchema();
16759
+ const now = (/* @__PURE__ */ new Date()).toISOString();
16760
+ await getDb().execute({
16761
+ sql: `
16762
+ UPDATE service_connections SET
16763
+ operational_status = ?, last_checked_at = ?,
16764
+ last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
16765
+ last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
16766
+ last_failure_code = ?, last_failure_retryable = ?,
16767
+ consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
16768
+ updated_at = datetime('now')
16769
+ WHERE id = ?
16770
+ `,
16771
+ args: [
16772
+ args.operationalStatus,
16773
+ now,
16774
+ args.operationalStatus,
16775
+ now,
16776
+ args.operationalStatus,
16777
+ now,
16778
+ args.failureCode ?? null,
16779
+ args.retryable == null ? null : args.retryable ? 1 : 0,
16780
+ args.operationalStatus,
16781
+ args.connectionId
16782
+ ]
16783
+ });
16784
+ await getDb().execute({
16785
+ sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
16786
+ args: [randomUUID6(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
16787
+ });
16788
+ }
16789
+ async function setServiceConnectionActions(identity, connectionId, enabled) {
16790
+ const connection = await getOwnedServiceConnection(identity, connectionId);
16791
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
16792
+ await getDb().execute({
16793
+ sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
16794
+ args: [enabled ? 1 : 0, connectionId, connection.userId]
16795
+ });
16796
+ return enabled;
16797
+ }
16798
+ async function markServiceConnectionDisconnected(identity, connectionId) {
16799
+ const connection = await getOwnedServiceConnection(identity, connectionId);
16800
+ if (!connection) throw new Error("service_connection_not_found");
16801
+ await getDb().execute({
16802
+ sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
16803
+ args: [connectionId, connection.userId]
16804
+ });
16805
+ const remaining = await getDb().execute({
16806
+ sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
16807
+ args: [connection.userId]
16808
+ });
16809
+ return Number(remaining.rows[0]?.count ?? 0);
16810
+ }
16811
+ async function recordServiceConnectionAction(args) {
16812
+ await ensureServiceConnectionsSchema();
16813
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
16814
+ if (!connection) throw new Error("service_connection_not_found");
16815
+ await getDb().execute({
16816
+ sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
16817
+ args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
16818
+ });
16819
+ }
16820
+ async function claimServiceConnectionAction(args) {
16821
+ await ensureServiceConnectionsSchema();
16822
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
16823
+ if (!connection) throw new Error("service_connection_not_found");
16824
+ const inserted = await getDb().execute({
16825
+ sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
16826
+ args: [randomUUID6(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
16827
+ });
16828
+ if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
16829
+ const existing = await getDb().execute({
16830
+ 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`,
16831
+ args: [connection.userId, args.requestId]
16832
+ });
16833
+ const row = existing.rows[0];
16834
+ const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
16835
+ let result = void 0;
16836
+ if (!conflict && typeof row?.result_json === "string") {
16837
+ try {
16838
+ result = JSON.parse(row.result_json);
16839
+ } catch {
16840
+ result = void 0;
16841
+ }
16842
+ }
16843
+ return {
16844
+ claimed: false,
16845
+ status: String(row?.status ?? "unknown"),
16846
+ errorCode: typeof row?.error_code === "string" ? row.error_code : null,
16847
+ conflict,
16848
+ ...result !== void 0 ? { result } : {}
16849
+ };
16850
+ }
16851
+
16508
16852
  // src/api/connected-account-billing.ts
16509
16853
  var DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
16510
16854
  function connectedBillingPeriodStart(now = /* @__PURE__ */ new Date()) {
@@ -16521,6 +16865,87 @@ async function chargedConnectionIds(userId, periodStart) {
16521
16865
  });
16522
16866
  return new Set(result.rows.map((row) => String(row.connection_id)));
16523
16867
  }
16868
+ async function inheritDisconnectedConnectionCharge(userId, connectionId, periodStart) {
16869
+ await ensureServiceConnectionsSchema();
16870
+ const normalizedUserId = Number(userId);
16871
+ const db = getDb();
16872
+ await db.batch([
16873
+ {
16874
+ sql: `
16875
+ INSERT OR IGNORE INTO connected_account_charge_transfers (
16876
+ user_id, period_start, from_connection_id, to_connection_id, provider_config_key
16877
+ )
16878
+ SELECT ?, ?, previous.id, replacement.id, replacement.provider_config_key
16879
+ FROM service_connections AS replacement
16880
+ JOIN service_connections AS previous
16881
+ ON previous.user_id = replacement.user_id
16882
+ AND previous.transport = 'nango'
16883
+ AND previous.provider_config_key = replacement.provider_config_key
16884
+ AND previous.lifecycle_status = 'disconnected'
16885
+ JOIN connected_account_charges AS charge
16886
+ ON charge.user_id = previous.user_id
16887
+ AND charge.connection_id = previous.id
16888
+ AND charge.period_start = ?
16889
+ WHERE replacement.id = ?
16890
+ AND replacement.user_id = ?
16891
+ AND replacement.transport = 'nango'
16892
+ AND replacement.lifecycle_status IN ('connected', 'needs_reauth')
16893
+ AND NOT EXISTS (
16894
+ SELECT 1 FROM connected_account_charges AS existing
16895
+ WHERE existing.user_id = replacement.user_id
16896
+ AND existing.connection_id = replacement.id
16897
+ AND existing.period_start = ?
16898
+ )
16899
+ ORDER BY previous.updated_at DESC, previous.id ASC
16900
+ LIMIT 1
16901
+ `,
16902
+ args: [normalizedUserId, periodStart, periodStart, connectionId, normalizedUserId, periodStart]
16903
+ },
16904
+ {
16905
+ sql: `
16906
+ UPDATE connected_account_charges
16907
+ SET connection_id = ?
16908
+ WHERE user_id = ?
16909
+ AND period_start = ?
16910
+ AND connection_id = (
16911
+ SELECT transfer.from_connection_id
16912
+ FROM connected_account_charge_transfers AS transfer
16913
+ JOIN connected_account_charges AS current_charge
16914
+ ON current_charge.user_id = transfer.user_id
16915
+ AND current_charge.period_start = transfer.period_start
16916
+ AND current_charge.connection_id = transfer.from_connection_id
16917
+ WHERE transfer.user_id = ?
16918
+ AND transfer.period_start = ?
16919
+ AND transfer.to_connection_id = ?
16920
+ ORDER BY transfer.transferred_at DESC, transfer.id DESC
16921
+ LIMIT 1
16922
+ )
16923
+ AND NOT EXISTS (
16924
+ SELECT 1 FROM connected_account_charges AS existing
16925
+ WHERE existing.user_id = ?
16926
+ AND existing.connection_id = ?
16927
+ AND existing.period_start = ?
16928
+ )
16929
+ `,
16930
+ args: [
16931
+ connectionId,
16932
+ normalizedUserId,
16933
+ periodStart,
16934
+ normalizedUserId,
16935
+ periodStart,
16936
+ connectionId,
16937
+ normalizedUserId,
16938
+ connectionId,
16939
+ periodStart
16940
+ ]
16941
+ }
16942
+ ], "write");
16943
+ const inherited = await db.execute({
16944
+ sql: "SELECT 1 FROM connected_account_charges WHERE user_id = ? AND connection_id = ? AND period_start = ? LIMIT 1",
16945
+ args: [normalizedUserId, connectionId, periodStart]
16946
+ });
16947
+ return inherited.rows.length > 0;
16948
+ }
16524
16949
  async function getConnectedAccountBillingState(userId) {
16525
16950
  const result = await getDb().execute({
16526
16951
  sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
@@ -16661,6 +17086,7 @@ async function reconcileConnectedAccountBilling(user, connections) {
16661
17086
  const alreadyCharged = await chargedConnectionIds(user.id, periodStart);
16662
17087
  const unpaid = billableConnectionIds.filter((id) => !alreadyCharged.has(id));
16663
17088
  for (const connectionId of unpaid) {
17089
+ if (await inheritDisconnectedConnectionCharge(user.id, connectionId, periodStart)) continue;
16664
17090
  const feeMc = CONNECTED_ACTIVE_CONNECTION_MONTHLY_MC;
16665
17091
  const debit = await debitMcIdempotent(
16666
17092
  user.id,
@@ -19752,7 +20178,7 @@ var adminAuth = createMiddleware3(async (c, next) => {
19752
20178
  import { Hono as Hono9 } from "hono";
19753
20179
 
19754
20180
  // src/api/admin-credits.ts
19755
- import { randomUUID as randomUUID6 } from "crypto";
20181
+ import { randomUUID as randomUUID7 } from "crypto";
19756
20182
  var MIN_ADJUSTMENT_CREDITS = 1;
19757
20183
  var LARGE_ADJUSTMENT_CREDITS = 1e5;
19758
20184
  var MAX_ADJUSTMENT_CREDITS = 1e7;
@@ -19902,7 +20328,7 @@ async function applyAdjustment(input) {
19902
20328
  const credits = validateCredits(input.credits, input.confirmLarge === true);
19903
20329
  const reason = validateReason(input.reason);
19904
20330
  const actor = validateActor(input.actor);
19905
- const reference = input.reference?.trim() || `adj_${randomUUID6()}`;
20331
+ const reference = input.reference?.trim() || `adj_${randomUUID7()}`;
19906
20332
  if (reference.length > 120) throw new AdminCreditError(400, "reference must be at most 120 characters");
19907
20333
  const db = getDb();
19908
20334
  const existing = await db.execute({
@@ -22509,17 +22935,17 @@ g2App.post("/reviews", createApiKeyAuth(), async (c) => {
22509
22935
  });
22510
22936
 
22511
22937
  // src/api/directory-routes.ts
22512
- import { createHash as createHash5, randomUUID as randomUUID8 } from "crypto";
22938
+ import { createHash as createHash5, randomUUID as randomUUID9 } from "crypto";
22513
22939
  import { Hono as Hono16 } from "hono";
22514
22940
 
22515
22941
  // src/api/directory-workflow-dispatch.ts
22516
- import { randomUUID as randomUUID7 } from "crypto";
22942
+ import { randomUUID as randomUUID8 } from "crypto";
22517
22943
  function retryDelaySeconds(attempts) {
22518
22944
  return Math.min(60 * 60, Math.max(15, 15 * 2 ** Math.min(8, Math.max(0, attempts - 1))));
22519
22945
  }
22520
22946
  async function dispatchPendingDirectoryWorkflows(limit = 25) {
22521
22947
  const rows = await claimDirectoryWorkflowOutbox({
22522
- workerId: `directory-dispatch-${process.pid}-${randomUUID7().slice(0, 8)}`,
22948
+ workerId: `directory-dispatch-${process.pid}-${randomUUID8().slice(0, 8)}`,
22523
22949
  limit
22524
22950
  });
22525
22951
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
@@ -22558,14 +22984,14 @@ function idempotencyKey(raw) {
22558
22984
  if (trimmed.length > 500) return { ok: false, message: "Idempotency-Key must be 500 characters or fewer." };
22559
22985
  return { ok: true, key: trimmed };
22560
22986
  }
22561
- return { ok: true, key: `directory-${randomUUID8()}` };
22987
+ return { ok: true, key: `directory-${randomUUID9()}` };
22562
22988
  }
22563
22989
  function debitKeyFor(userId, responseKey) {
22564
22990
  const digest2 = createHash5("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
22565
22991
  return `directory-workflow:${userId}:${digest2}`;
22566
22992
  }
22567
22993
  function jobId() {
22568
- return `dir_${randomUUID8().replace(/-/g, "")}`;
22994
+ return `dir_${randomUUID9().replace(/-/g, "")}`;
22569
22995
  }
22570
22996
  function publicStatus(job, result) {
22571
22997
  if (job.status === "failed") return "failed";
@@ -22879,8 +23305,8 @@ import { homedir as homedir3 } from "os";
22879
23305
  import { join as join7 } from "path";
22880
23306
 
22881
23307
  // src/api/lead-list-import-repository.ts
22882
- import { randomUUID as randomUUID9 } from "crypto";
22883
- var schemaDb3 = null;
23308
+ import { randomUUID as randomUUID10 } from "crypto";
23309
+ var schemaDb4 = null;
22884
23310
  var schemaPromise3 = null;
22885
23311
  function parseJson3(value, fallback) {
22886
23312
  if (value == null) return fallback;
@@ -22945,15 +23371,15 @@ function bounded(value, field, min, max) {
22945
23371
  return normalized;
22946
23372
  }
22947
23373
  function newLeadListUploadId() {
22948
- return `upl_${randomUUID9().replace(/-/g, "")}`;
23374
+ return `upl_${randomUUID10().replace(/-/g, "")}`;
22949
23375
  }
22950
23376
  function newImportedLeadListId() {
22951
- return `lst_${randomUUID9().replace(/-/g, "")}`;
23377
+ return `lst_${randomUUID10().replace(/-/g, "")}`;
22952
23378
  }
22953
23379
  async function ensureLeadListImportRepositorySchema() {
22954
23380
  const db = getDb();
22955
- if (schemaDb3 !== db) {
22956
- schemaDb3 = db;
23381
+ if (schemaDb4 !== db) {
23382
+ schemaDb4 = db;
22957
23383
  schemaPromise3 = null;
22958
23384
  }
22959
23385
  if (!schemaPromise3) {
@@ -24090,17 +24516,17 @@ leadListInputApp.post("/imports", async (c) => {
24090
24516
  });
24091
24517
 
24092
24518
  // src/api/lead-list-enrichment-routes.ts
24093
- import { createHash as createHash8, randomUUID as randomUUID11 } from "crypto";
24519
+ import { createHash as createHash8, randomUUID as randomUUID12 } from "crypto";
24094
24520
  import { Hono as Hono18 } from "hono";
24095
24521
  import { z as z25 } from "zod";
24096
24522
 
24097
24523
  // src/api/lead-list-enrichment-dispatch.ts
24098
- import { randomUUID as randomUUID10 } from "crypto";
24524
+ import { randomUUID as randomUUID11 } from "crypto";
24099
24525
  function retryDelaySeconds2(attempts) {
24100
24526
  return Math.min(3600, Math.max(15, 15 * 2 ** Math.min(8, Math.max(0, attempts - 1))));
24101
24527
  }
24102
24528
  async function dispatchPendingLeadListEnrichments(limit = 25) {
24103
- const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${randomUUID10().slice(0, 8)}`, limit });
24529
+ const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${randomUUID11().slice(0, 8)}`, limit });
24104
24530
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
24105
24531
  for (const row of rows) {
24106
24532
  if (!row.claimToken) continue;
@@ -24148,7 +24574,7 @@ function idempotencyKey2(raw) {
24148
24574
  return { ok: true, value };
24149
24575
  }
24150
24576
  function newJobId() {
24151
- return `lle_${randomUUID11().replace(/-/g, "")}`;
24577
+ return `lle_${randomUUID12().replace(/-/g, "")}`;
24152
24578
  }
24153
24579
  function debitKeyFor2(userId, key) {
24154
24580
  const digest2 = createHash8("sha256").update(String(userId)).update("\0").update(key).digest("hex");
@@ -26158,7 +26584,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
26158
26584
 
26159
26585
  // src/api/serp-intelligence-routes.ts
26160
26586
  import { Hono as Hono22 } from "hono";
26161
- import { createHash as createHash10, randomUUID as randomUUID12 } from "crypto";
26587
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
26162
26588
 
26163
26589
  // src/serp-intelligence/page-snapshot-extractor.ts
26164
26590
  import { createHash as createHash9 } from "crypto";
@@ -26944,7 +27370,7 @@ async function logRequestEventBestEffort(input) {
26944
27370
  }
26945
27371
  }
26946
27372
  function captureBillingKeys(userId, suppliedKey, body) {
26947
- const responseKey = suppliedKey?.trim() || randomUUID12();
27373
+ const responseKey = suppliedKey?.trim() || randomUUID13();
26948
27374
  const requestFingerprint3 = createHash10("sha256").update(JSON.stringify(body)).digest("hex");
26949
27375
  const keyDigest = createHash10("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
26950
27376
  return {
@@ -27274,7 +27700,7 @@ var MemoryMcpToolExecutor = class {
27274
27700
  };
27275
27701
 
27276
27702
  // src/api/image-source-artifacts.ts
27277
- import { randomUUID as randomUUID13 } from "crypto";
27703
+ import { randomUUID as randomUUID14 } from "crypto";
27278
27704
  var IMAGE_SOURCE_ARTIFACT_PREFIX = "image-sources/";
27279
27705
  var IMAGE_SOURCE_ARTIFACT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
27280
27706
  var IMAGE_SOURCE_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
@@ -27305,7 +27731,7 @@ async function createImageSourceArtifact(args) {
27305
27731
  if (args.content.length === 0 || args.content.length > IMAGE_SOURCE_MAX_BYTES) {
27306
27732
  throw new Error("image_source_size_invalid");
27307
27733
  }
27308
- const id = randomUUID13().replaceAll("-", "");
27734
+ const id = randomUUID14().replaceAll("-", "");
27309
27735
  return createPrivateArtifact({
27310
27736
  policy: policy3(),
27311
27737
  ownerId: args.ownerId,
@@ -28050,11 +28476,11 @@ mcpApp.all("/", async (c) => {
28050
28476
  });
28051
28477
 
28052
28478
  // src/api/browser-agent-routes.ts
28053
- import { randomUUID as randomUUID17 } from "crypto";
28479
+ import { randomUUID as randomUUID18 } from "crypto";
28054
28480
  import { Hono as Hono24 } from "hono";
28055
28481
 
28056
28482
  // src/api/browser-agent-db.ts
28057
- import { randomUUID as randomUUID14 } from "crypto";
28483
+ import { randomUUID as randomUUID15 } from "crypto";
28058
28484
  var _ready = false;
28059
28485
  var _migrationPromise = null;
28060
28486
  async function migrateBrowserAgent() {
@@ -28185,7 +28611,7 @@ async function runBrowserAgentMigration() {
28185
28611
  }
28186
28612
  async function createExtensionRow(input) {
28187
28613
  const db = getDb();
28188
- const id = `bext_${randomUUID14().replace(/-/g, "").slice(0, 20)}`;
28614
+ const id = `bext_${randomUUID15().replace(/-/g, "").slice(0, 20)}`;
28189
28615
  await db.execute({
28190
28616
  sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
28191
28617
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -28220,7 +28646,7 @@ async function deleteExtensionRow(userId, name) {
28220
28646
  }
28221
28647
  async function createAuthConnectionRow(input) {
28222
28648
  const db = getDb();
28223
- const connectionId = `authc_${randomUUID14().replace(/-/g, "").slice(0, 20)}`;
28649
+ const connectionId = `authc_${randomUUID15().replace(/-/g, "").slice(0, 20)}`;
28224
28650
  await db.execute({
28225
28651
  sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
28226
28652
  VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
@@ -28305,7 +28731,7 @@ async function deleteProfileLabel(userId, profile) {
28305
28731
  }
28306
28732
  async function createSessionRow(input) {
28307
28733
  const db = getDb();
28308
- const id = `bas_${randomUUID14().replace(/-/g, "").slice(0, 20)}`;
28734
+ const id = `bas_${randomUUID15().replace(/-/g, "").slice(0, 20)}`;
28309
28735
  await db.execute({
28310
28736
  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)
28311
28737
  VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
@@ -28383,7 +28809,7 @@ async function recordAction(input) {
28383
28809
  sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
28384
28810
  VALUES (?, ?, ?, ?, ?, ?)`,
28385
28811
  args: [
28386
- `baa_${randomUUID14().replace(/-/g, "").slice(0, 20)}`,
28812
+ `baa_${randomUUID15().replace(/-/g, "").slice(0, 20)}`,
28387
28813
  input.sessionId,
28388
28814
  input.type,
28389
28815
  input.params == null ? null : JSON.stringify(input.params),
@@ -29494,9 +29920,9 @@ async function replayList(runtimeSessionId) {
29494
29920
  }
29495
29921
 
29496
29922
  // src/api/serp-identity-db.ts
29497
- import { randomUUID as randomUUID15 } from "crypto";
29923
+ import { randomUUID as randomUUID16 } from "crypto";
29498
29924
  async function createSerpIdentityRow(input) {
29499
- const id = `serpi_${randomUUID15().replace(/-/g, "").slice(0, 20)}`;
29925
+ const id = `serpi_${randomUUID16().replace(/-/g, "").slice(0, 20)}`;
29500
29926
  await getDb().execute({
29501
29927
  sql: `INSERT INTO serp_identities
29502
29928
  (id, user_id, name, kernel_profile_name, kernel_proxy_id, proxy_type, country, status)
@@ -29543,7 +29969,7 @@ async function deleteSerpIdentityRow(userId, name) {
29543
29969
  }
29544
29970
 
29545
29971
  // src/api/serp-identity-service.ts
29546
- import { createHash as createHash11, randomUUID as randomUUID16 } from "crypto";
29972
+ import { createHash as createHash11, randomUUID as randomUUID17 } from "crypto";
29547
29973
  import Kernel5 from "@onkernel/sdk";
29548
29974
  var MAX_SERP_IDENTITIES_PER_USER = 3;
29549
29975
  function kernelClient() {
@@ -29553,7 +29979,7 @@ function kernelClient() {
29553
29979
  }
29554
29980
  function backendName(userId, name, resource) {
29555
29981
  const digest2 = createHash11("sha256").update(`${userId}:${name}`).digest("hex").slice(0, 16);
29556
- const nonce = randomUUID16().replace(/-/g, "").slice(0, 8);
29982
+ const nonce = randomUUID17().replace(/-/g, "").slice(0, 8);
29557
29983
  return `mcp-serp-${resource}-${digest2}-${nonce}`;
29558
29984
  }
29559
29985
  function isNotFound2(error) {
@@ -30339,7 +30765,7 @@ function buildBrowserAgentRoutes() {
30339
30765
  }
30340
30766
  const existing = await getExtensionRow(user.id, name);
30341
30767
  if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
30342
- const backendName2 = `u${user.id}_${randomUUID17().replace(/-/g, "")}`;
30768
+ const backendName2 = `u${user.id}_${randomUUID18().replace(/-/g, "")}`;
30343
30769
  try {
30344
30770
  const imported = await importExtensionFromStore(storeUrl, backendName2);
30345
30771
  const row = await createExtensionRow({
@@ -30892,7 +31318,7 @@ async function resolveUser3(customerId, emailFallback) {
30892
31318
  // src/api/oauth-routes.ts
30893
31319
  import { Hono as Hono26 } from "hono";
30894
31320
  import { getCookie, setCookie } from "hono/cookie";
30895
- import { createHash as createHash12, randomBytes as randomBytes3, randomUUID as randomUUID18 } from "crypto";
31321
+ import { createHash as createHash12, randomBytes as randomBytes3, randomUUID as randomUUID19 } from "crypto";
30896
31322
  import { importPKCS8, exportJWK, SignJWT } from "jose";
30897
31323
  var ISSUER = "https://mcpscraper.dev";
30898
31324
  var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
@@ -31277,7 +31703,7 @@ function pkceMatches(verifier, challenge) {
31277
31703
  }
31278
31704
  async function mintAccessToken(identity, scope, plan, audience) {
31279
31705
  const { privateKey, kid } = await getKeys();
31280
- return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(randomUUID18()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
31706
+ return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(randomUUID19()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
31281
31707
  }
31282
31708
  function tokenErrorResponse(c, error, description, status) {
31283
31709
  return c.json({ error, error_description: description }, status);
@@ -33727,7 +34153,7 @@ function renderWorkshopFieldNotesBody() {
33727
34153
  }
33728
34154
 
33729
34155
  // src/api/workshop-registration-repository.ts
33730
- import { randomUUID as randomUUID19 } from "crypto";
34156
+ import { randomUUID as randomUUID20 } from "crypto";
33731
34157
  function rowToRegistration(row) {
33732
34158
  return {
33733
34159
  id: String(row.id),
@@ -33796,7 +34222,7 @@ async function claimWorkshopRegistration(input) {
33796
34222
  ) < ?
33797
34223
  `,
33798
34224
  args: [
33799
- randomUUID19(),
34225
+ randomUUID20(),
33800
34226
  input.eventSlug,
33801
34227
  input.email,
33802
34228
  input.firstName,
@@ -33813,7 +34239,7 @@ async function claimWorkshopRegistration(input) {
33813
34239
  id, event_slug, email, first_name, last_name, status
33814
34240
  ) VALUES (?, ?, ?, ?, ?, 'waitlisted')
33815
34241
  `,
33816
- args: [randomUUID19(), input.eventSlug, input.email, input.firstName, input.lastName || null]
34242
+ args: [randomUUID20(), input.eventSlug, input.email, input.firstName, input.lastName || null]
33817
34243
  });
33818
34244
  const waitlisted = await getWorkshopRegistration(input.eventSlug, input.email);
33819
34245
  if (!waitlisted) throw new Error("workshop registration could not be claimed");
@@ -33828,7 +34254,7 @@ async function registerWorkshopInterest(input) {
33828
34254
  id, event_slug, email, first_name, last_name, status
33829
34255
  ) VALUES (?, ?, ?, ?, ?, 'interested')
33830
34256
  `,
33831
- args: [randomUUID19(), input.eventSlug, input.email, input.firstName, input.lastName || null]
34257
+ args: [randomUUID20(), input.eventSlug, input.email, input.firstName, input.lastName || null]
33832
34258
  });
33833
34259
  const registration = await getWorkshopRegistration(input.eventSlug, input.email);
33834
34260
  if (!registration) throw new Error("workshop interest could not be recorded");
@@ -34631,7 +35057,7 @@ import { Hono as Hono33 } from "hono";
34631
35057
  import { z as z33 } from "zod";
34632
35058
 
34633
35059
  // src/api/commons-repository.ts
34634
- import { createHash as createHash17, randomUUID as randomUUID20 } from "crypto";
35060
+ import { createHash as createHash17, randomUUID as randomUUID21 } from "crypto";
34635
35061
 
34636
35062
  // src/api/commons-image-store.ts
34637
35063
  import { createHash as createHash15 } from "crypto";
@@ -34640,20 +35066,27 @@ import { isIP } from "net";
34640
35066
  var COMMONS_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
34641
35067
  var COMMONS_IMAGE_MAX_REDIRECTS = 3;
34642
35068
  var COMMONS_IMAGE_FETCH_TIMEOUT_MS = 2e4;
35069
+ var 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.";
34643
35070
  var CommonsImageError = class extends Error {
34644
- constructor(code, message, httpStatus = 400) {
35071
+ constructor(code, message, httpStatus = 400, retryable = false, retryGuidance = COMMONS_IMAGE_RETRY_GUIDANCE) {
34645
35072
  super(message);
34646
35073
  this.code = code;
34647
35074
  this.httpStatus = httpStatus;
35075
+ this.retryable = retryable;
35076
+ this.retryGuidance = retryGuidance;
34648
35077
  this.name = "CommonsImageError";
34649
35078
  }
34650
35079
  code;
34651
35080
  httpStatus;
35081
+ retryable;
35082
+ retryGuidance;
34652
35083
  };
34653
35084
  var imageSchemaReady = false;
35085
+ var imageSchemaDb = null;
34654
35086
  async function ensureCommonsImageSchema() {
34655
- if (imageSchemaReady) return;
34656
- await getDb().execute(`
35087
+ const db = getDb();
35088
+ if (imageSchemaReady && imageSchemaDb === db) return;
35089
+ await db.execute(`
34657
35090
  CREATE TABLE IF NOT EXISTS commons_images (
34658
35091
  id TEXT PRIMARY KEY,
34659
35092
  user_id INTEGER NOT NULL,
@@ -34669,8 +35102,9 @@ async function ensureCommonsImageSchema() {
34669
35102
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
34670
35103
  )
34671
35104
  `);
34672
- await getDb().execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
35105
+ await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
34673
35106
  imageSchemaReady = true;
35107
+ imageSchemaDb = db;
34674
35108
  }
34675
35109
  function sniffImageFormat(bytes) {
34676
35110
  if (bytes.length < 12) return null;
@@ -34700,7 +35134,7 @@ async function assertPublicHttpsUrl(rawUrl) {
34700
35134
  try {
34701
35135
  url = new URL(rawUrl);
34702
35136
  } catch {
34703
- throw new CommonsImageError("image_url_invalid", `"${rawUrl}" is not a valid URL.`);
35137
+ throw new CommonsImageError("image_url_invalid", "The image source is not a valid URL.");
34704
35138
  }
34705
35139
  if (url.protocol !== "https:") {
34706
35140
  throw new CommonsImageError("image_url_not_https", "Commons image sources must be https URLs.");
@@ -34708,7 +35142,7 @@ async function assertPublicHttpsUrl(rawUrl) {
34708
35142
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
34709
35143
  const addresses = isIP(hostname) ? [{ address: hostname }] : await dnsLookup(hostname, { all: true }).catch(() => []);
34710
35144
  if (!addresses.length) {
34711
- throw new CommonsImageError("image_host_unresolvable", `Could not resolve ${url.hostname}.`);
35145
+ throw new CommonsImageError("image_host_unresolvable", "The image source host could not be resolved.");
34712
35146
  }
34713
35147
  if (addresses.some((entry) => isBlockedAddress(entry.address))) {
34714
35148
  throw new CommonsImageError("image_host_blocked", "Commons image sources cannot resolve to private or loopback addresses.");
@@ -34719,15 +35153,36 @@ async function downloadImage(sourceUrl) {
34719
35153
  let current = sourceUrl;
34720
35154
  for (let redirect = 0; redirect <= COMMONS_IMAGE_MAX_REDIRECTS; redirect += 1) {
34721
35155
  const url = await assertPublicHttpsUrl(current);
34722
- const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
35156
+ let response;
35157
+ try {
35158
+ response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
35159
+ } catch (error) {
35160
+ if (error instanceof CommonsImageError) throw error;
35161
+ throw new CommonsImageError(
35162
+ "image_source_unavailable",
35163
+ "The image source could not be reached by Commons.",
35164
+ 422,
35165
+ true
35166
+ );
35167
+ }
34723
35168
  if (response.status >= 300 && response.status < 400) {
34724
35169
  const location2 = response.headers.get("location");
34725
35170
  if (!location2) throw new CommonsImageError("image_redirect_invalid", "Image source redirected without a destination.");
34726
- current = new URL(location2, url).toString();
35171
+ try {
35172
+ current = new URL(location2, url).toString();
35173
+ } catch {
35174
+ throw new CommonsImageError("image_redirect_invalid", "Image source redirected to an invalid destination.");
35175
+ }
34727
35176
  continue;
34728
35177
  }
34729
35178
  if (!response.ok) {
34730
- throw new CommonsImageError("image_source_unavailable", `Image source returned HTTP ${response.status}.`, 422);
35179
+ const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
35180
+ throw new CommonsImageError(
35181
+ "image_source_unavailable",
35182
+ `The image source returned HTTP ${response.status} and could not be hosted.`,
35183
+ 422,
35184
+ retryable
35185
+ );
34731
35186
  }
34732
35187
  const declaredLength = Number(response.headers.get("content-length") ?? "0");
34733
35188
  if (declaredLength > COMMONS_IMAGE_MAX_BYTES) {
@@ -34754,8 +35209,35 @@ function commonsImageBlobStore() {
34754
35209
  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();
34755
35210
  return token6 ? new VercelBlobStore(token6) : getBlobStore();
34756
35211
  }
34757
- function isCommonsHostedImageUrl(url) {
34758
- return /^https:\/\/[^/]*(\.public\.blob\.vercel-storage\.com|blob\.vercel-storage\.com)\//i.test(url);
35212
+ function normalizeCommonsImageUrl(rawUrl) {
35213
+ try {
35214
+ const url = new URL(rawUrl.trim());
35215
+ if (url.protocol !== "https:") return null;
35216
+ url.hash = "";
35217
+ return url.toString();
35218
+ } catch {
35219
+ return null;
35220
+ }
35221
+ }
35222
+ async function findCommonsHostedImageByUrl(rawUrl) {
35223
+ const url = normalizeCommonsImageUrl(rawUrl);
35224
+ if (!url) return null;
35225
+ await ensureCommonsImageSchema();
35226
+ const result = await getDb().execute({
35227
+ sql: "SELECT id, url, content_type, bytes, digest, source_url FROM commons_images WHERE url = ? LIMIT 1",
35228
+ args: [url]
35229
+ });
35230
+ const row = result.rows[0];
35231
+ if (!row) return null;
35232
+ return {
35233
+ id: String(row.id),
35234
+ url: String(row.url),
35235
+ contentType: String(row.content_type),
35236
+ bytes: Number(row.bytes),
35237
+ digest: String(row.digest),
35238
+ sourceUrl: row.source_url ? String(row.source_url) : null,
35239
+ reused: true
35240
+ };
34759
35241
  }
34760
35242
  var hostOverride = null;
34761
35243
  async function hostCommonsImage(input) {
@@ -34827,7 +35309,8 @@ async function hostCommonsImage(input) {
34827
35309
  async function hostEntityImages(input) {
34828
35310
  const rewrite = { mediaUrls: /* @__PURE__ */ new Map(), dropped: [] };
34829
35311
  const featuredUrl = input.featuredImage?.url;
34830
- if (featuredUrl && !isCommonsHostedImageUrl(featuredUrl)) {
35312
+ const registeredFeatured = featuredUrl ? await findCommonsHostedImageByUrl(featuredUrl) : null;
35313
+ if (featuredUrl && !registeredFeatured) {
34831
35314
  const hosted = await hostCommonsImage({
34832
35315
  userId: input.userId,
34833
35316
  sourceUrl: featuredUrl,
@@ -34835,17 +35318,19 @@ async function hostEntityImages(input) {
34835
35318
  license: input.featuredImage?.license,
34836
35319
  attribution: input.featuredImage?.sourceUrl
34837
35320
  }).catch((error) => {
34838
- const message = error instanceof Error ? error.message : String(error);
35321
+ if (error instanceof CommonsImageError) throw error;
34839
35322
  throw new CommonsImageError(
34840
- error instanceof CommonsImageError ? error.code : "featured_image_unhostable",
34841
- `The featured image could not be hosted, so this entity would publish with a link that can break: ${message}`,
35323
+ "featured_image_unhostable",
35324
+ "The featured image could not be hosted, so the entity was not persisted with a link that could break.",
34842
35325
  422
34843
35326
  );
34844
35327
  });
34845
35328
  rewrite.featuredImageUrl = hosted.url;
35329
+ rewrite.mediaUrls.set(featuredUrl, hosted.url);
34846
35330
  }
34847
35331
  for (const item of input.media ?? []) {
34848
- if (!item.url || isCommonsHostedImageUrl(item.url)) continue;
35332
+ if (item.url && rewrite.mediaUrls.has(item.url)) continue;
35333
+ if (!item.url || await findCommonsHostedImageByUrl(item.url)) continue;
34849
35334
  try {
34850
35335
  const hosted = await hostCommonsImage({
34851
35336
  userId: input.userId,
@@ -35213,7 +35698,7 @@ var COMMONS_ENTITY_PROFILES = {
35213
35698
  ]
35214
35699
  }
35215
35700
  };
35216
- var schemaReady = false;
35701
+ var schemaReady2 = false;
35217
35702
  var COMMONS_SCHEMA_OBJECTS = [
35218
35703
  "schema_migrations",
35219
35704
  "commons_entities",
@@ -35240,9 +35725,9 @@ var COMMONS_SCHEMA_OBJECTS = [
35240
35725
  "commons_publication_editions_publication_published"
35241
35726
  ];
35242
35727
  async function ensureCommonsSchema() {
35243
- if (schemaReady) return;
35728
+ if (schemaReady2) return;
35244
35729
  if (await schemaObjectsPresent(COMMONS_SCHEMA_OBJECTS)) {
35245
- schemaReady = true;
35730
+ schemaReady2 = true;
35246
35731
  return;
35247
35732
  }
35248
35733
  const db = getDb();
@@ -35465,7 +35950,7 @@ async function ensureCommonsSchema() {
35465
35950
  args: [COMMONS_SCHEMA_VERSION]
35466
35951
  }
35467
35952
  ], "write");
35468
- schemaReady = true;
35953
+ schemaReady2 = true;
35469
35954
  }
35470
35955
  async function searchCommonsEntities(filters = {}, userId) {
35471
35956
  await ensureCommonsSchema();
@@ -35674,7 +36159,7 @@ async function prepareCommonsEntity(input, user) {
35674
36159
  normalizedEntityType: entityType,
35675
36160
  proposedSlug: slug4 || null,
35676
36161
  publicUrl: slug4 ? publicUrlForSlug(slug4) : null,
35677
- writePath: "commons_prepare_entity -> commons_validate_entity -> commons_submit_entity"
36162
+ writePath: "commons_prepare_entity -> compose the entity -> commons_host_image -> commons_validate_entity -> commons_submit_entity"
35678
36163
  },
35679
36164
  contract,
35680
36165
  billing: {
@@ -35696,8 +36181,8 @@ async function prepareCommonsEntity(input, user) {
35696
36181
  "Search likely duplicates before submitting a new entity. If one matches, edit it with entityId and baseRevision instead of creating another page.",
35697
36182
  "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.",
35698
36183
  "Write neutral encyclopedia prose. Do not paste raw scrape Markdown, marketing layout fragments, or empty/filler headings.",
35699
- "Attach a featured image for auto-publish eligibility, preserve original URL/source byline/relCanonical when republishing, and put media assets in the media array.",
35700
- "Call commons_validate_entity with the composed payload before commons_submit_entity.",
36184
+ "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.",
36185
+ "Call commons_validate_entity with the composed, image-ready payload before commons_submit_entity.",
35701
36186
  "Validation is free. Submitting a new entity costs 10 Credits; submitting an edit to an existing entity costs 2 Credits."
35702
36187
  ]
35703
36188
  };
@@ -35712,6 +36197,8 @@ async function validateCommonsEntity(input, user) {
35712
36197
  const contract = commonsEntityProfileFor(input.entityType, input.seo?.schemaOrgType);
35713
36198
  const source = normalizeSource(input.source);
35714
36199
  const featuredImage = normalizeFeaturedImage(input.featuredImage);
36200
+ const normalizedFeaturedImageUrl = featuredImage?.url ? normalizeCommonsImageUrl(featuredImage.url) : null;
36201
+ const registeredFeaturedImage = normalizedFeaturedImageUrl ? await findCommonsHostedImageByUrl(normalizedFeaturedImageUrl) : null;
35715
36202
  const bodyMarkdown = safeCandidateBody(input.bodyMarkdown, input.contentSections);
35716
36203
  const headings = extractMarkdownHeadings(bodyMarkdown);
35717
36204
  const placeholderSections = unsupportedPlaceholderSections(bodyMarkdown);
@@ -35749,15 +36236,44 @@ async function validateCommonsEntity(input, user) {
35749
36236
  if (input.reviewPolicy === "always_review") {
35750
36237
  warnings.push("reviewPolicy is always_review, so the proposal will not auto-publish even if the payload is otherwise valid.");
35751
36238
  }
36239
+ if (featuredImage?.url && !registeredFeaturedImage) {
36240
+ 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.");
36241
+ }
35752
36242
  if (user?.id == null) warnings.push("No authenticated user was supplied to validation; submit still requires API-key authenticated user context.");
35753
36243
  const valid = errors.length === 0;
35754
- const autoPublishEligible = valid && input.reviewPolicy !== "always_review";
36244
+ const imageReadyForPublish = Boolean(registeredFeaturedImage);
36245
+ const publishable = valid && imageReadyForPublish;
36246
+ const autoPublishEligible = publishable && input.reviewPolicy !== "always_review";
35755
36247
  const tagVocabulary = await commonsTagVocabulary();
35756
36248
  return {
35757
36249
  ok: true,
35758
36250
  valid,
35759
- publishable: valid,
36251
+ publishable,
35760
36252
  autoPublishEligible,
36253
+ imageDiagnostics: {
36254
+ featuredImage: registeredFeaturedImage ? {
36255
+ state: "commons_hosted",
36256
+ url: registeredFeaturedImage.url,
36257
+ commonsImageId: registeredFeaturedImage.id,
36258
+ readyForPublish: true,
36259
+ submitWillAttemptAutoHost: false,
36260
+ requiredAction: null
36261
+ } : featuredImage?.url ? {
36262
+ state: "external_unhosted",
36263
+ url: normalizedFeaturedImageUrl,
36264
+ commonsImageId: null,
36265
+ readyForPublish: false,
36266
+ submitWillAttemptAutoHost: true,
36267
+ requiredAction: "Call commons_host_image, replace featuredImage.url with its returned permanent URL, and validate again."
36268
+ } : {
36269
+ state: "missing",
36270
+ url: null,
36271
+ commonsImageId: null,
36272
+ readyForPublish: false,
36273
+ submitWillAttemptAutoHost: false,
36274
+ requiredAction: "Add a featured image by calling commons_host_image, then set featuredImage.url to its returned permanent URL."
36275
+ }
36276
+ },
35761
36277
  errors,
35762
36278
  warnings,
35763
36279
  contract,
@@ -35838,7 +36354,7 @@ async function submitCommonsEntity(input, user) {
35838
36354
  const existingClaims = existing && normalized.claims !== void 0 ? await getCommonsClaimsByEntityId(existing.id) : [];
35839
36355
  const safety = evaluatePublishSafety(normalized, existing);
35840
36356
  const shouldApply = safety.safe && normalized.reviewPolicy !== "always_review";
35841
- const proposalId = `commons-proposal-${randomUUID20()}`;
36357
+ const proposalId = `commons-proposal-${randomUUID21()}`;
35842
36358
  const now = (/* @__PURE__ */ new Date()).toISOString();
35843
36359
  const entityId = existing?.id ?? normalized.entityId ?? allocateEntityId();
35844
36360
  const proposalStatus = shouldApply ? "accepted" : "pending_review";
@@ -35893,7 +36409,7 @@ async function submitCommonsEntity(input, user) {
35893
36409
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
35894
36410
  `,
35895
36411
  args: [
35896
- `commons-ledger-${randomUUID20()}`,
36412
+ `commons-ledger-${randomUUID21()}`,
35897
36413
  nextEntity.id,
35898
36414
  proposalId,
35899
36415
  user.id,
@@ -35999,7 +36515,7 @@ async function saveCommonsFilter(input, user) {
35999
36515
  sql: "SELECT id FROM commons_saved_filters WHERE user_id = ? AND name = ? LIMIT 1",
36000
36516
  args: [user.id, name]
36001
36517
  });
36002
- const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${randomUUID20()}`;
36518
+ const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${randomUUID21()}`;
36003
36519
  await getDb().execute({
36004
36520
  sql: `
36005
36521
  INSERT INTO commons_saved_filters (id, user_id, name, description, filter_json, created_at, updated_at)
@@ -37355,7 +37871,7 @@ var CommonsRepositoryError = class extends Error {
37355
37871
  };
37356
37872
 
37357
37873
  // src/api/commons-publication-repository.ts
37358
- import { createHash as createHash18, randomUUID as randomUUID21 } from "crypto";
37874
+ import { createHash as createHash18, randomUUID as randomUUID22 } from "crypto";
37359
37875
 
37360
37876
  // src/editorial-reading-room/article-page.ts
37361
37877
  function escapeHtml6(value) {
@@ -37568,7 +38084,7 @@ async function claimCommonsPublication(input, user) {
37568
38084
  }
37569
38085
  const existingName = await getCommonsPublicationBySubdomain(subdomain);
37570
38086
  if (existingName) throw new CommonsPublicationError("publication_name_unavailable", "That publication name is already claimed.", 409);
37571
- const id = `tcpub_${randomUUID21()}`;
38087
+ const id = `tcpub_${randomUUID22()}`;
37572
38088
  const now = (/* @__PURE__ */ new Date()).toISOString();
37573
38089
  try {
37574
38090
  await getDb().execute({
@@ -37620,7 +38136,7 @@ async function publishCommonsEditorial(input, user) {
37620
38136
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
37621
38137
  const rendered = renderEditorialReadingRoom(editionInput);
37622
38138
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
37623
- const editionId = `tced_${randomUUID21()}`;
38139
+ const editionId = `tced_${randomUUID22()}`;
37624
38140
  const revision = (latest?.revision ?? 0) + 1;
37625
38141
  const now = (/* @__PURE__ */ new Date()).toISOString();
37626
38142
  await getDb().batch([
@@ -37702,7 +38218,7 @@ async function updateCommonsEditorialArticle(input, user) {
37702
38218
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
37703
38219
  const rendered = renderEditorialReadingRoom(editionInput);
37704
38220
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
37705
- const editionId = `tced_${randomUUID21()}`;
38221
+ const editionId = `tced_${randomUUID22()}`;
37706
38222
  const revision = latest.revision + 1;
37707
38223
  const now = (/* @__PURE__ */ new Date()).toISOString();
37708
38224
  await getDb().batch([
@@ -38236,7 +38752,7 @@ commonsApp.post("/images", auth2, async (c) => {
38236
38752
  return c.json({ ok: true, data: hosted }, hosted.reused ? 200 : 201);
38237
38753
  } catch (error) {
38238
38754
  if (error instanceof CommonsImageError) {
38239
- return c.json({ ok: false, error: error.code, message: error.message }, error.httpStatus);
38755
+ return imageErrorResponse(c, error);
38240
38756
  }
38241
38757
  throw error;
38242
38758
  }
@@ -38300,7 +38816,14 @@ commonsApp.post("/entities/propose", auth2, async (c) => {
38300
38816
  }
38301
38817
  return c.json({ ok: true, data: result, billing: publicListingWriteBilling(billing) }, 201);
38302
38818
  } catch (error) {
38303
- if (billing) await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
38819
+ let chargeStatus = billing ? billing.duplicate ? "not_charged" : "refund_pending" : "not_charged";
38820
+ if (billing && !billing.duplicate) {
38821
+ const settlement = await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
38822
+ if (settlement) chargeStatus = "refunded";
38823
+ }
38824
+ if (error instanceof CommonsImageError) {
38825
+ return imageErrorResponse(c, error, chargeStatus);
38826
+ }
38304
38827
  if (error instanceof CommonsRepositoryError) {
38305
38828
  return c.json({ ok: false, error: error.code, message: error.message }, repositoryStatus(error));
38306
38829
  }
@@ -38433,6 +38956,18 @@ function repositoryStatus(error) {
38433
38956
  if (error.httpStatus === 409) return 409;
38434
38957
  return 400;
38435
38958
  }
38959
+ function imageErrorResponse(c, error, chargeStatus) {
38960
+ return c.json({
38961
+ ok: false,
38962
+ error: error.code,
38963
+ error_code: error.code,
38964
+ error_type: "invalid_request",
38965
+ retryable: error.retryable,
38966
+ ...chargeStatus ? { charge_status: chargeStatus } : {},
38967
+ message: `${error.message} ${error.retryGuidance}`,
38968
+ details: { retry_guidance: error.retryGuidance }
38969
+ }, error.httpStatus);
38970
+ }
38436
38971
  async function publicationOperation(c, operation, successStatus = 200) {
38437
38972
  try {
38438
38973
  return c.json({ ok: true, data: await operation() }, successStatus);
@@ -40172,10 +40707,10 @@ function scheduledArtifactOwnerIdForApiKey(apiKey) {
40172
40707
  }
40173
40708
 
40174
40709
  // src/api/scheduled-run-view-links.ts
40175
- import { createHash as createHash22, randomBytes as randomBytes5, randomUUID as randomUUID22 } from "crypto";
40176
- var schemaReady2 = false;
40710
+ import { createHash as createHash22, randomBytes as randomBytes5, randomUUID as randomUUID23 } from "crypto";
40711
+ var schemaReady3 = false;
40177
40712
  async function ensureScheduledRunViewLinksSchema() {
40178
- if (schemaReady2) return;
40713
+ if (schemaReady3) return;
40179
40714
  const db = getDb();
40180
40715
  await db.execute(`
40181
40716
  CREATE TABLE IF NOT EXISTS scheduled_run_view_links (
@@ -40223,7 +40758,7 @@ async function ensureScheduledRunViewLinksSchema() {
40223
40758
  CREATE INDEX IF NOT EXISTS scheduled_run_view_links_expiry
40224
40759
  ON scheduled_run_view_links(expires_at, revoked_at)
40225
40760
  `);
40226
- schemaReady2 = true;
40761
+ schemaReady3 = true;
40227
40762
  }
40228
40763
  function tokenHash2(token6) {
40229
40764
  return createHash22("sha256").update(token6).digest("hex");
@@ -40248,7 +40783,7 @@ async function createScheduledRunViewLink(input) {
40248
40783
  const now = input.now ?? /* @__PURE__ */ new Date();
40249
40784
  const token6 = randomBytes5(32).toString("base64url");
40250
40785
  const record = {
40251
- shareId: randomUUID22(),
40786
+ shareId: randomUUID23(),
40252
40787
  ownerId: input.ownerId,
40253
40788
  runId: input.runId,
40254
40789
  artifactId: input.artifactId,
@@ -41116,352 +41651,6 @@ import { Hono as Hono37 } from "hono";
41116
41651
 
41117
41652
  // src/api/scheduler-integration-auth.ts
41118
41653
  import { createHash as createHash28, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
41119
-
41120
- // src/api/service-connections.ts
41121
- import { randomUUID as randomUUID23 } from "crypto";
41122
- var schemaReady3 = null;
41123
- var schemaDb4 = null;
41124
- function ensureServiceConnectionsSchema() {
41125
- const currentDb = getDb();
41126
- if (schemaReady3 && schemaDb4 === currentDb) return schemaReady3;
41127
- schemaDb4 = currentDb;
41128
- schemaReady3 = (async () => {
41129
- const db = currentDb;
41130
- await db.execute(`
41131
- CREATE TABLE IF NOT EXISTS service_connections (
41132
- id TEXT PRIMARY KEY,
41133
- user_id INTEGER NOT NULL REFERENCES users(id),
41134
- provider_config_key TEXT NOT NULL,
41135
- provider TEXT,
41136
- transport TEXT NOT NULL,
41137
- upstream_connection_id TEXT,
41138
- label TEXT,
41139
- user_label TEXT,
41140
- provider_account_id TEXT,
41141
- provider_account_email TEXT,
41142
- provider_account_name TEXT,
41143
- provider_identity_checked_at TEXT,
41144
- provider_identity_source_updated_at TEXT,
41145
- lifecycle_status TEXT NOT NULL DEFAULT 'pending',
41146
- operational_status TEXT NOT NULL DEFAULT 'unknown',
41147
- reconnect_required INTEGER NOT NULL DEFAULT 0,
41148
- actions_enabled INTEGER NOT NULL DEFAULT 0,
41149
- read_tools_json TEXT NOT NULL DEFAULT '[]',
41150
- action_tools_json TEXT NOT NULL DEFAULT '[]',
41151
- tool_revision TEXT,
41152
- last_checked_at TEXT,
41153
- last_successful_call_at TEXT,
41154
- last_failure_at TEXT,
41155
- last_failure_code TEXT,
41156
- last_failure_retryable INTEGER,
41157
- consecutive_failures INTEGER NOT NULL DEFAULT 0,
41158
- vault_name TEXT,
41159
- table_name TEXT,
41160
- legacy_owner_identity TEXT,
41161
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
41162
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
41163
- UNIQUE(user_id, provider_config_key, upstream_connection_id)
41164
- )
41165
- `);
41166
- try {
41167
- await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
41168
- } catch {
41169
- }
41170
- try {
41171
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
41172
- } catch {
41173
- }
41174
- try {
41175
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
41176
- } catch {
41177
- }
41178
- try {
41179
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
41180
- } catch {
41181
- }
41182
- try {
41183
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
41184
- } catch {
41185
- }
41186
- try {
41187
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
41188
- } catch {
41189
- }
41190
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
41191
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
41192
- await db.execute(`
41193
- CREATE TABLE IF NOT EXISTS service_connection_health_events (
41194
- id TEXT PRIMARY KEY,
41195
- connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
41196
- operational_status TEXT NOT NULL,
41197
- failure_code TEXT,
41198
- retryable INTEGER,
41199
- evidence_source TEXT NOT NULL,
41200
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
41201
- )
41202
- `);
41203
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
41204
- await db.execute(`
41205
- CREATE TABLE IF NOT EXISTS service_connection_action_audit (
41206
- id TEXT PRIMARY KEY,
41207
- connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
41208
- user_id INTEGER NOT NULL REFERENCES users(id),
41209
- tool TEXT NOT NULL,
41210
- request_id TEXT NOT NULL,
41211
- status TEXT NOT NULL,
41212
- error_code TEXT,
41213
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
41214
- UNIQUE(user_id, request_id)
41215
- )
41216
- `);
41217
- try {
41218
- await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
41219
- } catch {
41220
- }
41221
- try {
41222
- await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
41223
- } catch {
41224
- }
41225
- })().catch((error) => {
41226
- schemaReady3 = null;
41227
- schemaDb4 = null;
41228
- throw error;
41229
- });
41230
- return schemaReady3;
41231
- }
41232
- function parseTools(value) {
41233
- if (typeof value !== "string") return [];
41234
- try {
41235
- const parsed = JSON.parse(value);
41236
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
41237
- } catch {
41238
- return [];
41239
- }
41240
- }
41241
- function rowToRecord(row) {
41242
- return {
41243
- id: String(row.id),
41244
- userId: Number(row.user_id),
41245
- providerConfigKey: String(row.provider_config_key),
41246
- provider: typeof row.provider === "string" ? row.provider : null,
41247
- transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
41248
- upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
41249
- label: typeof row.label === "string" ? row.label : null,
41250
- userLabel: typeof row.user_label === "string" ? row.user_label : null,
41251
- providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
41252
- providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
41253
- providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
41254
- providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
41255
- providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
41256
- lifecycleStatus: String(row.lifecycle_status),
41257
- operationalStatus: String(row.operational_status),
41258
- reconnectRequired: Number(row.reconnect_required) === 1,
41259
- actionsEnabled: Number(row.actions_enabled) === 1,
41260
- readTools: parseTools(row.read_tools_json),
41261
- actionTools: parseTools(row.action_tools_json),
41262
- toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
41263
- lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
41264
- lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
41265
- lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
41266
- lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
41267
- lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
41268
- consecutiveFailures: Number(row.consecutive_failures ?? 0),
41269
- vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
41270
- tableName: typeof row.table_name === "string" ? row.table_name : null,
41271
- createdAt: String(row.created_at),
41272
- updatedAt: String(row.updated_at)
41273
- };
41274
- }
41275
- async function userIdForIdentity(identity) {
41276
- const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
41277
- if (!user) throw new Error("service_connection_user_not_found");
41278
- return Number(user.id);
41279
- }
41280
- async function reconcileDiscoveredNangoConnections(identity, discovered) {
41281
- await ensureServiceConnectionsSchema();
41282
- const db = getDb();
41283
- const userId = await userIdForIdentity(identity);
41284
- for (const connection of discovered) {
41285
- await db.execute({
41286
- sql: `
41287
- INSERT INTO service_connections (
41288
- id, user_id, provider_config_key, provider, transport, upstream_connection_id,
41289
- label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
41290
- ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
41291
- ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
41292
- provider = excluded.provider,
41293
- label = COALESCE(excluded.label, service_connections.label),
41294
- lifecycle_status = excluded.lifecycle_status,
41295
- reconnect_required = excluded.reconnect_required,
41296
- updated_at = excluded.updated_at
41297
- `,
41298
- args: [
41299
- randomUUID23(),
41300
- userId,
41301
- connection.providerConfigKey,
41302
- connection.provider,
41303
- connection.upstreamConnectionId,
41304
- connection.label,
41305
- connection.lifecycleStatus,
41306
- connection.reconnectRequired ? 1 : 0,
41307
- identity,
41308
- connection.createdAt,
41309
- connection.updatedAt
41310
- ]
41311
- });
41312
- }
41313
- return listServiceConnections(identity, "nango");
41314
- }
41315
- async function listServiceConnections(identity, transport) {
41316
- await ensureServiceConnectionsSchema();
41317
- const userId = await userIdForIdentity(identity);
41318
- const result = await getDb().execute({
41319
- sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
41320
- args: transport ? [userId, transport] : [userId]
41321
- });
41322
- return result.rows.map((row) => rowToRecord(row));
41323
- }
41324
- async function getOwnedServiceConnection(identity, connectionId) {
41325
- await ensureServiceConnectionsSchema();
41326
- const userId = await userIdForIdentity(identity);
41327
- const result = await getDb().execute({
41328
- sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
41329
- args: [connectionId, userId]
41330
- });
41331
- const row = result.rows[0];
41332
- return row ? rowToRecord(row) : null;
41333
- }
41334
- async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
41335
- await ensureServiceConnectionsSchema();
41336
- await getDb().execute({
41337
- sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
41338
- args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
41339
- });
41340
- }
41341
- async function setServiceConnectionProviderIdentity(args) {
41342
- await ensureServiceConnectionsSchema();
41343
- await getDb().execute({
41344
- sql: `
41345
- UPDATE service_connections SET
41346
- provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
41347
- provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
41348
- updated_at = datetime('now')
41349
- WHERE id = ?
41350
- `,
41351
- args: [
41352
- args.providerAccountId ?? null,
41353
- args.providerAccountEmail?.toLowerCase() ?? null,
41354
- args.providerAccountName ?? null,
41355
- args.sourceUpdatedAt,
41356
- args.connectionId
41357
- ]
41358
- });
41359
- }
41360
- async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
41361
- const connection = await getOwnedServiceConnection(identity, connectionId);
41362
- if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
41363
- await getDb().execute({
41364
- sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
41365
- args: [userLabel, connectionId, connection.userId]
41366
- });
41367
- return userLabel;
41368
- }
41369
- async function recordServiceConnectionHealth(args) {
41370
- await ensureServiceConnectionsSchema();
41371
- const now = (/* @__PURE__ */ new Date()).toISOString();
41372
- await getDb().execute({
41373
- sql: `
41374
- UPDATE service_connections SET
41375
- operational_status = ?, last_checked_at = ?,
41376
- last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
41377
- last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
41378
- last_failure_code = ?, last_failure_retryable = ?,
41379
- consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
41380
- updated_at = datetime('now')
41381
- WHERE id = ?
41382
- `,
41383
- args: [
41384
- args.operationalStatus,
41385
- now,
41386
- args.operationalStatus,
41387
- now,
41388
- args.operationalStatus,
41389
- now,
41390
- args.failureCode ?? null,
41391
- args.retryable == null ? null : args.retryable ? 1 : 0,
41392
- args.operationalStatus,
41393
- args.connectionId
41394
- ]
41395
- });
41396
- await getDb().execute({
41397
- sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
41398
- args: [randomUUID23(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
41399
- });
41400
- }
41401
- async function setServiceConnectionActions(identity, connectionId, enabled) {
41402
- const connection = await getOwnedServiceConnection(identity, connectionId);
41403
- if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
41404
- await getDb().execute({
41405
- sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
41406
- args: [enabled ? 1 : 0, connectionId, connection.userId]
41407
- });
41408
- return enabled;
41409
- }
41410
- async function markServiceConnectionDisconnected(identity, connectionId) {
41411
- const connection = await getOwnedServiceConnection(identity, connectionId);
41412
- if (!connection) throw new Error("service_connection_not_found");
41413
- await getDb().execute({
41414
- sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
41415
- args: [connectionId, connection.userId]
41416
- });
41417
- const remaining = await getDb().execute({
41418
- sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
41419
- args: [connection.userId]
41420
- });
41421
- return Number(remaining.rows[0]?.count ?? 0);
41422
- }
41423
- async function recordServiceConnectionAction(args) {
41424
- await ensureServiceConnectionsSchema();
41425
- const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
41426
- if (!connection) throw new Error("service_connection_not_found");
41427
- await getDb().execute({
41428
- sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
41429
- args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
41430
- });
41431
- }
41432
- async function claimServiceConnectionAction(args) {
41433
- await ensureServiceConnectionsSchema();
41434
- const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
41435
- if (!connection) throw new Error("service_connection_not_found");
41436
- const inserted = await getDb().execute({
41437
- sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
41438
- args: [randomUUID23(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
41439
- });
41440
- if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
41441
- const existing = await getDb().execute({
41442
- 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`,
41443
- args: [connection.userId, args.requestId]
41444
- });
41445
- const row = existing.rows[0];
41446
- const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
41447
- let result = void 0;
41448
- if (!conflict && typeof row?.result_json === "string") {
41449
- try {
41450
- result = JSON.parse(row.result_json);
41451
- } catch {
41452
- result = void 0;
41453
- }
41454
- }
41455
- return {
41456
- claimed: false,
41457
- status: String(row?.status ?? "unknown"),
41458
- errorCode: typeof row?.error_code === "string" ? row.error_code : null,
41459
- conflict,
41460
- ...result !== void 0 ? { result } : {}
41461
- };
41462
- }
41463
-
41464
- // src/api/scheduler-integration-auth.ts
41465
41654
  var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
41466
41655
  var SchedulerIntegrationAuthError = class extends Error {
41467
41656
  code;
@@ -44203,6 +44392,7 @@ function connectedToolPolicyOrThrow(registry, toolName) {
44203
44392
  // src/api/main-nango-transport.ts
44204
44393
  var DEFAULT_NANGO_MCP_URL = "https://api.nango.dev/mcp";
44205
44394
  var NANGO_TIMEOUT_MS = 2e4;
44395
+ var NANGO_TOOL_CALL_TIMEOUT_MS = 24e4;
44206
44396
  var NANGO_CONNECTION_PAGE_SIZE = 100;
44207
44397
  var NANGO_CONNECTION_MAX_PAGES = 20;
44208
44398
  var PROVIDER_IDENTITY_RETRY_MS = 6 * 60 * 60 * 1e3;
@@ -44241,6 +44431,15 @@ function retryAfterMsFromText(value) {
44241
44431
  function normalizeNangoClientError(error) {
44242
44432
  if (error instanceof MainNangoTransportError) return error;
44243
44433
  const detail = nangoClientErrorText(error);
44434
+ const structured = error && typeof error === "object" ? error : null;
44435
+ if (structured?.code === -32001 || structured?.name === "RequestTimeout" || /Request timed out|RequestTimeout|Maximum total timeout exceeded/i.test(detail)) {
44436
+ return new MainNangoTransportError(
44437
+ "The connected service operation timed out before returning a result. Retry failed items or use a smaller batch.",
44438
+ 503,
44439
+ "mcp_request_timeout",
44440
+ true
44441
+ );
44442
+ }
44244
44443
  if (/SERVICE_DISABLED|accessNotConfigured/i.test(detail)) {
44245
44444
  return new MainNangoTransportError(
44246
44445
  "The provider API required by this connection is disabled. Enable it in the provider project, wait for propagation, then retry.",
@@ -44271,6 +44470,12 @@ function normalizeNangoClientError(error) {
44271
44470
  }
44272
44471
  return new MainNangoTransportError("The service connection is temporarily unavailable.", 503, "connection_transport_unavailable");
44273
44472
  }
44473
+ async function callNangoToolWithBudget(client2, tool, input) {
44474
+ return client2.callTool(
44475
+ { name: tool, arguments: input },
44476
+ { timeout: NANGO_TOOL_CALL_TIMEOUT_MS }
44477
+ );
44478
+ }
44274
44479
  function asRecord(value) {
44275
44480
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
44276
44481
  }
@@ -44730,7 +44935,7 @@ async function callNangoToolDirect(args) {
44730
44935
  let result;
44731
44936
  let providerError;
44732
44937
  try {
44733
- result = await withNangoClient(connection, (client2) => client2.callTool({ name: args.tool, arguments: args.input }));
44938
+ result = await withNangoClient(connection, (client2) => callNangoToolWithBudget(client2, args.tool, args.input));
44734
44939
  } catch (error) {
44735
44940
  providerError = error;
44736
44941
  }
@@ -49691,7 +49896,7 @@ app.get("/cron/tick", async (c) => {
49691
49896
  return c.json({ error: "Unauthorized" }, 401);
49692
49897
  }
49693
49898
  const startedAt = Date.now();
49694
- const { drainQueue } = await import("./worker-UTBUWEXQ.js");
49899
+ const { drainQueue } = await import("./worker-IBLGV272.js");
49695
49900
  const budget = { maxJobs: 10, deadlineMs: startedAt + CRON_TICK_DRAIN_BUDGET_MS };
49696
49901
  const origin = `${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`;
49697
49902
  const unfinished = [];
@@ -49769,8 +49974,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
49769
49974
  finishExtractJob: finishExtractJob2,
49770
49975
  terminalExtractJobStatus: terminalExtractJobStatus2,
49771
49976
  extractJobLimitInfo: extractJobLimitInfo2
49772
- } = await import("./site-extract-repository-NTM4MDQN.js");
49773
- const { assembleExtractArtifacts } = await import("./extract-bundle-EA5URLXN.js");
49977
+ } = await import("./site-extract-repository-CZ7SVT3Z.js");
49978
+ const { assembleExtractArtifacts } = await import("./extract-bundle-RW7CVIRF.js");
49774
49979
  const existing = await getExtractJob2(jobId2);
49775
49980
  if (!existing) return c.json({ error: "job not found" }, 404);
49776
49981
  if (existing.status !== "failed") {
@@ -49797,7 +50002,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
49797
50002
  if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
49798
50003
  let settlement = "already_settled_or_refunded";
49799
50004
  if (job.billedMc == null && job.userId != null) {
49800
- const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-NTM4MDQN.js");
50005
+ const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-CZ7SVT3Z.js");
49801
50006
  const heldMc = Number(job.options.heldMc ?? 0);
49802
50007
  const successful = await countSuccessfulPages2(jobId2);
49803
50008
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);