@burdenoff/microfe-subscriberbot 2026.1030.4 → 2026.1030.6

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 (26) hide show
  1. package/dist/subscriberbot/components/decisions/DecisionCard.d.ts.map +1 -1
  2. package/dist/subscriberbot/components/decisions/DecisionCard.js +1 -0
  3. package/dist/subscriberbot/components/decisions/DecisionCard.js.map +1 -1
  4. package/dist/subscriberbot/components/decisions/decisionModel.d.ts +3 -2
  5. package/dist/subscriberbot/components/decisions/decisionModel.d.ts.map +1 -1
  6. package/dist/subscriberbot/components/decisions/decisionModel.js +18 -5
  7. package/dist/subscriberbot/components/decisions/decisionModel.js.map +1 -1
  8. package/dist/subscriberbot/components/savings/SavingsCards.d.ts +2 -2
  9. package/dist/subscriberbot/components/savings/SavingsCards.d.ts.map +1 -1
  10. package/dist/subscriberbot/components/savings/SavingsCards.js +128 -104
  11. package/dist/subscriberbot/components/savings/SavingsCards.js.map +1 -1
  12. package/dist/subscriberbot/components/savings/savingsModel.d.ts +14 -9
  13. package/dist/subscriberbot/components/savings/savingsModel.d.ts.map +1 -1
  14. package/dist/subscriberbot/components/savings/savingsModel.js +9 -7
  15. package/dist/subscriberbot/components/savings/savingsModel.js.map +1 -1
  16. package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
  17. package/dist/subscriberbot/hooks/useSubscriberBotApi.js +1 -1
  18. package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
  19. package/dist/subscriberbot/pages/SavingsPage.js +1 -1
  20. package/dist/subscriberbot/pages/SavingsPage.js.map +1 -1
  21. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -1
  22. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +292 -301
  23. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
  24. package/dist/subscriberbot/types/index.d.ts +28 -17
  25. package/dist/subscriberbot/types/index.d.ts.map +1 -1
  26. package/package.json +1 -1
@@ -71,10 +71,12 @@ function o(e) {
71
71
  function s(e) {
72
72
  return Math.round(e / 12 * 100) / 100;
73
73
  }
74
- function c(e, t = e.keepRelationshipId) {
74
+ function c(e) {
75
+ let t = new Set(e.dropRelationshipIds);
75
76
  return {
76
- keep: e.relationships.find((e) => e.id === t) ?? null,
77
- cancel: e.relationships.filter((e) => e.id !== t)
77
+ keep: e.members.find((t) => t.relationshipId === e.keepRelationshipId) ?? null,
78
+ cancel: e.members.filter((e) => t.has(e.relationshipId)),
79
+ untouched: e.members.filter((n) => n.relationshipId !== e.keepRelationshipId && !t.has(n.relationshipId))
78
80
  };
79
81
  }
80
82
  var l = {
@@ -87,10 +89,10 @@ var l = {
87
89
  function u(e, t) {
88
90
  return Math.round(e * (l[t] ?? 0) * 100) / 100;
89
91
  }
90
- function d(e, t, n) {
91
- let { keep: r } = c(e, t);
92
- if (!r) return 0;
93
- let i = e.relationships.reduce((e, t) => e + u(t.amount, t.interval), 0), a = n ? u(n.amount, n.interval) : u(r.amount, r.interval);
92
+ function d(e, t) {
93
+ let { keep: n, cancel: r } = c(e);
94
+ if (!n) return 0;
95
+ let i = [n, ...r].reduce((e, t) => e + u(t.amount, t.interval), 0), a = t ? u(t.amount, t.interval) : u(n.amount, n.interval);
94
96
  return Math.round((i - a) * 100) / 100;
95
97
  }
96
98
  function f(e) {
@@ -1 +1 @@
1
- {"version":3,"file":"savingsModel.js","names":[],"sources":["../../../../src/subscriberbot/components/savings/savingsModel.ts"],"sourcesContent":["/**\n * Savings dashboard model (BOFF-4001)\n * @module subscriberbot/components/savings\n *\n * Pure helpers behind `/subscriberbot/savings`: how open decisions are grouped\n * into savings sections, how multi-currency totals read, and how a bundle's\n * consolidation is described. No data lives here; everything is computed from\n * the `subSavingsOverview` and `subListDecisions` responses.\n */\n\nimport type {\n SubBundleRecommendation,\n SubDecision,\n SubDecisionRecommendation,\n SubSavingsTotal,\n} from '../../types';\n\n/** The savings sections, in the order the dashboard shows them. */\nexport type SavingsSectionId = 'cancel' | 'downgrade' | 'annual' | 'other';\n\nexport const SAVINGS_SECTIONS: Array<{\n id: SavingsSectionId;\n key: string;\n fallback: string;\n descriptionKey: string;\n descriptionFallback: string;\n}> = [\n {\n id: 'cancel',\n key: 'savings.sections.cancel',\n fallback: 'Cancel unused',\n descriptionKey: 'savings.sections.cancelDescription',\n descriptionFallback:\n 'Subscriptions you pay for but no longer use, and trials about to charge you.',\n },\n {\n id: 'downgrade',\n key: 'savings.sections.downgrade',\n fallback: 'Downgrade plan',\n descriptionKey: 'savings.sections.downgradeDescription',\n descriptionFallback: 'A cheaper plan from the same provider covers what you use.',\n },\n {\n id: 'annual',\n key: 'savings.sections.annual',\n fallback: 'Switch to annual',\n descriptionKey: 'savings.sections.annualDescription',\n descriptionFallback: 'The same plan costs less when billed yearly.',\n },\n {\n id: 'other',\n key: 'savings.sections.other',\n fallback: 'Other recommendations',\n descriptionKey: 'savings.sections.otherDescription',\n descriptionFallback: 'Pause, keep after a trial, or let it renew.',\n },\n];\n\nconst SECTION_OF: Record<SubDecisionRecommendation, SavingsSectionId> = {\n CANCEL: 'cancel',\n DOWNGRADE: 'downgrade',\n SWITCH_TO_ANNUAL: 'annual',\n PAUSE: 'other',\n CONVERT_TRIAL: 'other',\n RENEW: 'other',\n};\n\nexport function sectionOf(recommendation: SubDecisionRecommendation): SavingsSectionId {\n return SECTION_OF[recommendation];\n}\n\n/**\n * Open decisions grouped into sections, biggest saving first within each.\n * Decisions that are not PROPOSED are left out: only open advice saves money.\n */\nexport function groupDecisions(\n decisions: readonly SubDecision[]\n): Record<SavingsSectionId, SubDecision[]> {\n const groups: Record<SavingsSectionId, SubDecision[]> = {\n cancel: [],\n downgrade: [],\n annual: [],\n other: [],\n };\n for (const decision of decisions) {\n if (decision.status !== 'PROPOSED') continue;\n groups[sectionOf(decision.recommendation)].push(decision);\n }\n for (const list of Object.values(groups)) {\n list.sort(\n (a, b) => b.estimatedSavings - a.estimatedSavings || a.createdAt.localeCompare(b.createdAt)\n );\n }\n return groups;\n}\n\n/** Sum of estimated savings per currency, for a section header. */\nexport function savingsByCurrency(\n decisions: readonly Pick<SubDecision, 'estimatedSavings' | 'currency'>[]\n): Array<{ currency: string; amount: number }> {\n const sums = new Map<string, number>();\n for (const d of decisions) {\n if (!(d.estimatedSavings > 0)) continue;\n sums.set(d.currency, (sums.get(d.currency) ?? 0) + d.estimatedSavings);\n }\n return [...sums.entries()]\n .map(([currency, amount]) => ({ currency, amount: Math.round(amount * 100) / 100 }))\n .sort((a, b) => b.amount - a.amount || a.currency.localeCompare(b.currency));\n}\n\n/** Join per-currency amounts with a formatter: \"$480.00 + €50.00\". Empty list → null. */\nexport function joinAmounts(\n amounts: ReadonlyArray<{ currency: string; amount: number }>,\n format: (amount: number, currency: string) => string\n): string | null {\n const parts = amounts.filter((a) => a.amount > 0).map((a) => format(a.amount, a.currency));\n return parts.length > 0 ? parts.join(' + ') : null;\n}\n\n/** Headline totals as amounts (yearly). */\nexport function headlineAmounts(\n totals: readonly SubSavingsTotal[]\n): Array<{ currency: string; amount: number }> {\n return totals.map((t) => ({ currency: t.currency, amount: t.annualSavings }));\n}\n\n/** Monthly equivalent of a yearly amount, for the \"per month\" sub-line. */\nexport function perMonth(annual: number): number {\n return Math.round((annual / 12) * 100) / 100;\n}\n\n/** The kept subscription and the ones a consolidation would cancel, for a bundle. */\nexport function bundleMembers(\n bundle: SubBundleRecommendation,\n keepId: string = bundle.keepRelationshipId\n): {\n keep: SubBundleRecommendation['relationships'][number] | null;\n cancel: SubBundleRecommendation['relationships'];\n} {\n const keep = bundle.relationships.find((r) => r.id === keepId) ?? null;\n const cancel = bundle.relationships.filter((r) => r.id !== keepId);\n return { keep, cancel };\n}\n\nconst PERIODS_PER_YEAR: Record<string, number> = {\n DAILY: 365,\n WEEKLY: 52,\n MONTHLY: 12,\n QUARTERLY: 4,\n YEARLY: 1,\n};\n\n/** Yearly cost of an amount billed every `interval`; 0 for non-recurring intervals. */\nexport function annualCost(amount: number, interval: string): number {\n return Math.round(amount * (PERIODS_PER_YEAR[interval] ?? 0) * 100) / 100;\n}\n\n/**\n * Yearly saving of a consolidation the user is about to confirm: the yearly\n * cost of every member today minus the kept subscription afterwards (at the\n * bundled price when one is entered).\n */\nexport function projectedConsolidationSavings(\n bundle: SubBundleRecommendation,\n keepId: string,\n bundledPlan?: { amount: number; interval: string } | null\n): number {\n const { keep } = bundleMembers(bundle, keepId);\n if (!keep) return 0;\n const before = bundle.relationships.reduce((sum, r) => sum + annualCost(r.amount, r.interval), 0);\n const after = bundledPlan\n ? annualCost(bundledPlan.amount, bundledPlan.interval)\n : annualCost(keep.amount, keep.interval);\n return Math.round((before - after) * 100) / 100;\n}\n\n/** Parse the optional bundled-plan price field: blank → null, invalid → NaN. */\nexport function parseBundledAmount(raw: string): number | null {\n const trimmed = raw.trim();\n if (trimmed === '') return null;\n const value = Number(trimmed);\n return Number.isFinite(value) && value >= 0 ? value : Number.NaN;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"savingsModel.js","names":[],"sources":["../../../../src/subscriberbot/components/savings/savingsModel.ts"],"sourcesContent":["/**\n * Savings dashboard model (BOFF-4001)\n * @module subscriberbot/components/savings\n *\n * Pure helpers behind `/subscriberbot/savings`: how open decisions are grouped\n * into savings sections, how multi-currency totals read, and how a bundle's\n * consolidation is described. No data lives here; everything is computed from\n * the `subSavingsOverview` and `subListDecisions` responses.\n */\n\nimport type {\n SubOverlap,\n SubOverlapMember,\n SubDecision,\n SubDecisionRecommendation,\n SubSavingsTotal,\n} from '../../types';\n\n/** The savings sections, in the order the dashboard shows them. */\nexport type SavingsSectionId = 'cancel' | 'downgrade' | 'annual' | 'other';\n\nexport const SAVINGS_SECTIONS: Array<{\n id: SavingsSectionId;\n key: string;\n fallback: string;\n descriptionKey: string;\n descriptionFallback: string;\n}> = [\n {\n id: 'cancel',\n key: 'savings.sections.cancel',\n fallback: 'Cancel unused',\n descriptionKey: 'savings.sections.cancelDescription',\n descriptionFallback:\n 'Subscriptions you pay for but no longer use, and trials about to charge you.',\n },\n {\n id: 'downgrade',\n key: 'savings.sections.downgrade',\n fallback: 'Downgrade plan',\n descriptionKey: 'savings.sections.downgradeDescription',\n descriptionFallback: 'A cheaper plan from the same provider covers what you use.',\n },\n {\n id: 'annual',\n key: 'savings.sections.annual',\n fallback: 'Switch to annual',\n descriptionKey: 'savings.sections.annualDescription',\n descriptionFallback: 'The same plan costs less when billed yearly.',\n },\n {\n id: 'other',\n key: 'savings.sections.other',\n fallback: 'Other recommendations',\n descriptionKey: 'savings.sections.otherDescription',\n descriptionFallback: 'Pause, keep after a trial, or let it renew.',\n },\n];\n\nconst SECTION_OF: Record<SubDecisionRecommendation, SavingsSectionId> = {\n CANCEL: 'cancel',\n DOWNGRADE: 'downgrade',\n SWITCH_TO_ANNUAL: 'annual',\n PAUSE: 'other',\n CONVERT_TRIAL: 'other',\n RENEW: 'other',\n};\n\nexport function sectionOf(recommendation: SubDecisionRecommendation): SavingsSectionId {\n return SECTION_OF[recommendation];\n}\n\n/**\n * Open decisions grouped into sections, biggest saving first within each.\n * Decisions that are not PROPOSED are left out: only open advice saves money.\n */\nexport function groupDecisions(\n decisions: readonly SubDecision[]\n): Record<SavingsSectionId, SubDecision[]> {\n const groups: Record<SavingsSectionId, SubDecision[]> = {\n cancel: [],\n downgrade: [],\n annual: [],\n other: [],\n };\n for (const decision of decisions) {\n if (decision.status !== 'PROPOSED') continue;\n groups[sectionOf(decision.recommendation)].push(decision);\n }\n for (const list of Object.values(groups)) {\n list.sort(\n (a, b) => b.estimatedSavings - a.estimatedSavings || a.createdAt.localeCompare(b.createdAt)\n );\n }\n return groups;\n}\n\n/** Sum of estimated savings per currency, for a section header. */\nexport function savingsByCurrency(\n decisions: readonly Pick<SubDecision, 'estimatedSavings' | 'currency'>[]\n): Array<{ currency: string; amount: number }> {\n const sums = new Map<string, number>();\n for (const d of decisions) {\n if (!(d.estimatedSavings > 0)) continue;\n sums.set(d.currency, (sums.get(d.currency) ?? 0) + d.estimatedSavings);\n }\n return [...sums.entries()]\n .map(([currency, amount]) => ({ currency, amount: Math.round(amount * 100) / 100 }))\n .sort((a, b) => b.amount - a.amount || a.currency.localeCompare(b.currency));\n}\n\n/** Join per-currency amounts with a formatter: \"$480.00 + €50.00\". Empty list → null. */\nexport function joinAmounts(\n amounts: ReadonlyArray<{ currency: string; amount: number }>,\n format: (amount: number, currency: string) => string\n): string | null {\n const parts = amounts.filter((a) => a.amount > 0).map((a) => format(a.amount, a.currency));\n return parts.length > 0 ? parts.join(' + ') : null;\n}\n\n/** Headline totals as amounts (yearly). */\nexport function headlineAmounts(\n totals: readonly SubSavingsTotal[]\n): Array<{ currency: string; amount: number }> {\n return totals.map((t) => ({ currency: t.currency, amount: t.annualSavings }));\n}\n\n/** Monthly equivalent of a yearly amount, for the \"per month\" sub-line. */\nexport function perMonth(annual: number): number {\n return Math.round((annual / 12) * 100) / 100;\n}\n\n/**\n * A bundle's members split the way consolidating it acts on them: the one it\n * keeps, the ones it cancels, and any it leaves alone because they also cover\n * something the kept one does not.\n */\nexport function bundleMembers(bundle: SubOverlap): {\n keep: SubOverlapMember | null;\n cancel: SubOverlapMember[];\n untouched: SubOverlapMember[];\n} {\n const drop = new Set(bundle.dropRelationshipIds);\n return {\n keep: bundle.members.find((m) => m.relationshipId === bundle.keepRelationshipId) ?? null,\n cancel: bundle.members.filter((m) => drop.has(m.relationshipId)),\n untouched: bundle.members.filter(\n (m) => m.relationshipId !== bundle.keepRelationshipId && !drop.has(m.relationshipId)\n ),\n };\n}\n\nconst PERIODS_PER_YEAR: Record<string, number> = {\n DAILY: 365,\n WEEKLY: 52,\n MONTHLY: 12,\n QUARTERLY: 4,\n YEARLY: 1,\n};\n\n/** Yearly cost of an amount billed every `interval`; 0 for non-recurring intervals. */\nexport function annualCost(amount: number, interval: string): number {\n return Math.round(amount * (PERIODS_PER_YEAR[interval] ?? 0) * 100) / 100;\n}\n\n/**\n * Yearly saving of a consolidation the user is about to confirm: the kept and\n * cancelled subscriptions' yearly cost today minus the kept one afterwards\n * (at the bundled price when one is entered).\n */\nexport function projectedConsolidationSavings(\n bundle: SubOverlap,\n bundledPlan?: { amount: number; interval: string } | null\n): number {\n const { keep, cancel } = bundleMembers(bundle);\n if (!keep) return 0;\n const before = [keep, ...cancel].reduce((sum, m) => sum + annualCost(m.amount, m.interval), 0);\n const after = bundledPlan\n ? annualCost(bundledPlan.amount, bundledPlan.interval)\n : annualCost(keep.amount, keep.interval);\n return Math.round((before - after) * 100) / 100;\n}\n\n/** Parse the optional bundled-plan price field: blank → null, invalid → NaN. */\nexport function parseBundledAmount(raw: string): number | null {\n const trimmed = raw.trim();\n if (trimmed === '') return null;\n const value = Number(trimmed);\n return Number.isFinite(value) && value >= 0 ? value : Number.NaN;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSubscriberBotApi.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/hooks/useSubscriberBotApi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSubscriberBotApi.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/hooks/useSubscriberBotApi.ts"],"names":[],"mappings":"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"}
@@ -655,7 +655,7 @@ var c = async (e, t, n) => {
655
655
  agentId: e.agentId
656
656
  })
657
657
  });
658
- }, ve = "\n generatedAt openDecisions truncated\n totals { currency annualSavings fromDecisions fromBundles }\n groups { recommendation currency count annualSavings }\n bundles {\n id basis providerId providerName category currency keepRelationshipId\n relationshipIds cancelRelationshipIds currentAnnualCost projectedAnnualCost\n estimatedSavings confidence rationale\n relationships { id label status amount currency interval }\n }\n", ye = () => {
658
+ }, ve = "\n generatedAt openDecisions truncated\n totals { currency annualSavings fromDecisions fromBundles }\n groups { recommendation currency count annualSavings }\n bundles {\n id type key title currency keepRelationshipId dropRelationshipIds\n annualSpend annualSavings confidence rationale\n members {\n relationshipId label providerId providerName amount currency interval\n annualCost capabilities\n }\n }\n", ye = () => {
659
659
  let { apiGatewayUrl: t } = e();
660
660
  return i({
661
661
  queryKey: l.savings,
@@ -1 +1 @@
1
- {"version":3,"file":"useSubscriberBotApi.js","names":[],"sources":["../../../src/subscriberbot/hooks/useSubscriberBotApi.ts"],"sourcesContent":["/**\n * SubscriberBot API hooks\n * @module subscriberbot/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-subscriberbot-svc`\n * federated GraphQL contract (the `Sub*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })`\n * — the same pattern the workspace MFEs use; the host shell supplies the\n * gateway base URL + a workspace-scoped token.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n// clearGraphqlFetchResponseCache is re-exported from the barrel only in newer\n// fe-libs; import it from its defining module so this works on the pinned\n// 2026.625.5 without forcing a risky fe-libs major bump.\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql/fetch';\n\nimport { useSubscriberBot } from '../context';\nimport type {\n PaginationInput,\n SubAdapterSubmission,\n SubAgentKind,\n SubAgentStatus,\n SubAiAgent,\n SubAsset,\n SubBenchmarkDataPoint,\n SubBenchmarkFilterInput,\n SubCommunicationChannel,\n SubConnection,\n SubCreateAgentInput,\n SubCreateAssetInput,\n SubCreateChannelInput,\n SubCreateEntitlementInput,\n SubCreateInvoiceInput,\n SubCreatePaymentInstrumentInput,\n SubCreatePlanInput,\n SubCreatePolicyInput,\n SubCreateProviderInput,\n SubCreateRelationshipInput,\n SubCreateWorkflowInput,\n SubAgentRunResult,\n SubDecision,\n SubDecisionFilter,\n SubDecisionResult,\n SubConsolidateRelationshipsInput,\n SubConsolidationResult,\n SubSavingsOverview,\n SubEntitlement,\n SubInboxMessage,\n SubIngestMessageInput,\n SubInvoice,\n SubInvoiceStatus,\n SubMarketplaceReport,\n SubMessageCategory,\n SubModerationAction,\n SubPaymentInstrument,\n SubPlan,\n SubPolicy,\n SubProvider,\n SubProviderCategory,\n SubProviderStatus,\n SubRelationship,\n SubRelationshipKind,\n SubRelationshipStatus,\n SubRenewalEvent,\n SubReportStatus,\n SubSubmissionStatus,\n SubUpdateProviderInput,\n SubUpdateRelationshipInput,\n SubWorkflow,\n SubWorkflowStatus,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\nconst subQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n // A mutation just changed server state. fe-libs' graphqlFetch keeps a ~15s\n // global response micro-cache keyed by query+vars, so the react-query refetch\n // that an onSuccess triggers would otherwise be served the STALE pre-write\n // response (a newly created provider/relationship/agent wouldn't appear, a\n // just-marked-read message would still show unread) until the TTL expires.\n // Drop that cache here — centralized so no mutation call site can forget it.\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlFetchResponseCache();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n relationships: ['subscriberbot', 'relationships'] as const,\n relationship: (id: string) => ['subscriberbot', 'relationship', id] as const,\n providers: ['subscriberbot', 'providers'] as const,\n provider: (id: string) => ['subscriberbot', 'provider', id] as const,\n plans: ['subscriberbot', 'plans'] as const,\n benchmarks: (providerSlug: string) => ['subscriberbot', 'benchmarks', providerSlug] as const,\n inbox: ['subscriberbot', 'inbox'] as const,\n invoices: ['subscriberbot', 'invoices'] as const,\n paymentInstruments: ['subscriberbot', 'payment-instruments'] as const,\n channels: ['subscriberbot', 'channels'] as const,\n agents: ['subscriberbot', 'agents'] as const,\n agent: (id: string) => ['subscriberbot', 'agent', id] as const,\n decisions: ['subscriberbot', 'decisions'] as const,\n decision: (id: string) => ['subscriberbot', 'decision', id] as const,\n // Nested under `decisions` so every decision change (apply, dismiss, run)\n // also refreshes the savings dashboard.\n savings: ['subscriberbot', 'decisions', 'savings-overview'] as const,\n workflows: ['subscriberbot', 'workflows'] as const,\n renewals: ['subscriberbot', 'renewals'] as const,\n assets: ['subscriberbot', 'assets'] as const,\n entitlements: ['subscriberbot', 'entitlements'] as const,\n adapterSubmissions: ['subscriberbot', 'admin', 'adapter-submissions'] as const,\n marketplaceReports: ['subscriberbot', 'admin', 'marketplace-reports'] as const,\n} as const;\n\n// ============================================================================\n// Reusable gql field selections (kept inline-string for codegen-free strictness)\n// ============================================================================\n\nconst PROVIDER_FIELDS = `\n id name slug category status description industry region language websiteUrl\n logoFileId tags churnRiskScore vendorHealthScore workspaceId createdAt updatedAt\n`;\n\nconst PLAN_FIELDS = `\n id name description providerId amount currency interval features isActive\n workspaceId createdAt updatedAt\n`;\n\nconst RELATIONSHIP_FIELDS = `\n id label kind status amount currency interval autoRenew discoverySource notes\n tags healthScore ownerId providerId planId startedAt renewsAt trialEndsAt\n cancelledAt workspaceId createdAt updatedAt\n`;\n\nconst INBOX_FIELDS = `\n id subject preview category categoryConfidence channelId relationshipId\n externalId actionRequired isRead receivedAt workspaceId createdAt updatedAt\n`;\n\nconst INVOICE_FIELDS = `\n id number relationshipId status currency subtotal taxAmount total issueDate\n dueDate paidAt paymentInstrumentId workspaceId createdAt updatedAt\n`;\n\nconst AGENT_FIELDS = `\n id name description kind status ownerId lastRunAt workspaceId createdAt updatedAt\n`;\n\nconst DECISION_FIELDS = `\n id agentId runId relationshipId kind recommendation status rationale confidence\n estimatedSavings currency evidence proposedChange validationErrors invocationRef\n applyInvocationRef appliedAt appliedBy dismissedAt dismissedReason workspaceId\n createdAt updatedAt\n agent { id name kind status }\n relationship { id label status amount currency interval providerId planId }\n`;\n\nconst WORKFLOW_FIELDS = `\n id name description triggerEvent status runCount lastRunAt workspaceId\n createdAt updatedAt\n`;\n\nconst ENTITLEMENT_FIELDS = `\n id name description type relationshipId quota used unit isActive\n workspaceId createdAt updatedAt\n`;\n\nconst ASSET_FIELDS = `\n id name type relationshipId secretRef expiresAt workspaceId createdAt\n updatedAt file { id }\n`;\n\nconst PAYMENT_INSTRUMENT_FIELDS = `\n id label type status brand last4 expiryMonth expiryYear isDefault ownerId\n workspaceId createdAt updatedAt\n`;\n\nconst CHANNEL_FIELDS = `\n id label type status address ingestEnabled ownerId verifiedAt workspaceId\n createdAt updatedAt\n`;\n\nconst POLICY_FIELDS = `\n id name description action agentId condition isEnabled triggerCount\n lastTriggeredAt workspaceId createdAt updatedAt\n`;\n\nconst BENCHMARK_DATA_POINT_FIELDS = `\n id providerSlug providerName planName region currency interval amount\n contractSizeSeats sourceKind sourceLabel notes observedAt createdAt\n`;\n\nconst ADAPTER_SUBMISSION_FIELDS = `\n id workspaceId adapterId submitterWorkspaceId status notes createdAt updatedAt\n`;\n\nconst MARKETPLACE_REPORT_FIELDS = `\n id workspaceId listingId reporterWorkspaceId reason status resolutionReason\n createdAt updatedAt\n`;\n\n// ============================================================================\n// Relationships (the core domain object)\n// ============================================================================\n\nexport interface RelationshipListFilters {\n kind?: SubRelationshipKind;\n status?: SubRelationshipStatus;\n providerId?: string;\n ownerId?: string;\n /** Case-insensitive substring match on the relationship label (server-side). */\n search?: string;\n pagination?: PaginationInput;\n}\n\nexport const useRelationships = (filters: RelationshipListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.relationships, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListRelationships: SubConnection<SubRelationship>;\n }>(\n `query SubRelationships(\n $kind: SubRelationshipKind\n $status: SubRelationshipStatus\n $providerId: ID\n $ownerId: ID\n $search: String\n $pagination: PaginationInput\n ) {\n subListRelationships(\n kind: $kind\n status: $status\n providerId: $providerId\n ownerId: $ownerId\n search: $search\n pagination: $pagination\n ) {\n items {\n ${RELATIONSHIP_FIELDS}\n provider { id name category status logoFileId }\n plan { id name amount currency interval }\n }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n kind: filters.kind,\n status: filters.status,\n providerId: filters.providerId,\n ownerId: filters.ownerId,\n search: filters.search,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListRelationships;\n },\n });\n};\n\nexport const useRelationship = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.relationship(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await subQuery<{ subRelationship: SubRelationship | null }>(\n `query SubRelationship($id: ID!) {\n subRelationship(id: $id) {\n ${RELATIONSHIP_FIELDS}\n provider { ${PROVIDER_FIELDS} }\n plan { ${PLAN_FIELDS} }\n entitlements {\n id name description type relationshipId quota used unit isActive\n workspaceId createdAt updatedAt\n }\n assets {\n id name type relationshipId secretRef expiresAt workspaceId\n createdAt updatedAt file { id }\n }\n renewalEvents {\n id relationshipId eventType occursAt amount currency detail\n notified workspaceId createdAt updatedAt\n }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subRelationship;\n },\n });\n};\n\nexport const useCreateRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateRelationshipInput) => {\n const result = await subQuery<{ subCreateRelationship: SubRelationship }>(\n `mutation SubCreateRelationship($input: SubCreateRelationshipInput!) {\n subCreateRelationship(input: $input) { ${RELATIONSHIP_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateRelationship;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n },\n });\n};\n\nexport const useTransitionRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: SubRelationshipStatus }) => {\n const result = await subQuery<{ subTransitionRelationship: SubRelationship }>(\n `mutation SubTransitionRelationship($id: ID!, $status: SubRelationshipStatus!) {\n subTransitionRelationship(id: $id, status: $status) { ${RELATIONSHIP_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.subTransitionRelationship;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.id) });\n },\n });\n};\n\nexport const useCancelRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subCancelRelationship: SubRelationship }>(\n `mutation SubCancelRelationship($id: ID!) {\n subCancelRelationship(id: $id) { ${RELATIONSHIP_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subCancelRelationship;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.id) });\n },\n });\n};\n\nexport const useUpdateRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: SubUpdateRelationshipInput }) => {\n const result = await subQuery<{ subUpdateRelationship: SubRelationship }>(\n `mutation SubUpdateRelationship($id: ID!, $input: SubUpdateRelationshipInput!) {\n subUpdateRelationship(id: $id, input: $input) { ${RELATIONSHIP_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.subUpdateRelationship;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.id) });\n },\n });\n};\n\nexport const useRenewalEvents = (\n args: { relationshipId?: string; upcomingOnly?: boolean } = {}\n) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.renewals, args],\n queryFn: async () => {\n // subListRenewalEvents returns SubRenewalEventConnection (items/pageInfo),\n // not a bare list — matching every other subList* root field in this\n // schema (see useRelationships above). Selecting event fields directly\n // fails GraphQL validation server-side (`Cannot query field \"id\" on type\n // \"SubRenewalEventConnection\"`).\n const result = await subQuery<{\n subListRenewalEvents: SubConnection<SubRenewalEvent>;\n }>(\n `query SubRenewalEvents($relationshipId: ID, $upcomingOnly: Boolean) {\n subListRenewalEvents(relationshipId: $relationshipId, upcomingOnly: $upcomingOnly) {\n items {\n id relationshipId eventType occursAt amount currency detail notified\n workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { relationshipId: args.relationshipId, upcomingOnly: args.upcomingOnly },\n apiGatewayUrl\n );\n return result.subListRenewalEvents.items;\n },\n });\n};\n\nexport const useEntitlements = (relationshipId: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.entitlements, relationshipId],\n enabled: Boolean(relationshipId),\n queryFn: async () => {\n const result = await subQuery<{\n subListEntitlements: SubConnection<SubEntitlement>;\n }>(\n `query SubEntitlements($relationshipId: ID!) {\n subListEntitlements(relationshipId: $relationshipId) {\n items {\n id name description type relationshipId quota used unit isActive\n workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { relationshipId },\n apiGatewayUrl\n );\n return result.subListEntitlements.items;\n },\n });\n};\n\nexport const useCreateEntitlement = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateEntitlementInput) => {\n const result = await subQuery<{ subCreateEntitlement: SubEntitlement }>(\n `mutation SubCreateEntitlement($input: SubCreateEntitlementInput!) {\n subCreateEntitlement(input: $input) { ${ENTITLEMENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateEntitlement;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.entitlements });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.relationshipId) });\n },\n });\n};\n\nexport const useAssets = (relationshipId: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.assets, relationshipId],\n enabled: Boolean(relationshipId),\n queryFn: async () => {\n const result = await subQuery<{ subListAssets: SubConnection<SubAsset> }>(\n `query SubAssets($relationshipId: ID!) {\n subListAssets(relationshipId: $relationshipId) {\n items {\n id name type relationshipId secretRef expiresAt workspaceId\n createdAt updatedAt file { id }\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { relationshipId },\n apiGatewayUrl\n );\n return result.subListAssets.items;\n },\n });\n};\n\n/** Every asset in the workspace (Digital Asset Vault) — omits relationshipId. */\nexport const useAllAssets = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.assets, 'all'],\n queryFn: async () => {\n const result = await subQuery<{ subListAssets: SubConnection<SubAsset> }>(\n `query SubAllAssets {\n subListAssets { items { ${ASSET_FIELDS} } pageInfo { cursor hasMore } }\n }`,\n {},\n apiGatewayUrl\n );\n return result.subListAssets.items;\n },\n });\n};\n\nexport const useCreateAsset = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateAssetInput) => {\n const result = await subQuery<{ subCreateAsset: SubAsset }>(\n `mutation SubCreateAsset($input: SubCreateAssetInput!) {\n subCreateAsset(input: $input) { ${ASSET_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateAsset;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.assets });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.relationshipId) });\n },\n });\n};\n\n/**\n * Upload a file to `wspace-files-svc` and return its File id, using the\n * platform-standard 3-step flow (initiateUpload → presigned PUT → completeUpload)\n * over the same `graphqlFetch` transport the rest of this MFE uses. Assets store\n * only the returned `fileId` on `SubCreateAssetInput`; blobs live in files-svc.\n *\n * `CompleteUploadInput.fileSize` is a REQUIRED BigInt (quota tracking) and must\n * match the value given to `initiateUpload` — we pass `String(file.size)` to both.\n */\nconst ASSET_FOLDER_PATH = '/SubscriberBot/Assets';\n\nexport const useUploadFile = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n const [progress, setProgress] = useState(0);\n const [isUploading, setIsUploading] = useState(false);\n\n const uploadFile = useCallback(\n async (file: File): Promise<{ fileId: string; fileName: string }> => {\n setIsUploading(true);\n setProgress(0);\n try {\n // 1. Resolve (or create) the destination folder — File.folderId is a real FK.\n const folderRes = await subQuery<{ ensureFolderByPath: { id: string } | null }>(\n `mutation SubEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }`,\n { path: ASSET_FOLDER_PATH },\n apiGatewayUrl\n );\n const folderId = folderRes.ensureFolderByPath?.id;\n if (!folderId) throw new Error('Could not resolve the upload folder.');\n\n const mimeType = file.type || 'application/octet-stream';\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : 'bin';\n\n // 2. Open a presigned upload session.\n const initiated = await subQuery<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } } | null;\n }>(\n `mutation SubInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n folderId,\n },\n },\n apiGatewayUrl\n );\n const uploadUrl = initiated.initiateUpload?.uploadUrl;\n const sessionId = initiated.initiateUpload?.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('The files service did not return an upload URL.');\n }\n\n // 3. PUT the raw bytes to storage. Content-Type must match what was signed.\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', mimeType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n setProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve();\n else reject(new Error(`Upload to storage failed (${xhr.status}).`));\n };\n xhr.onerror = () => reject(new Error('Upload to storage failed.'));\n xhr.send(file);\n });\n\n // 4. Finalize — creates the File record. fileSize must match initiateUpload.\n const completed = await subQuery<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null } | null;\n }>(\n `mutation SubCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId, fileSize: String(file.size), folderId } },\n apiGatewayUrl\n );\n const fileId = completed.completeUpload?.file?.id ?? completed.completeUpload?.fileId;\n if (!fileId) throw new Error('The upload could not be finalized.');\n return { fileId, fileName: completed.completeUpload?.file?.name ?? file.name };\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { uploadFile, progress, isUploading };\n};\n\n/**\n * Resolve a short-lived presigned download URL for a `wspace-files-svc` File id.\n * Never select the federated arg-less `File.downloadUrl` (it mints a URL beyond\n * S3's 7-day cap and 500s the response) — use the root `getFileDownloadUrl`\n * query with a TTL under 604800s instead.\n */\nexport const useFileDownloadUrl = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useCallback(\n async (fileId: string): Promise<string> => {\n const result = await subQuery<{ getFileDownloadUrl: string }>(\n `query SubFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }`,\n { id: fileId, expiresIn: 21600 },\n apiGatewayUrl\n );\n return result.getFileDownloadUrl;\n },\n [apiGatewayUrl]\n );\n};\n\n// ============================================================================\n// Providers (the global discovery registry)\n// ============================================================================\n\nexport interface ProviderListFilters {\n category?: SubProviderCategory;\n status?: SubProviderStatus;\n search?: string;\n pagination?: PaginationInput;\n}\n\nexport const useProviders = (filters: ProviderListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.providers, filters],\n queryFn: async () => {\n const result = await subQuery<{ subListProviders: SubConnection<SubProvider> }>(\n `query SubProviders(\n $category: SubProviderCategory\n $status: SubProviderStatus\n $search: String\n $pagination: PaginationInput\n ) {\n subListProviders(\n category: $category\n status: $status\n search: $search\n pagination: $pagination\n ) {\n items { ${PROVIDER_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n category: filters.category,\n status: filters.status,\n search: filters.search,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListProviders;\n },\n });\n};\n\nexport const useProvider = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.provider(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await subQuery<{ subProvider: SubProvider | null }>(\n `query SubProvider($id: ID!) {\n subProvider(id: $id) {\n ${PROVIDER_FIELDS}\n plans { ${PLAN_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subProvider;\n },\n });\n};\n\nexport const useCreateProvider = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateProviderInput) => {\n const result = await subQuery<{ subCreateProvider: SubProvider }>(\n `mutation SubCreateProvider($input: SubCreateProviderInput!) {\n subCreateProvider(input: $input) { ${PROVIDER_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateProvider;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.providers });\n },\n });\n};\n\nexport const useUpdateProvider = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: SubUpdateProviderInput }) => {\n const result = await subQuery<{ subUpdateProvider: SubProvider }>(\n `mutation SubUpdateProvider($id: ID!, $input: SubUpdateProviderInput!) {\n subUpdateProvider(id: $id, input: $input) { ${PROVIDER_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.subUpdateProvider;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.providers });\n void queryClient.invalidateQueries({ queryKey: KEY.provider(data.id) });\n },\n });\n};\n\nexport const useArchiveProvider = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subArchiveProvider: boolean }>(\n `mutation SubArchiveProvider($id: ID!) {\n subArchiveProvider(id: $id)\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subArchiveProvider;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.providers });\n },\n });\n};\n\nexport const usePlans = (providerId: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.plans, providerId],\n enabled: Boolean(providerId),\n queryFn: async () => {\n const result = await subQuery<{ subListPlans: SubConnection<SubPlan> }>(\n `query SubPlans($providerId: ID) {\n subListPlans(providerId: $providerId) { items { ${PLAN_FIELDS} } pageInfo { cursor hasMore } }\n }`,\n { providerId },\n apiGatewayUrl\n );\n return result.subListPlans.items;\n },\n });\n};\n\nexport const useCreatePlan = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreatePlanInput) => {\n const result = await subQuery<{ subCreatePlan: SubPlan }>(\n `mutation SubCreatePlan($input: SubCreatePlanInput!) {\n subCreatePlan(input: $input) { ${PLAN_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreatePlan;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.plans });\n void queryClient.invalidateQueries({ queryKey: KEY.provider(data.providerId) });\n },\n });\n};\n\n/**\n * Observed price points for one provider, newest first from the server.\n *\n * Backs the provider \"Price history\" card. The benchmark dataset is read\n * GLOBALLY (`@rbac(..., public: true)` on the svc) — it is the same for every\n * workspace — so nothing here is workspace scoped. Matching is by\n * `providerSlug`, which is an exact match server-side; `providerName` is a\n * fuzzy `contains` match and would risk attributing another vendor's prices to\n * this provider, so the query stays disabled until a slug exists.\n */\nexport const useBenchmarkDataPoints = (providerSlug: string | null | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.benchmarks(providerSlug ?? ''),\n enabled: Boolean(providerSlug),\n queryFn: async () => {\n const filter: SubBenchmarkFilterInput = { providerSlug: providerSlug ?? undefined };\n const result = await subQuery<{\n subListBenchmarkDataPoints: SubConnection<SubBenchmarkDataPoint>;\n }>(\n `query SubBenchmarkDataPoints(\n $filter: SubBenchmarkFilterInput\n $pagination: PaginationInput\n ) {\n subListBenchmarkDataPoints(filter: $filter, pagination: $pagination) {\n items { ${BENCHMARK_DATA_POINT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { filter, pagination: { limit: 100, offset: 0 } },\n apiGatewayUrl\n );\n return result.subListBenchmarkDataPoints.items;\n },\n });\n};\n\n// ============================================================================\n// Universal Inbox\n// ============================================================================\n\nexport interface InboxListFilters {\n category?: SubMessageCategory;\n channelId?: string;\n relationshipId?: string;\n unreadOnly?: boolean;\n pagination?: PaginationInput;\n}\n\nexport const useInboxMessages = (filters: InboxListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.inbox, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListInboxMessages: SubConnection<SubInboxMessage>;\n }>(\n `query SubInboxMessages(\n $category: SubMessageCategory\n $channelId: ID\n $relationshipId: ID\n $unreadOnly: Boolean\n $pagination: PaginationInput\n ) {\n subListInboxMessages(\n category: $category\n channelId: $channelId\n relationshipId: $relationshipId\n unreadOnly: $unreadOnly\n pagination: $pagination\n ) {\n items { ${INBOX_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n category: filters.category,\n channelId: filters.channelId,\n relationshipId: filters.relationshipId,\n unreadOnly: filters.unreadOnly,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListInboxMessages;\n },\n });\n};\n\nexport const useMarkMessageRead = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subMarkMessageRead: SubInboxMessage }>(\n `mutation SubMarkMessageRead($id: ID!) {\n subMarkMessageRead(id: $id) { ${INBOX_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subMarkMessageRead;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.inbox });\n },\n });\n};\n\nexport const useIngestMessage = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubIngestMessageInput) => {\n const result = await subQuery<{ subIngestMessage: SubInboxMessage }>(\n `mutation SubIngestMessage($input: SubIngestMessageInput!) {\n subIngestMessage(input: $input) { ${INBOX_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subIngestMessage;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.inbox });\n },\n });\n};\n\n// ============================================================================\n// Invoices (Universal Billing Layer)\n// ============================================================================\n\nexport interface InvoiceListFilters {\n status?: SubInvoiceStatus;\n relationshipId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useInvoices = (\n filters: InvoiceListFilters = {},\n options: { enabled?: boolean } = {}\n) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.invoices, filters],\n enabled: options.enabled ?? true,\n queryFn: async () => {\n const result = await subQuery<{ subListInvoices: SubConnection<SubInvoice> }>(\n `query SubInvoices(\n $status: SubInvoiceStatus\n $relationshipId: ID\n $pagination: PaginationInput\n ) {\n subListInvoices(\n status: $status\n relationshipId: $relationshipId\n pagination: $pagination\n ) {\n items { ${INVOICE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n relationshipId: filters.relationshipId,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListInvoices;\n },\n });\n};\n\nexport const useMarkInvoicePaid = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subMarkInvoicePaid: SubInvoice }>(\n `mutation SubMarkInvoicePaid($id: ID!) {\n subMarkInvoicePaid(id: $id) { ${INVOICE_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subMarkInvoicePaid;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.invoices });\n },\n });\n};\n\nexport const useCreateInvoice = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateInvoiceInput) => {\n const result = await subQuery<{ subCreateInvoice: SubInvoice }>(\n `mutation SubCreateInvoice($input: SubCreateInvoiceInput!) {\n subCreateInvoice(input: $input) { ${INVOICE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateInvoice;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.invoices });\n },\n });\n};\n\n// ============================================================================\n// Payment instruments & communication channels\n// ============================================================================\n\nexport const usePaymentInstruments = (ownerId?: string) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.paymentInstruments, ownerId],\n queryFn: async () => {\n const result = await subQuery<{\n subListPaymentInstruments: SubConnection<SubPaymentInstrument>;\n }>(\n `query SubPaymentInstruments($ownerId: ID) {\n subListPaymentInstruments(ownerId: $ownerId) {\n items {\n id label type status brand last4 expiryMonth expiryYear isDefault\n ownerId workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { ownerId },\n apiGatewayUrl\n );\n return result.subListPaymentInstruments.items;\n },\n });\n};\n\nexport const useCreatePaymentInstrument = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreatePaymentInstrumentInput) => {\n const result = await subQuery<{ subCreatePaymentInstrument: SubPaymentInstrument }>(\n `mutation SubCreatePaymentInstrument($input: SubCreatePaymentInstrumentInput!) {\n subCreatePaymentInstrument(input: $input) { ${PAYMENT_INSTRUMENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreatePaymentInstrument;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.paymentInstruments });\n },\n });\n};\n\nexport const useRemovePaymentInstrument = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subRemovePaymentInstrument: boolean }>(\n `mutation SubRemovePaymentInstrument($id: ID!) {\n subRemovePaymentInstrument(id: $id)\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subRemovePaymentInstrument;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.paymentInstruments });\n },\n });\n};\n\nexport const useChannels = (ownerId?: string) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.channels, ownerId],\n queryFn: async () => {\n const result = await subQuery<{\n subListChannels: SubConnection<SubCommunicationChannel>;\n }>(\n `query SubChannels($ownerId: ID) {\n subListChannels(ownerId: $ownerId) {\n items {\n id label type status address ingestEnabled ownerId verifiedAt\n workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { ownerId },\n apiGatewayUrl\n );\n return result.subListChannels.items;\n },\n });\n};\n\nexport const useCreateChannel = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateChannelInput) => {\n const result = await subQuery<{ subCreateChannel: SubCommunicationChannel }>(\n `mutation SubCreateChannel($input: SubCreateChannelInput!) {\n subCreateChannel(input: $input) { ${CHANNEL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateChannel;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.channels });\n },\n });\n};\n\nexport const useVerifyChannel = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subVerifyChannel: SubCommunicationChannel }>(\n `mutation SubVerifyChannel($id: ID!) {\n subVerifyChannel(id: $id) { ${CHANNEL_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subVerifyChannel;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.channels });\n },\n });\n};\n\n// ============================================================================\n// AI Agents & workflows (automation)\n// ============================================================================\n\nexport interface AgentListFilters {\n kind?: SubAgentKind;\n status?: SubAgentStatus;\n ownerId?: string;\n}\n\nexport const useAgents = (filters: AgentListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.agents, filters],\n queryFn: async () => {\n const result = await subQuery<{ subListAgents: SubConnection<SubAiAgent> }>(\n `query SubAgents($kind: SubAgentKind, $status: SubAgentStatus, $ownerId: ID) {\n subListAgents(kind: $kind, status: $status, ownerId: $ownerId) {\n items { ${AGENT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { kind: filters.kind, status: filters.status, ownerId: filters.ownerId },\n apiGatewayUrl\n );\n return result.subListAgents.items;\n },\n });\n};\n\nexport const useAgent = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.agent(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await subQuery<{ subAgent: SubAiAgent | null }>(\n `query SubAgent($id: ID!) {\n subAgent(id: $id) {\n ${AGENT_FIELDS}\n policies { ${POLICY_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subAgent;\n },\n });\n};\n\nexport const useCreateAgent = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateAgentInput) => {\n const result = await subQuery<{ subCreateAgent: SubAiAgent }>(\n `mutation SubCreateAgent($input: SubCreateAgentInput!) {\n subCreateAgent(input: $input) { ${AGENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateAgent;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n },\n });\n};\n\nexport const useSetAgentStatus = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: SubAgentStatus }) => {\n const result = await subQuery<{ subSetAgentStatus: SubAiAgent }>(\n `mutation SubSetAgentStatus($id: ID!, $status: SubAgentStatus!) {\n subSetAgentStatus(id: $id, status: $status) { ${AGENT_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.subSetAgentStatus;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n void queryClient.invalidateQueries({ queryKey: KEY.agent(data.id) });\n },\n });\n};\n\nexport const useCreatePolicy = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreatePolicyInput) => {\n const result = await subQuery<{ subCreatePolicy: SubPolicy }>(\n `mutation SubCreatePolicy($input: SubCreatePolicyInput!) {\n subCreatePolicy(input: $input) { ${POLICY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreatePolicy;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n void queryClient.invalidateQueries({ queryKey: KEY.agent(data.agentId) });\n },\n });\n};\n\n// ----------------------------------------------------------------------------\n// Decision queue (BOFF-3634 engine). Every hook here reads or writes the real\n// `subListDecisions` / `subDecision` / `subRunAgent` / `subApplyDecision` /\n// `subDismissDecision` operations — no placeholder data.\n// ----------------------------------------------------------------------------\n\n/** Upper bound for one queue page; the svc caps `limit` server-side as well. */\nconst DECISION_PAGE_LIMIT = 100;\n\nexport const useDecisions = (\n filter: SubDecisionFilter = {},\n options: { enabled?: boolean } = {}\n) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.decisions, filter],\n enabled: options.enabled ?? true,\n // A decision engine outage must not retry-storm the gateway; the page\n // shows the degradation banner after the first failure.\n retry: 1,\n queryFn: async () => {\n const result = await subQuery<{ subListDecisions: SubConnection<SubDecision> }>(\n `query SubDecisions($filter: SubDecisionFilter, $pagination: PaginationInput) {\n subListDecisions(filter: $filter, pagination: $pagination) {\n items { ${DECISION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { filter, pagination: { limit: DECISION_PAGE_LIMIT } },\n apiGatewayUrl\n );\n return result.subListDecisions;\n },\n });\n};\n\nexport const useDecision = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.decision(id ?? ''),\n enabled: Boolean(id),\n retry: 1,\n queryFn: async () => {\n const result = await subQuery<{ subDecision: SubDecision | null }>(\n `query SubDecision($id: ID!) {\n subDecision(id: $id) { ${DECISION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subDecision;\n },\n });\n};\n\n/** Invalidate everything a decision change can affect. */\nconst invalidateDecisionViews = (\n queryClient: ReturnType<typeof useQueryClient>,\n ids: { decisionId?: string; agentId?: string; relationshipId?: string | null }\n) => {\n void queryClient.invalidateQueries({ queryKey: KEY.decisions });\n if (ids.decisionId)\n void queryClient.invalidateQueries({ queryKey: KEY.decision(ids.decisionId) });\n if (ids.agentId) {\n void queryClient.invalidateQueries({ queryKey: KEY.agent(ids.agentId) });\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n }\n if (ids.relationshipId) {\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(ids.relationshipId) });\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n }\n};\n\n/**\n * Run an agent. The UI only ever requests a dry run: decisions are persisted\n * for review and no relationship changes. Live runs stay with the\n * AUTONOMOUS posture and its fail-closed gate on the server.\n */\nexport const useRunAgent = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subRunAgent: SubAgentRunResult }>(\n `mutation SubRunAgent($id: ID!, $dryRun: Boolean) {\n subRunAgent(id: $id, dryRun: $dryRun) {\n runId agentId kind dryRun evaluated proposed flagged applied truncated\n invocationRef ranAt\n }\n }`,\n { id, dryRun: true },\n apiGatewayUrl\n );\n return result.subRunAgent;\n },\n onSuccess: (data) => invalidateDecisionViews(queryClient, { agentId: data.agentId }),\n });\n};\n\nexport const useApplyDecision = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subApplyDecision: SubDecisionResult }>(\n `mutation SubApplyDecision($id: ID!) {\n subApplyDecision(id: $id) {\n decision { ${DECISION_FIELDS} }\n relationship { id label status }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subApplyDecision;\n },\n onSuccess: (data) =>\n invalidateDecisionViews(queryClient, {\n decisionId: data.decision.id,\n agentId: data.decision.agentId,\n relationshipId: data.decision.relationshipId,\n }),\n // A refused apply (stale / already applied) still changes the decision's\n // server state, so the queue must refetch either way.\n onError: () => void queryClient.invalidateQueries({ queryKey: KEY.decisions }),\n });\n};\n\nexport const useDismissDecision = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, reason }: { id: string; reason?: string }) => {\n const result = await subQuery<{ subDismissDecision: SubDecision }>(\n `mutation SubDismissDecision($id: ID!, $reason: String) {\n subDismissDecision(id: $id, reason: $reason) { ${DECISION_FIELDS} }\n }`,\n { id, reason: reason?.trim() ? reason.trim() : undefined },\n apiGatewayUrl\n );\n return result.subDismissDecision;\n },\n onSuccess: (data) =>\n invalidateDecisionViews(queryClient, { decisionId: data.id, agentId: data.agentId }),\n });\n};\n\n// ----------------------------------------------------------------------------\n// Savings dashboard (BOFF-4001): `subSavingsOverview`,\n// `subConsolidateRelationships` and `subDismissBundle`.\n// ----------------------------------------------------------------------------\n\nconst SAVINGS_OVERVIEW_FIELDS = `\n generatedAt openDecisions truncated\n totals { currency annualSavings fromDecisions fromBundles }\n groups { recommendation currency count annualSavings }\n bundles {\n id basis providerId providerName category currency keepRelationshipId\n relationshipIds cancelRelationshipIds currentAnnualCost projectedAnnualCost\n estimatedSavings confidence rationale\n relationships { id label status amount currency interval }\n }\n`;\n\nexport const useSavingsOverview = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.savings,\n retry: 1,\n queryFn: async () => {\n const result = await subQuery<{ subSavingsOverview: SubSavingsOverview }>(\n `query SubSavingsOverview { subSavingsOverview { ${SAVINGS_OVERVIEW_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.subSavingsOverview;\n },\n });\n};\n\n/** Keep one subscription (optionally on a bundled plan) and cancel the others. */\nexport const useConsolidateRelationships = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubConsolidateRelationshipsInput) => {\n const result = await subQuery<{ subConsolidateRelationships: SubConsolidationResult }>(\n `mutation SubConsolidateRelationships($input: SubConsolidateRelationshipsInput!) {\n subConsolidateRelationships(input: $input) {\n kept { id label status amount currency interval }\n cancelled { id label status }\n failed { relationshipId message }\n estimatedSavings currency\n }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subConsolidateRelationships;\n },\n // Cancels can partly succeed, so refresh on either outcome.\n onSettled: (_data, _error, input) => {\n void queryClient.invalidateQueries({ queryKey: KEY.decisions });\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n for (const id of [input.keepRelationshipId, ...input.cancelRelationshipIds]) {\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(id) });\n }\n },\n });\n};\n\nexport const useDismissBundle = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, reason }: { id: string; reason?: string }) => {\n const result = await subQuery<{ subDismissBundle: boolean }>(\n `mutation SubDismissBundle($id: ID!, $reason: String) {\n subDismissBundle(id: $id, reason: $reason)\n }`,\n { id, reason: reason?.trim() ? reason.trim() : undefined },\n apiGatewayUrl\n );\n return result.subDismissBundle;\n },\n onSuccess: () => void queryClient.invalidateQueries({ queryKey: KEY.savings }),\n });\n};\n\nexport const useWorkflows = (status?: SubWorkflowStatus) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.workflows, status],\n queryFn: async () => {\n const result = await subQuery<{ subListWorkflows: SubConnection<SubWorkflow> }>(\n `query SubWorkflows($status: SubWorkflowStatus) {\n subListWorkflows(status: $status) { items { ${WORKFLOW_FIELDS} } pageInfo { cursor hasMore } }\n }`,\n { status },\n apiGatewayUrl\n );\n return result.subListWorkflows.items;\n },\n });\n};\n\nexport const useCreateWorkflow = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateWorkflowInput) => {\n const result = await subQuery<{ subCreateWorkflow: SubWorkflow }>(\n `mutation SubCreateWorkflow($input: SubCreateWorkflowInput!) {\n subCreateWorkflow(input: $input) { ${WORKFLOW_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateWorkflow;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.workflows });\n },\n });\n};\n\nexport const useActivateWorkflow = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subActivateWorkflow: SubWorkflow }>(\n `mutation SubActivateWorkflow($id: ID!) {\n subActivateWorkflow(id: $id) { ${WORKFLOW_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subActivateWorkflow;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.workflows });\n },\n });\n};\n\n// ============================================================================\n// Admin governance (adapter approvals + marketplace moderation)\n// ============================================================================\n\nexport interface AdapterSubmissionListFilters {\n status?: SubSubmissionStatus;\n pagination?: PaginationInput;\n}\n\nexport const useAdapterSubmissions = (filters: AdapterSubmissionListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.adapterSubmissions, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListAdapterSubmissions: SubConnection<SubAdapterSubmission>;\n }>(\n `query SubAdapterSubmissions($status: SubSubmissionStatus, $pagination: PaginationInput) {\n subListAdapterSubmissions(status: $status, pagination: $pagination) {\n items { ${ADAPTER_SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListAdapterSubmissions;\n },\n });\n};\n\nexport const useApproveAdapter = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, notes }: { id: string; notes?: string }) => {\n const result = await subQuery<{ subApproveAdapter: SubAdapterSubmission }>(\n `mutation SubApproveAdapter($id: ID!, $notes: String) {\n subApproveAdapter(id: $id, notes: $notes) { ${ADAPTER_SUBMISSION_FIELDS} }\n }`,\n { id, notes },\n apiGatewayUrl\n );\n return result.subApproveAdapter;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.adapterSubmissions });\n },\n });\n};\n\nexport const useRejectAdapter = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, reason }: { id: string; reason: string }) => {\n const result = await subQuery<{ subRejectAdapter: SubAdapterSubmission }>(\n `mutation SubRejectAdapter($id: ID!, $reason: String!) {\n subRejectAdapter(id: $id, reason: $reason) { ${ADAPTER_SUBMISSION_FIELDS} }\n }`,\n { id, reason },\n apiGatewayUrl\n );\n return result.subRejectAdapter;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.adapterSubmissions });\n },\n });\n};\n\nexport interface MarketplaceReportListFilters {\n status?: SubReportStatus;\n pagination?: PaginationInput;\n}\n\nexport const useMarketplaceReports = (filters: MarketplaceReportListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.marketplaceReports, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListMarketplaceReports: SubConnection<SubMarketplaceReport>;\n }>(\n `query SubMarketplaceReports($status: SubReportStatus, $pagination: PaginationInput) {\n subListMarketplaceReports(status: $status, pagination: $pagination) {\n items { ${MARKETPLACE_REPORT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListMarketplaceReports;\n },\n });\n};\n\nexport const useModerateListing = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({\n id,\n action,\n reason,\n }: {\n id: string;\n action: SubModerationAction;\n reason: string;\n }) => {\n const result = await subQuery<{ subModerateListing: SubMarketplaceReport }>(\n `mutation SubModerateListing($id: ID!, $action: SubModerationAction!, $reason: String!) {\n subModerateListing(id: $id, action: $action, reason: $reason) { ${MARKETPLACE_REPORT_FIELDS} }\n }`,\n { id, action, reason },\n apiGatewayUrl\n );\n return result.subModerateListing;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.marketplaceReports });\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSubscriberBotApi.js","names":[],"sources":["../../../src/subscriberbot/hooks/useSubscriberBotApi.ts"],"sourcesContent":["/**\n * SubscriberBot API hooks\n * @module subscriberbot/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-subscriberbot-svc`\n * federated GraphQL contract (the `Sub*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })`\n * — the same pattern the workspace MFEs use; the host shell supplies the\n * gateway base URL + a workspace-scoped token.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n// clearGraphqlFetchResponseCache is re-exported from the barrel only in newer\n// fe-libs; import it from its defining module so this works on the pinned\n// 2026.625.5 without forcing a risky fe-libs major bump.\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql/fetch';\n\nimport { useSubscriberBot } from '../context';\nimport type {\n PaginationInput,\n SubAdapterSubmission,\n SubAgentKind,\n SubAgentStatus,\n SubAiAgent,\n SubAsset,\n SubBenchmarkDataPoint,\n SubBenchmarkFilterInput,\n SubCommunicationChannel,\n SubConnection,\n SubCreateAgentInput,\n SubCreateAssetInput,\n SubCreateChannelInput,\n SubCreateEntitlementInput,\n SubCreateInvoiceInput,\n SubCreatePaymentInstrumentInput,\n SubCreatePlanInput,\n SubCreatePolicyInput,\n SubCreateProviderInput,\n SubCreateRelationshipInput,\n SubCreateWorkflowInput,\n SubAgentRunResult,\n SubDecision,\n SubDecisionFilter,\n SubDecisionResult,\n SubConsolidateRelationshipsInput,\n SubConsolidationResult,\n SubSavingsOverview,\n SubEntitlement,\n SubInboxMessage,\n SubIngestMessageInput,\n SubInvoice,\n SubInvoiceStatus,\n SubMarketplaceReport,\n SubMessageCategory,\n SubModerationAction,\n SubPaymentInstrument,\n SubPlan,\n SubPolicy,\n SubProvider,\n SubProviderCategory,\n SubProviderStatus,\n SubRelationship,\n SubRelationshipKind,\n SubRelationshipStatus,\n SubRenewalEvent,\n SubReportStatus,\n SubSubmissionStatus,\n SubUpdateProviderInput,\n SubUpdateRelationshipInput,\n SubWorkflow,\n SubWorkflowStatus,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\nconst subQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n // A mutation just changed server state. fe-libs' graphqlFetch keeps a ~15s\n // global response micro-cache keyed by query+vars, so the react-query refetch\n // that an onSuccess triggers would otherwise be served the STALE pre-write\n // response (a newly created provider/relationship/agent wouldn't appear, a\n // just-marked-read message would still show unread) until the TTL expires.\n // Drop that cache here — centralized so no mutation call site can forget it.\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlFetchResponseCache();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n relationships: ['subscriberbot', 'relationships'] as const,\n relationship: (id: string) => ['subscriberbot', 'relationship', id] as const,\n providers: ['subscriberbot', 'providers'] as const,\n provider: (id: string) => ['subscriberbot', 'provider', id] as const,\n plans: ['subscriberbot', 'plans'] as const,\n benchmarks: (providerSlug: string) => ['subscriberbot', 'benchmarks', providerSlug] as const,\n inbox: ['subscriberbot', 'inbox'] as const,\n invoices: ['subscriberbot', 'invoices'] as const,\n paymentInstruments: ['subscriberbot', 'payment-instruments'] as const,\n channels: ['subscriberbot', 'channels'] as const,\n agents: ['subscriberbot', 'agents'] as const,\n agent: (id: string) => ['subscriberbot', 'agent', id] as const,\n decisions: ['subscriberbot', 'decisions'] as const,\n decision: (id: string) => ['subscriberbot', 'decision', id] as const,\n // Nested under `decisions` so every decision change (apply, dismiss, run)\n // also refreshes the savings dashboard.\n savings: ['subscriberbot', 'decisions', 'savings-overview'] as const,\n workflows: ['subscriberbot', 'workflows'] as const,\n renewals: ['subscriberbot', 'renewals'] as const,\n assets: ['subscriberbot', 'assets'] as const,\n entitlements: ['subscriberbot', 'entitlements'] as const,\n adapterSubmissions: ['subscriberbot', 'admin', 'adapter-submissions'] as const,\n marketplaceReports: ['subscriberbot', 'admin', 'marketplace-reports'] as const,\n} as const;\n\n// ============================================================================\n// Reusable gql field selections (kept inline-string for codegen-free strictness)\n// ============================================================================\n\nconst PROVIDER_FIELDS = `\n id name slug category status description industry region language websiteUrl\n logoFileId tags churnRiskScore vendorHealthScore workspaceId createdAt updatedAt\n`;\n\nconst PLAN_FIELDS = `\n id name description providerId amount currency interval features isActive\n workspaceId createdAt updatedAt\n`;\n\nconst RELATIONSHIP_FIELDS = `\n id label kind status amount currency interval autoRenew discoverySource notes\n tags healthScore ownerId providerId planId startedAt renewsAt trialEndsAt\n cancelledAt workspaceId createdAt updatedAt\n`;\n\nconst INBOX_FIELDS = `\n id subject preview category categoryConfidence channelId relationshipId\n externalId actionRequired isRead receivedAt workspaceId createdAt updatedAt\n`;\n\nconst INVOICE_FIELDS = `\n id number relationshipId status currency subtotal taxAmount total issueDate\n dueDate paidAt paymentInstrumentId workspaceId createdAt updatedAt\n`;\n\nconst AGENT_FIELDS = `\n id name description kind status ownerId lastRunAt workspaceId createdAt updatedAt\n`;\n\nconst DECISION_FIELDS = `\n id agentId runId relationshipId kind recommendation status rationale confidence\n estimatedSavings currency evidence proposedChange validationErrors invocationRef\n applyInvocationRef appliedAt appliedBy dismissedAt dismissedReason workspaceId\n createdAt updatedAt\n agent { id name kind status }\n relationship { id label status amount currency interval providerId planId }\n`;\n\nconst WORKFLOW_FIELDS = `\n id name description triggerEvent status runCount lastRunAt workspaceId\n createdAt updatedAt\n`;\n\nconst ENTITLEMENT_FIELDS = `\n id name description type relationshipId quota used unit isActive\n workspaceId createdAt updatedAt\n`;\n\nconst ASSET_FIELDS = `\n id name type relationshipId secretRef expiresAt workspaceId createdAt\n updatedAt file { id }\n`;\n\nconst PAYMENT_INSTRUMENT_FIELDS = `\n id label type status brand last4 expiryMonth expiryYear isDefault ownerId\n workspaceId createdAt updatedAt\n`;\n\nconst CHANNEL_FIELDS = `\n id label type status address ingestEnabled ownerId verifiedAt workspaceId\n createdAt updatedAt\n`;\n\nconst POLICY_FIELDS = `\n id name description action agentId condition isEnabled triggerCount\n lastTriggeredAt workspaceId createdAt updatedAt\n`;\n\nconst BENCHMARK_DATA_POINT_FIELDS = `\n id providerSlug providerName planName region currency interval amount\n contractSizeSeats sourceKind sourceLabel notes observedAt createdAt\n`;\n\nconst ADAPTER_SUBMISSION_FIELDS = `\n id workspaceId adapterId submitterWorkspaceId status notes createdAt updatedAt\n`;\n\nconst MARKETPLACE_REPORT_FIELDS = `\n id workspaceId listingId reporterWorkspaceId reason status resolutionReason\n createdAt updatedAt\n`;\n\n// ============================================================================\n// Relationships (the core domain object)\n// ============================================================================\n\nexport interface RelationshipListFilters {\n kind?: SubRelationshipKind;\n status?: SubRelationshipStatus;\n providerId?: string;\n ownerId?: string;\n /** Case-insensitive substring match on the relationship label (server-side). */\n search?: string;\n pagination?: PaginationInput;\n}\n\nexport const useRelationships = (filters: RelationshipListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.relationships, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListRelationships: SubConnection<SubRelationship>;\n }>(\n `query SubRelationships(\n $kind: SubRelationshipKind\n $status: SubRelationshipStatus\n $providerId: ID\n $ownerId: ID\n $search: String\n $pagination: PaginationInput\n ) {\n subListRelationships(\n kind: $kind\n status: $status\n providerId: $providerId\n ownerId: $ownerId\n search: $search\n pagination: $pagination\n ) {\n items {\n ${RELATIONSHIP_FIELDS}\n provider { id name category status logoFileId }\n plan { id name amount currency interval }\n }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n kind: filters.kind,\n status: filters.status,\n providerId: filters.providerId,\n ownerId: filters.ownerId,\n search: filters.search,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListRelationships;\n },\n });\n};\n\nexport const useRelationship = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.relationship(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await subQuery<{ subRelationship: SubRelationship | null }>(\n `query SubRelationship($id: ID!) {\n subRelationship(id: $id) {\n ${RELATIONSHIP_FIELDS}\n provider { ${PROVIDER_FIELDS} }\n plan { ${PLAN_FIELDS} }\n entitlements {\n id name description type relationshipId quota used unit isActive\n workspaceId createdAt updatedAt\n }\n assets {\n id name type relationshipId secretRef expiresAt workspaceId\n createdAt updatedAt file { id }\n }\n renewalEvents {\n id relationshipId eventType occursAt amount currency detail\n notified workspaceId createdAt updatedAt\n }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subRelationship;\n },\n });\n};\n\nexport const useCreateRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateRelationshipInput) => {\n const result = await subQuery<{ subCreateRelationship: SubRelationship }>(\n `mutation SubCreateRelationship($input: SubCreateRelationshipInput!) {\n subCreateRelationship(input: $input) { ${RELATIONSHIP_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateRelationship;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n },\n });\n};\n\nexport const useTransitionRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: SubRelationshipStatus }) => {\n const result = await subQuery<{ subTransitionRelationship: SubRelationship }>(\n `mutation SubTransitionRelationship($id: ID!, $status: SubRelationshipStatus!) {\n subTransitionRelationship(id: $id, status: $status) { ${RELATIONSHIP_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.subTransitionRelationship;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.id) });\n },\n });\n};\n\nexport const useCancelRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subCancelRelationship: SubRelationship }>(\n `mutation SubCancelRelationship($id: ID!) {\n subCancelRelationship(id: $id) { ${RELATIONSHIP_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subCancelRelationship;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.id) });\n },\n });\n};\n\nexport const useUpdateRelationship = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: SubUpdateRelationshipInput }) => {\n const result = await subQuery<{ subUpdateRelationship: SubRelationship }>(\n `mutation SubUpdateRelationship($id: ID!, $input: SubUpdateRelationshipInput!) {\n subUpdateRelationship(id: $id, input: $input) { ${RELATIONSHIP_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.subUpdateRelationship;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.id) });\n },\n });\n};\n\nexport const useRenewalEvents = (\n args: { relationshipId?: string; upcomingOnly?: boolean } = {}\n) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.renewals, args],\n queryFn: async () => {\n // subListRenewalEvents returns SubRenewalEventConnection (items/pageInfo),\n // not a bare list — matching every other subList* root field in this\n // schema (see useRelationships above). Selecting event fields directly\n // fails GraphQL validation server-side (`Cannot query field \"id\" on type\n // \"SubRenewalEventConnection\"`).\n const result = await subQuery<{\n subListRenewalEvents: SubConnection<SubRenewalEvent>;\n }>(\n `query SubRenewalEvents($relationshipId: ID, $upcomingOnly: Boolean) {\n subListRenewalEvents(relationshipId: $relationshipId, upcomingOnly: $upcomingOnly) {\n items {\n id relationshipId eventType occursAt amount currency detail notified\n workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { relationshipId: args.relationshipId, upcomingOnly: args.upcomingOnly },\n apiGatewayUrl\n );\n return result.subListRenewalEvents.items;\n },\n });\n};\n\nexport const useEntitlements = (relationshipId: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.entitlements, relationshipId],\n enabled: Boolean(relationshipId),\n queryFn: async () => {\n const result = await subQuery<{\n subListEntitlements: SubConnection<SubEntitlement>;\n }>(\n `query SubEntitlements($relationshipId: ID!) {\n subListEntitlements(relationshipId: $relationshipId) {\n items {\n id name description type relationshipId quota used unit isActive\n workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { relationshipId },\n apiGatewayUrl\n );\n return result.subListEntitlements.items;\n },\n });\n};\n\nexport const useCreateEntitlement = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateEntitlementInput) => {\n const result = await subQuery<{ subCreateEntitlement: SubEntitlement }>(\n `mutation SubCreateEntitlement($input: SubCreateEntitlementInput!) {\n subCreateEntitlement(input: $input) { ${ENTITLEMENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateEntitlement;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.entitlements });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.relationshipId) });\n },\n });\n};\n\nexport const useAssets = (relationshipId: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.assets, relationshipId],\n enabled: Boolean(relationshipId),\n queryFn: async () => {\n const result = await subQuery<{ subListAssets: SubConnection<SubAsset> }>(\n `query SubAssets($relationshipId: ID!) {\n subListAssets(relationshipId: $relationshipId) {\n items {\n id name type relationshipId secretRef expiresAt workspaceId\n createdAt updatedAt file { id }\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { relationshipId },\n apiGatewayUrl\n );\n return result.subListAssets.items;\n },\n });\n};\n\n/** Every asset in the workspace (Digital Asset Vault) — omits relationshipId. */\nexport const useAllAssets = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.assets, 'all'],\n queryFn: async () => {\n const result = await subQuery<{ subListAssets: SubConnection<SubAsset> }>(\n `query SubAllAssets {\n subListAssets { items { ${ASSET_FIELDS} } pageInfo { cursor hasMore } }\n }`,\n {},\n apiGatewayUrl\n );\n return result.subListAssets.items;\n },\n });\n};\n\nexport const useCreateAsset = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateAssetInput) => {\n const result = await subQuery<{ subCreateAsset: SubAsset }>(\n `mutation SubCreateAsset($input: SubCreateAssetInput!) {\n subCreateAsset(input: $input) { ${ASSET_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateAsset;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.assets });\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(data.relationshipId) });\n },\n });\n};\n\n/**\n * Upload a file to `wspace-files-svc` and return its File id, using the\n * platform-standard 3-step flow (initiateUpload → presigned PUT → completeUpload)\n * over the same `graphqlFetch` transport the rest of this MFE uses. Assets store\n * only the returned `fileId` on `SubCreateAssetInput`; blobs live in files-svc.\n *\n * `CompleteUploadInput.fileSize` is a REQUIRED BigInt (quota tracking) and must\n * match the value given to `initiateUpload` — we pass `String(file.size)` to both.\n */\nconst ASSET_FOLDER_PATH = '/SubscriberBot/Assets';\n\nexport const useUploadFile = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n const [progress, setProgress] = useState(0);\n const [isUploading, setIsUploading] = useState(false);\n\n const uploadFile = useCallback(\n async (file: File): Promise<{ fileId: string; fileName: string }> => {\n setIsUploading(true);\n setProgress(0);\n try {\n // 1. Resolve (or create) the destination folder — File.folderId is a real FK.\n const folderRes = await subQuery<{ ensureFolderByPath: { id: string } | null }>(\n `mutation SubEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }`,\n { path: ASSET_FOLDER_PATH },\n apiGatewayUrl\n );\n const folderId = folderRes.ensureFolderByPath?.id;\n if (!folderId) throw new Error('Could not resolve the upload folder.');\n\n const mimeType = file.type || 'application/octet-stream';\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : 'bin';\n\n // 2. Open a presigned upload session.\n const initiated = await subQuery<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } } | null;\n }>(\n `mutation SubInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n folderId,\n },\n },\n apiGatewayUrl\n );\n const uploadUrl = initiated.initiateUpload?.uploadUrl;\n const sessionId = initiated.initiateUpload?.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('The files service did not return an upload URL.');\n }\n\n // 3. PUT the raw bytes to storage. Content-Type must match what was signed.\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', mimeType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n setProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve();\n else reject(new Error(`Upload to storage failed (${xhr.status}).`));\n };\n xhr.onerror = () => reject(new Error('Upload to storage failed.'));\n xhr.send(file);\n });\n\n // 4. Finalize — creates the File record. fileSize must match initiateUpload.\n const completed = await subQuery<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null } | null;\n }>(\n `mutation SubCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId, fileSize: String(file.size), folderId } },\n apiGatewayUrl\n );\n const fileId = completed.completeUpload?.file?.id ?? completed.completeUpload?.fileId;\n if (!fileId) throw new Error('The upload could not be finalized.');\n return { fileId, fileName: completed.completeUpload?.file?.name ?? file.name };\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { uploadFile, progress, isUploading };\n};\n\n/**\n * Resolve a short-lived presigned download URL for a `wspace-files-svc` File id.\n * Never select the federated arg-less `File.downloadUrl` (it mints a URL beyond\n * S3's 7-day cap and 500s the response) — use the root `getFileDownloadUrl`\n * query with a TTL under 604800s instead.\n */\nexport const useFileDownloadUrl = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useCallback(\n async (fileId: string): Promise<string> => {\n const result = await subQuery<{ getFileDownloadUrl: string }>(\n `query SubFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }`,\n { id: fileId, expiresIn: 21600 },\n apiGatewayUrl\n );\n return result.getFileDownloadUrl;\n },\n [apiGatewayUrl]\n );\n};\n\n// ============================================================================\n// Providers (the global discovery registry)\n// ============================================================================\n\nexport interface ProviderListFilters {\n category?: SubProviderCategory;\n status?: SubProviderStatus;\n search?: string;\n pagination?: PaginationInput;\n}\n\nexport const useProviders = (filters: ProviderListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.providers, filters],\n queryFn: async () => {\n const result = await subQuery<{ subListProviders: SubConnection<SubProvider> }>(\n `query SubProviders(\n $category: SubProviderCategory\n $status: SubProviderStatus\n $search: String\n $pagination: PaginationInput\n ) {\n subListProviders(\n category: $category\n status: $status\n search: $search\n pagination: $pagination\n ) {\n items { ${PROVIDER_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n category: filters.category,\n status: filters.status,\n search: filters.search,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListProviders;\n },\n });\n};\n\nexport const useProvider = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.provider(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await subQuery<{ subProvider: SubProvider | null }>(\n `query SubProvider($id: ID!) {\n subProvider(id: $id) {\n ${PROVIDER_FIELDS}\n plans { ${PLAN_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subProvider;\n },\n });\n};\n\nexport const useCreateProvider = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateProviderInput) => {\n const result = await subQuery<{ subCreateProvider: SubProvider }>(\n `mutation SubCreateProvider($input: SubCreateProviderInput!) {\n subCreateProvider(input: $input) { ${PROVIDER_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateProvider;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.providers });\n },\n });\n};\n\nexport const useUpdateProvider = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: SubUpdateProviderInput }) => {\n const result = await subQuery<{ subUpdateProvider: SubProvider }>(\n `mutation SubUpdateProvider($id: ID!, $input: SubUpdateProviderInput!) {\n subUpdateProvider(id: $id, input: $input) { ${PROVIDER_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.subUpdateProvider;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.providers });\n void queryClient.invalidateQueries({ queryKey: KEY.provider(data.id) });\n },\n });\n};\n\nexport const useArchiveProvider = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subArchiveProvider: boolean }>(\n `mutation SubArchiveProvider($id: ID!) {\n subArchiveProvider(id: $id)\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subArchiveProvider;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.providers });\n },\n });\n};\n\nexport const usePlans = (providerId: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.plans, providerId],\n enabled: Boolean(providerId),\n queryFn: async () => {\n const result = await subQuery<{ subListPlans: SubConnection<SubPlan> }>(\n `query SubPlans($providerId: ID) {\n subListPlans(providerId: $providerId) { items { ${PLAN_FIELDS} } pageInfo { cursor hasMore } }\n }`,\n { providerId },\n apiGatewayUrl\n );\n return result.subListPlans.items;\n },\n });\n};\n\nexport const useCreatePlan = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreatePlanInput) => {\n const result = await subQuery<{ subCreatePlan: SubPlan }>(\n `mutation SubCreatePlan($input: SubCreatePlanInput!) {\n subCreatePlan(input: $input) { ${PLAN_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreatePlan;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.plans });\n void queryClient.invalidateQueries({ queryKey: KEY.provider(data.providerId) });\n },\n });\n};\n\n/**\n * Observed price points for one provider, newest first from the server.\n *\n * Backs the provider \"Price history\" card. The benchmark dataset is read\n * GLOBALLY (`@rbac(..., public: true)` on the svc) — it is the same for every\n * workspace — so nothing here is workspace scoped. Matching is by\n * `providerSlug`, which is an exact match server-side; `providerName` is a\n * fuzzy `contains` match and would risk attributing another vendor's prices to\n * this provider, so the query stays disabled until a slug exists.\n */\nexport const useBenchmarkDataPoints = (providerSlug: string | null | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.benchmarks(providerSlug ?? ''),\n enabled: Boolean(providerSlug),\n queryFn: async () => {\n const filter: SubBenchmarkFilterInput = { providerSlug: providerSlug ?? undefined };\n const result = await subQuery<{\n subListBenchmarkDataPoints: SubConnection<SubBenchmarkDataPoint>;\n }>(\n `query SubBenchmarkDataPoints(\n $filter: SubBenchmarkFilterInput\n $pagination: PaginationInput\n ) {\n subListBenchmarkDataPoints(filter: $filter, pagination: $pagination) {\n items { ${BENCHMARK_DATA_POINT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { filter, pagination: { limit: 100, offset: 0 } },\n apiGatewayUrl\n );\n return result.subListBenchmarkDataPoints.items;\n },\n });\n};\n\n// ============================================================================\n// Universal Inbox\n// ============================================================================\n\nexport interface InboxListFilters {\n category?: SubMessageCategory;\n channelId?: string;\n relationshipId?: string;\n unreadOnly?: boolean;\n pagination?: PaginationInput;\n}\n\nexport const useInboxMessages = (filters: InboxListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.inbox, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListInboxMessages: SubConnection<SubInboxMessage>;\n }>(\n `query SubInboxMessages(\n $category: SubMessageCategory\n $channelId: ID\n $relationshipId: ID\n $unreadOnly: Boolean\n $pagination: PaginationInput\n ) {\n subListInboxMessages(\n category: $category\n channelId: $channelId\n relationshipId: $relationshipId\n unreadOnly: $unreadOnly\n pagination: $pagination\n ) {\n items { ${INBOX_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n category: filters.category,\n channelId: filters.channelId,\n relationshipId: filters.relationshipId,\n unreadOnly: filters.unreadOnly,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListInboxMessages;\n },\n });\n};\n\nexport const useMarkMessageRead = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subMarkMessageRead: SubInboxMessage }>(\n `mutation SubMarkMessageRead($id: ID!) {\n subMarkMessageRead(id: $id) { ${INBOX_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subMarkMessageRead;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.inbox });\n },\n });\n};\n\nexport const useIngestMessage = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubIngestMessageInput) => {\n const result = await subQuery<{ subIngestMessage: SubInboxMessage }>(\n `mutation SubIngestMessage($input: SubIngestMessageInput!) {\n subIngestMessage(input: $input) { ${INBOX_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subIngestMessage;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.inbox });\n },\n });\n};\n\n// ============================================================================\n// Invoices (Universal Billing Layer)\n// ============================================================================\n\nexport interface InvoiceListFilters {\n status?: SubInvoiceStatus;\n relationshipId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useInvoices = (\n filters: InvoiceListFilters = {},\n options: { enabled?: boolean } = {}\n) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.invoices, filters],\n enabled: options.enabled ?? true,\n queryFn: async () => {\n const result = await subQuery<{ subListInvoices: SubConnection<SubInvoice> }>(\n `query SubInvoices(\n $status: SubInvoiceStatus\n $relationshipId: ID\n $pagination: PaginationInput\n ) {\n subListInvoices(\n status: $status\n relationshipId: $relationshipId\n pagination: $pagination\n ) {\n items { ${INVOICE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n relationshipId: filters.relationshipId,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListInvoices;\n },\n });\n};\n\nexport const useMarkInvoicePaid = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subMarkInvoicePaid: SubInvoice }>(\n `mutation SubMarkInvoicePaid($id: ID!) {\n subMarkInvoicePaid(id: $id) { ${INVOICE_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subMarkInvoicePaid;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.invoices });\n },\n });\n};\n\nexport const useCreateInvoice = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateInvoiceInput) => {\n const result = await subQuery<{ subCreateInvoice: SubInvoice }>(\n `mutation SubCreateInvoice($input: SubCreateInvoiceInput!) {\n subCreateInvoice(input: $input) { ${INVOICE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateInvoice;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.invoices });\n },\n });\n};\n\n// ============================================================================\n// Payment instruments & communication channels\n// ============================================================================\n\nexport const usePaymentInstruments = (ownerId?: string) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.paymentInstruments, ownerId],\n queryFn: async () => {\n const result = await subQuery<{\n subListPaymentInstruments: SubConnection<SubPaymentInstrument>;\n }>(\n `query SubPaymentInstruments($ownerId: ID) {\n subListPaymentInstruments(ownerId: $ownerId) {\n items {\n id label type status brand last4 expiryMonth expiryYear isDefault\n ownerId workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { ownerId },\n apiGatewayUrl\n );\n return result.subListPaymentInstruments.items;\n },\n });\n};\n\nexport const useCreatePaymentInstrument = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreatePaymentInstrumentInput) => {\n const result = await subQuery<{ subCreatePaymentInstrument: SubPaymentInstrument }>(\n `mutation SubCreatePaymentInstrument($input: SubCreatePaymentInstrumentInput!) {\n subCreatePaymentInstrument(input: $input) { ${PAYMENT_INSTRUMENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreatePaymentInstrument;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.paymentInstruments });\n },\n });\n};\n\nexport const useRemovePaymentInstrument = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subRemovePaymentInstrument: boolean }>(\n `mutation SubRemovePaymentInstrument($id: ID!) {\n subRemovePaymentInstrument(id: $id)\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subRemovePaymentInstrument;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.paymentInstruments });\n },\n });\n};\n\nexport const useChannels = (ownerId?: string) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.channels, ownerId],\n queryFn: async () => {\n const result = await subQuery<{\n subListChannels: SubConnection<SubCommunicationChannel>;\n }>(\n `query SubChannels($ownerId: ID) {\n subListChannels(ownerId: $ownerId) {\n items {\n id label type status address ingestEnabled ownerId verifiedAt\n workspaceId createdAt updatedAt\n }\n pageInfo { cursor hasMore }\n }\n }`,\n { ownerId },\n apiGatewayUrl\n );\n return result.subListChannels.items;\n },\n });\n};\n\nexport const useCreateChannel = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateChannelInput) => {\n const result = await subQuery<{ subCreateChannel: SubCommunicationChannel }>(\n `mutation SubCreateChannel($input: SubCreateChannelInput!) {\n subCreateChannel(input: $input) { ${CHANNEL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateChannel;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.channels });\n },\n });\n};\n\nexport const useVerifyChannel = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subVerifyChannel: SubCommunicationChannel }>(\n `mutation SubVerifyChannel($id: ID!) {\n subVerifyChannel(id: $id) { ${CHANNEL_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subVerifyChannel;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.channels });\n },\n });\n};\n\n// ============================================================================\n// AI Agents & workflows (automation)\n// ============================================================================\n\nexport interface AgentListFilters {\n kind?: SubAgentKind;\n status?: SubAgentStatus;\n ownerId?: string;\n}\n\nexport const useAgents = (filters: AgentListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.agents, filters],\n queryFn: async () => {\n const result = await subQuery<{ subListAgents: SubConnection<SubAiAgent> }>(\n `query SubAgents($kind: SubAgentKind, $status: SubAgentStatus, $ownerId: ID) {\n subListAgents(kind: $kind, status: $status, ownerId: $ownerId) {\n items { ${AGENT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { kind: filters.kind, status: filters.status, ownerId: filters.ownerId },\n apiGatewayUrl\n );\n return result.subListAgents.items;\n },\n });\n};\n\nexport const useAgent = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.agent(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await subQuery<{ subAgent: SubAiAgent | null }>(\n `query SubAgent($id: ID!) {\n subAgent(id: $id) {\n ${AGENT_FIELDS}\n policies { ${POLICY_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subAgent;\n },\n });\n};\n\nexport const useCreateAgent = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateAgentInput) => {\n const result = await subQuery<{ subCreateAgent: SubAiAgent }>(\n `mutation SubCreateAgent($input: SubCreateAgentInput!) {\n subCreateAgent(input: $input) { ${AGENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateAgent;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n },\n });\n};\n\nexport const useSetAgentStatus = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: SubAgentStatus }) => {\n const result = await subQuery<{ subSetAgentStatus: SubAiAgent }>(\n `mutation SubSetAgentStatus($id: ID!, $status: SubAgentStatus!) {\n subSetAgentStatus(id: $id, status: $status) { ${AGENT_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.subSetAgentStatus;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n void queryClient.invalidateQueries({ queryKey: KEY.agent(data.id) });\n },\n });\n};\n\nexport const useCreatePolicy = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreatePolicyInput) => {\n const result = await subQuery<{ subCreatePolicy: SubPolicy }>(\n `mutation SubCreatePolicy($input: SubCreatePolicyInput!) {\n subCreatePolicy(input: $input) { ${POLICY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreatePolicy;\n },\n onSuccess: (data) => {\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n void queryClient.invalidateQueries({ queryKey: KEY.agent(data.agentId) });\n },\n });\n};\n\n// ----------------------------------------------------------------------------\n// Decision queue (BOFF-3634 engine). Every hook here reads or writes the real\n// `subListDecisions` / `subDecision` / `subRunAgent` / `subApplyDecision` /\n// `subDismissDecision` operations — no placeholder data.\n// ----------------------------------------------------------------------------\n\n/** Upper bound for one queue page; the svc caps `limit` server-side as well. */\nconst DECISION_PAGE_LIMIT = 100;\n\nexport const useDecisions = (\n filter: SubDecisionFilter = {},\n options: { enabled?: boolean } = {}\n) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.decisions, filter],\n enabled: options.enabled ?? true,\n // A decision engine outage must not retry-storm the gateway; the page\n // shows the degradation banner after the first failure.\n retry: 1,\n queryFn: async () => {\n const result = await subQuery<{ subListDecisions: SubConnection<SubDecision> }>(\n `query SubDecisions($filter: SubDecisionFilter, $pagination: PaginationInput) {\n subListDecisions(filter: $filter, pagination: $pagination) {\n items { ${DECISION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { filter, pagination: { limit: DECISION_PAGE_LIMIT } },\n apiGatewayUrl\n );\n return result.subListDecisions;\n },\n });\n};\n\nexport const useDecision = (id: string | undefined) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.decision(id ?? ''),\n enabled: Boolean(id),\n retry: 1,\n queryFn: async () => {\n const result = await subQuery<{ subDecision: SubDecision | null }>(\n `query SubDecision($id: ID!) {\n subDecision(id: $id) { ${DECISION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subDecision;\n },\n });\n};\n\n/** Invalidate everything a decision change can affect. */\nconst invalidateDecisionViews = (\n queryClient: ReturnType<typeof useQueryClient>,\n ids: { decisionId?: string; agentId?: string; relationshipId?: string | null }\n) => {\n void queryClient.invalidateQueries({ queryKey: KEY.decisions });\n if (ids.decisionId)\n void queryClient.invalidateQueries({ queryKey: KEY.decision(ids.decisionId) });\n if (ids.agentId) {\n void queryClient.invalidateQueries({ queryKey: KEY.agent(ids.agentId) });\n void queryClient.invalidateQueries({ queryKey: KEY.agents });\n }\n if (ids.relationshipId) {\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(ids.relationshipId) });\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n }\n};\n\n/**\n * Run an agent. The UI only ever requests a dry run: decisions are persisted\n * for review and no relationship changes. Live runs stay with the\n * AUTONOMOUS posture and its fail-closed gate on the server.\n */\nexport const useRunAgent = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subRunAgent: SubAgentRunResult }>(\n `mutation SubRunAgent($id: ID!, $dryRun: Boolean) {\n subRunAgent(id: $id, dryRun: $dryRun) {\n runId agentId kind dryRun evaluated proposed flagged applied truncated\n invocationRef ranAt\n }\n }`,\n { id, dryRun: true },\n apiGatewayUrl\n );\n return result.subRunAgent;\n },\n onSuccess: (data) => invalidateDecisionViews(queryClient, { agentId: data.agentId }),\n });\n};\n\nexport const useApplyDecision = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subApplyDecision: SubDecisionResult }>(\n `mutation SubApplyDecision($id: ID!) {\n subApplyDecision(id: $id) {\n decision { ${DECISION_FIELDS} }\n relationship { id label status }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subApplyDecision;\n },\n onSuccess: (data) =>\n invalidateDecisionViews(queryClient, {\n decisionId: data.decision.id,\n agentId: data.decision.agentId,\n relationshipId: data.decision.relationshipId,\n }),\n // A refused apply (stale / already applied) still changes the decision's\n // server state, so the queue must refetch either way.\n onError: () => void queryClient.invalidateQueries({ queryKey: KEY.decisions }),\n });\n};\n\nexport const useDismissDecision = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, reason }: { id: string; reason?: string }) => {\n const result = await subQuery<{ subDismissDecision: SubDecision }>(\n `mutation SubDismissDecision($id: ID!, $reason: String) {\n subDismissDecision(id: $id, reason: $reason) { ${DECISION_FIELDS} }\n }`,\n { id, reason: reason?.trim() ? reason.trim() : undefined },\n apiGatewayUrl\n );\n return result.subDismissDecision;\n },\n onSuccess: (data) =>\n invalidateDecisionViews(queryClient, { decisionId: data.id, agentId: data.agentId }),\n });\n};\n\n// ----------------------------------------------------------------------------\n// Savings dashboard (BOFF-4001): `subSavingsOverview`,\n// `subConsolidateRelationships` and `subDismissBundle`.\n// ----------------------------------------------------------------------------\n\nconst SAVINGS_OVERVIEW_FIELDS = `\n generatedAt openDecisions truncated\n totals { currency annualSavings fromDecisions fromBundles }\n groups { recommendation currency count annualSavings }\n bundles {\n id type key title currency keepRelationshipId dropRelationshipIds\n annualSpend annualSavings confidence rationale\n members {\n relationshipId label providerId providerName amount currency interval\n annualCost capabilities\n }\n }\n`;\n\nexport const useSavingsOverview = () => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: KEY.savings,\n retry: 1,\n queryFn: async () => {\n const result = await subQuery<{ subSavingsOverview: SubSavingsOverview }>(\n `query SubSavingsOverview { subSavingsOverview { ${SAVINGS_OVERVIEW_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.subSavingsOverview;\n },\n });\n};\n\n/** Keep one subscription (optionally on a bundled plan) and cancel the others. */\nexport const useConsolidateRelationships = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubConsolidateRelationshipsInput) => {\n const result = await subQuery<{ subConsolidateRelationships: SubConsolidationResult }>(\n `mutation SubConsolidateRelationships($input: SubConsolidateRelationshipsInput!) {\n subConsolidateRelationships(input: $input) {\n kept { id label status amount currency interval }\n cancelled { id label status }\n failed { relationshipId message }\n estimatedSavings currency\n }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subConsolidateRelationships;\n },\n // Cancels can partly succeed, so refresh on either outcome.\n onSettled: (_data, _error, input) => {\n void queryClient.invalidateQueries({ queryKey: KEY.decisions });\n void queryClient.invalidateQueries({ queryKey: KEY.relationships });\n for (const id of [input.keepRelationshipId, ...input.cancelRelationshipIds]) {\n void queryClient.invalidateQueries({ queryKey: KEY.relationship(id) });\n }\n },\n });\n};\n\nexport const useDismissBundle = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, reason }: { id: string; reason?: string }) => {\n const result = await subQuery<{ subDismissBundle: boolean }>(\n `mutation SubDismissBundle($id: ID!, $reason: String) {\n subDismissBundle(id: $id, reason: $reason)\n }`,\n { id, reason: reason?.trim() ? reason.trim() : undefined },\n apiGatewayUrl\n );\n return result.subDismissBundle;\n },\n onSuccess: () => void queryClient.invalidateQueries({ queryKey: KEY.savings }),\n });\n};\n\nexport const useWorkflows = (status?: SubWorkflowStatus) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.workflows, status],\n queryFn: async () => {\n const result = await subQuery<{ subListWorkflows: SubConnection<SubWorkflow> }>(\n `query SubWorkflows($status: SubWorkflowStatus) {\n subListWorkflows(status: $status) { items { ${WORKFLOW_FIELDS} } pageInfo { cursor hasMore } }\n }`,\n { status },\n apiGatewayUrl\n );\n return result.subListWorkflows.items;\n },\n });\n};\n\nexport const useCreateWorkflow = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (input: SubCreateWorkflowInput) => {\n const result = await subQuery<{ subCreateWorkflow: SubWorkflow }>(\n `mutation SubCreateWorkflow($input: SubCreateWorkflowInput!) {\n subCreateWorkflow(input: $input) { ${WORKFLOW_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.subCreateWorkflow;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.workflows });\n },\n });\n};\n\nexport const useActivateWorkflow = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await subQuery<{ subActivateWorkflow: SubWorkflow }>(\n `mutation SubActivateWorkflow($id: ID!) {\n subActivateWorkflow(id: $id) { ${WORKFLOW_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.subActivateWorkflow;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.workflows });\n },\n });\n};\n\n// ============================================================================\n// Admin governance (adapter approvals + marketplace moderation)\n// ============================================================================\n\nexport interface AdapterSubmissionListFilters {\n status?: SubSubmissionStatus;\n pagination?: PaginationInput;\n}\n\nexport const useAdapterSubmissions = (filters: AdapterSubmissionListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.adapterSubmissions, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListAdapterSubmissions: SubConnection<SubAdapterSubmission>;\n }>(\n `query SubAdapterSubmissions($status: SubSubmissionStatus, $pagination: PaginationInput) {\n subListAdapterSubmissions(status: $status, pagination: $pagination) {\n items { ${ADAPTER_SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListAdapterSubmissions;\n },\n });\n};\n\nexport const useApproveAdapter = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, notes }: { id: string; notes?: string }) => {\n const result = await subQuery<{ subApproveAdapter: SubAdapterSubmission }>(\n `mutation SubApproveAdapter($id: ID!, $notes: String) {\n subApproveAdapter(id: $id, notes: $notes) { ${ADAPTER_SUBMISSION_FIELDS} }\n }`,\n { id, notes },\n apiGatewayUrl\n );\n return result.subApproveAdapter;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.adapterSubmissions });\n },\n });\n};\n\nexport const useRejectAdapter = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({ id, reason }: { id: string; reason: string }) => {\n const result = await subQuery<{ subRejectAdapter: SubAdapterSubmission }>(\n `mutation SubRejectAdapter($id: ID!, $reason: String!) {\n subRejectAdapter(id: $id, reason: $reason) { ${ADAPTER_SUBMISSION_FIELDS} }\n }`,\n { id, reason },\n apiGatewayUrl\n );\n return result.subRejectAdapter;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.adapterSubmissions });\n },\n });\n};\n\nexport interface MarketplaceReportListFilters {\n status?: SubReportStatus;\n pagination?: PaginationInput;\n}\n\nexport const useMarketplaceReports = (filters: MarketplaceReportListFilters = {}) => {\n const { apiGatewayUrl } = useSubscriberBot();\n return useQuery({\n queryKey: [...KEY.marketplaceReports, filters],\n queryFn: async () => {\n const result = await subQuery<{\n subListMarketplaceReports: SubConnection<SubMarketplaceReport>;\n }>(\n `query SubMarketplaceReports($status: SubReportStatus, $pagination: PaginationInput) {\n subListMarketplaceReports(status: $status, pagination: $pagination) {\n items { ${MARKETPLACE_REPORT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n pagination: filters.pagination ?? { limit: 50, offset: 0 },\n },\n apiGatewayUrl\n );\n return result.subListMarketplaceReports;\n },\n });\n};\n\nexport const useModerateListing = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useSubscriberBot();\n return useMutation({\n mutationFn: async ({\n id,\n action,\n reason,\n }: {\n id: string;\n action: SubModerationAction;\n reason: string;\n }) => {\n const result = await subQuery<{ subModerateListing: SubMarketplaceReport }>(\n `mutation SubModerateListing($id: ID!, $action: SubModerationAction!, $reason: String!) {\n subModerateListing(id: $id, action: $action, reason: $reason) { ${MARKETPLACE_REPORT_FIELDS} }\n }`,\n { id, action, reason },\n apiGatewayUrl\n );\n return result.subModerateListing;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: KEY.marketplaceReports });\n },\n });\n};\n"],"mappings":"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"}
@@ -185,7 +185,7 @@ var k = { status: "PROPOSED" }, A = () => {
185
185
  children: [/* @__PURE__ */ E(S, {
186
186
  className: "h-4 w-4",
187
187
  "aria-hidden": !0
188
- }), A("savings.bundles.empty", "No overlapping subscriptions found. Duplicate accounts with one provider, or several streaming, AI, news, telecom or education services, show up here.")]
188
+ }), A("savings.bundles.empty", "No overlapping subscriptions found. Duplicate accounts with one provider, different services tagged with the same capability, and subscriptions whose features another one already includes show up here.")]
189
189
  }) : /* @__PURE__ */ E("div", {
190
190
  className: "grid grid-cols-1 gap-3 lg:grid-cols-2",
191
191
  "data-testid": "savings-bundles",
@@ -1 +1 @@
1
- {"version":3,"file":"SavingsPage.js","names":[],"sources":["../../../src/subscriberbot/pages/SavingsPage.tsx"],"sourcesContent":["/**\n * Cost optimization dashboard (BOFF-4001)\n * @module subscriberbot/pages\n *\n * `/subscriberbot/savings` — \"how much can I save this year, and what should I\n * do about it\". The headline yearly saving and the bundle suggestions come\n * from `subSavingsOverview`; the action cards are the open (PROPOSED)\n * decisions from `subListDecisions`, grouped into Cancel unused, Downgrade\n * plan, Switch to annual and Other. Apply / Dismiss run `subApplyDecision` /\n * `subDismissDecision`; a bundle is consolidated with\n * `subConsolidateRelationships` or hidden with `subDismissBundle`. Nothing on\n * this page is placeholder data.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Bot, Combine, PiggyBank, RefreshCw } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, EmptyState, LoadingView, Section } from '../components';\nimport { AiDisclaimer, DecisionsUnavailableBanner } from '../components/decisions';\nimport {\n BundleCard,\n SAVINGS_SECTIONS,\n SavingsCard,\n groupDecisions,\n headlineAmounts,\n joinAmounts,\n perMonth,\n savingsByCurrency,\n} from '../components/savings';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport { useDecisions, useSavingsOverview } from '../hooks/useSubscriberBotApi';\nimport { useTr } from '../hooks/useTr';\nimport { formatCurrency } from '../utils/formatters';\nimport type { SubConsolidationResult, SubDecisionFilter } from '../types';\n\nconst OPEN_FILTER: SubDecisionFilter = { status: 'PROPOSED' };\n\nexport const SavingsPage: FC = () => {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const overview = useSavingsOverview();\n const decisions = useDecisions(OPEN_FILTER);\n const [notice, setNotice] = useState<string | null>(null);\n\n const grouped = useMemo(() => groupDecisions(decisions.data?.items ?? []), [decisions.data]);\n const bundles = overview.data?.bundles ?? [];\n const totals = overview.data?.totals ?? [];\n const headline = joinAmounts(headlineAmounts(totals), formatCurrency);\n const monthly = joinAmounts(\n totals.map((t) => ({ currency: t.currency, amount: perMonth(t.annualSavings) })),\n formatCurrency\n );\n const fromDecisions = joinAmounts(\n totals.map((t) => ({ currency: t.currency, amount: t.fromDecisions })),\n formatCurrency\n );\n const fromBundles = joinAmounts(\n totals.map((t) => ({ currency: t.currency, amount: t.fromBundles })),\n formatCurrency\n );\n const openCount = Object.values(grouped).reduce((n, list) => n + list.length, 0);\n const isLoading = overview.isLoading || decisions.isLoading;\n const isError = overview.isError || decisions.isError;\n const error = overview.error ?? decisions.error;\n\n const onConsolidated = (result: SubConsolidationResult) => {\n const saved = formatCurrency(result.estimatedSavings, result.currency);\n setNotice(\n result.failed.length > 0\n ? tr(\n 'savings.bundle.partial',\n 'Kept \"{{keep}}\" and cancelled {{count}} subscription(s); {{failed}} could not be cancelled and are unchanged.',\n {\n keep: result.kept.label,\n count: result.cancelled.length,\n failed: result.failed.length,\n }\n )\n : tr(\n 'savings.bundle.done',\n 'Kept \"{{keep}}\" and cancelled {{count}} subscription(s). Estimated saving: {{amount}} a year.',\n { keep: result.kept.label, count: result.cancelled.length, amount: saved }\n )\n );\n };\n\n const refresh = () => {\n void overview.refetch();\n void decisions.refetch();\n };\n\n return (\n <PageLayout\n title={tr('savings.title', 'Optimize your spend')}\n description={tr(\n 'savings.description',\n 'What you could save this year and the specific changes that get you there. Every change asks you to confirm first.'\n )}\n actions={\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={refresh}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className=\"h-4 w-4\" aria-hidden /> {tr('savings.refresh', 'Refresh')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Bot className=\"h-4 w-4\" aria-hidden /> {tr('savings.runAgents', 'Run agents')}\n </button>\n </div>\n }\n >\n <AiDisclaimer />\n\n {isLoading ? (\n <LoadingView label={tr('savings.loading', 'Finding savings…')} />\n ) : isError ? (\n <div className=\"mt-6\">\n <DecisionsUnavailableBanner\n message={error instanceof Error ? error.message : undefined}\n onRetry={refresh}\n />\n </div>\n ) : (\n <>\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"mt-6 flex flex-col gap-4 p-6 sm:flex-row sm:items-end sm:justify-between\"\n data-testid=\"savings-headline\"\n >\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-2 text-sm font-medium text-text-secondary\">\n <PiggyBank className=\"h-4 w-4\" aria-hidden />\n {tr('savings.headline.label', 'Estimated savings this year')}\n </p>\n <p\n className=\"mt-1 break-words text-3xl font-bold tabular-nums text-status-success-text sm:text-4xl\"\n data-testid=\"savings-headline-amount\"\n >\n {headline ?? formatCurrency(0, 'USD')}\n </p>\n {monthly && (\n <p className=\"mt-1 text-sm tabular-nums text-text-secondary\">\n {tr('savings.headline.perMonth', 'About {{amount}} a month', { amount: monthly })}\n </p>\n )}\n </div>\n <dl className=\"grid shrink-0 grid-cols-3 gap-4 text-sm\">\n <div>\n <dt className=\"text-xs text-text-secondary\">\n {tr('savings.headline.actions', 'Actions')}\n </dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">{openCount}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-secondary\">\n {tr('savings.headline.fromDecisions', 'From recommendations')}\n </dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">\n {fromDecisions ?? '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-secondary\">\n {tr('savings.headline.fromBundles', 'From bundles')}\n </dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">\n {fromBundles ?? '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {overview.data?.truncated && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'savings.truncated',\n 'Your portfolio is large, so this total covers the newest recommendations and subscriptions only.'\n )}\n </p>\n )}\n {decisions.data?.pageInfo.hasMore && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'savings.moreDecisions',\n 'Showing the newest {{count}} recommendations. The full queue is on the AI Decisions page.',\n { count: decisions.data.items.length }\n )}\n </p>\n )}\n\n {notice && (\n <div\n role=\"status\"\n className=\"mt-4 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle px-4 py-2 text-sm text-status-success-text\"\n data-testid=\"savings-notice\"\n >\n {notice}\n </div>\n )}\n\n {openCount === 0 && bundles.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n title={tr('savings.empty.title', 'No savings found right now')}\n description={tr(\n 'savings.empty.description',\n 'Run your cost, renewal, vendor and discovery agents to look for savings. Running an agent never changes a subscription on its own.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('savings.empty.action', 'Go to agents')}\n </button>\n }\n />\n </div>\n ) : (\n <div className=\"mt-6\">\n {SAVINGS_SECTIONS.map((section) => {\n const items = grouped[section.id];\n if (items.length === 0) return null;\n const sum = joinAmounts(savingsByCurrency(items), formatCurrency);\n return (\n <Section\n key={section.id}\n title={`${tr(section.key, section.fallback)} (${items.length})`}\n description={tr(section.descriptionKey, section.descriptionFallback)}\n action={\n sum ? (\n <span className=\"text-sm font-semibold tabular-nums text-status-success-text\">\n {tr('savings.sectionTotal', '{{amount}} a year', { amount: sum })}\n </span>\n ) : undefined\n }\n >\n <div\n className=\"grid grid-cols-1 gap-3 lg:grid-cols-2\"\n data-testid={`savings-section-${section.id}`}\n >\n {items.map((d) => (\n <SavingsCard key={d.id} decision={d} />\n ))}\n </div>\n </Section>\n );\n })}\n\n <Section\n title={tr('savings.bundles.title', 'Bundle recommendations')}\n description={tr(\n 'savings.bundles.description',\n 'Subscriptions that overlap and could be merged onto a single plan.'\n )}\n >\n {bundles.length === 0 ? (\n <p\n className=\"flex items-center gap-2 rounded-lg border border-border-subtle px-4 py-3 text-sm text-text-secondary\"\n data-testid=\"bundles-empty\"\n >\n <Combine className=\"h-4 w-4\" aria-hidden />\n {tr(\n 'savings.bundles.empty',\n 'No overlapping subscriptions found. Duplicate accounts with one provider, or several streaming, AI, news, telecom or education services, show up here.'\n )}\n </p>\n ) : (\n <div\n className=\"grid grid-cols-1 gap-3 lg:grid-cols-2\"\n data-testid=\"savings-bundles\"\n >\n {bundles.map((b) => (\n <BundleCard key={b.id} bundle={b} onConsolidated={onConsolidated} />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"SavingsPage.js","names":[],"sources":["../../../src/subscriberbot/pages/SavingsPage.tsx"],"sourcesContent":["/**\n * Cost optimization dashboard (BOFF-4001)\n * @module subscriberbot/pages\n *\n * `/subscriberbot/savings` — \"how much can I save this year, and what should I\n * do about it\". The headline yearly saving and the bundle suggestions come\n * from `subSavingsOverview` (bundles are the BOFF-3703 overlap groups); the action cards are the open (PROPOSED)\n * decisions from `subListDecisions`, grouped into Cancel unused, Downgrade\n * plan, Switch to annual and Other. Apply / Dismiss run `subApplyDecision` /\n * `subDismissDecision`; a bundle is consolidated with\n * `subConsolidateRelationships` or hidden with `subDismissBundle`. Nothing on\n * this page is placeholder data.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Bot, Combine, PiggyBank, RefreshCw } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, EmptyState, LoadingView, Section } from '../components';\nimport { AiDisclaimer, DecisionsUnavailableBanner } from '../components/decisions';\nimport {\n BundleCard,\n SAVINGS_SECTIONS,\n SavingsCard,\n groupDecisions,\n headlineAmounts,\n joinAmounts,\n perMonth,\n savingsByCurrency,\n} from '../components/savings';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport { useDecisions, useSavingsOverview } from '../hooks/useSubscriberBotApi';\nimport { useTr } from '../hooks/useTr';\nimport { formatCurrency } from '../utils/formatters';\nimport type { SubConsolidationResult, SubDecisionFilter } from '../types';\n\nconst OPEN_FILTER: SubDecisionFilter = { status: 'PROPOSED' };\n\nexport const SavingsPage: FC = () => {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const overview = useSavingsOverview();\n const decisions = useDecisions(OPEN_FILTER);\n const [notice, setNotice] = useState<string | null>(null);\n\n const grouped = useMemo(() => groupDecisions(decisions.data?.items ?? []), [decisions.data]);\n const bundles = overview.data?.bundles ?? [];\n const totals = overview.data?.totals ?? [];\n const headline = joinAmounts(headlineAmounts(totals), formatCurrency);\n const monthly = joinAmounts(\n totals.map((t) => ({ currency: t.currency, amount: perMonth(t.annualSavings) })),\n formatCurrency\n );\n const fromDecisions = joinAmounts(\n totals.map((t) => ({ currency: t.currency, amount: t.fromDecisions })),\n formatCurrency\n );\n const fromBundles = joinAmounts(\n totals.map((t) => ({ currency: t.currency, amount: t.fromBundles })),\n formatCurrency\n );\n const openCount = Object.values(grouped).reduce((n, list) => n + list.length, 0);\n const isLoading = overview.isLoading || decisions.isLoading;\n const isError = overview.isError || decisions.isError;\n const error = overview.error ?? decisions.error;\n\n const onConsolidated = (result: SubConsolidationResult) => {\n const saved = formatCurrency(result.estimatedSavings, result.currency);\n setNotice(\n result.failed.length > 0\n ? tr(\n 'savings.bundle.partial',\n 'Kept \"{{keep}}\" and cancelled {{count}} subscription(s); {{failed}} could not be cancelled and are unchanged.',\n {\n keep: result.kept.label,\n count: result.cancelled.length,\n failed: result.failed.length,\n }\n )\n : tr(\n 'savings.bundle.done',\n 'Kept \"{{keep}}\" and cancelled {{count}} subscription(s). Estimated saving: {{amount}} a year.',\n { keep: result.kept.label, count: result.cancelled.length, amount: saved }\n )\n );\n };\n\n const refresh = () => {\n void overview.refetch();\n void decisions.refetch();\n };\n\n return (\n <PageLayout\n title={tr('savings.title', 'Optimize your spend')}\n description={tr(\n 'savings.description',\n 'What you could save this year and the specific changes that get you there. Every change asks you to confirm first.'\n )}\n actions={\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={refresh}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className=\"h-4 w-4\" aria-hidden /> {tr('savings.refresh', 'Refresh')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Bot className=\"h-4 w-4\" aria-hidden /> {tr('savings.runAgents', 'Run agents')}\n </button>\n </div>\n }\n >\n <AiDisclaimer />\n\n {isLoading ? (\n <LoadingView label={tr('savings.loading', 'Finding savings…')} />\n ) : isError ? (\n <div className=\"mt-6\">\n <DecisionsUnavailableBanner\n message={error instanceof Error ? error.message : undefined}\n onRetry={refresh}\n />\n </div>\n ) : (\n <>\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"mt-6 flex flex-col gap-4 p-6 sm:flex-row sm:items-end sm:justify-between\"\n data-testid=\"savings-headline\"\n >\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-2 text-sm font-medium text-text-secondary\">\n <PiggyBank className=\"h-4 w-4\" aria-hidden />\n {tr('savings.headline.label', 'Estimated savings this year')}\n </p>\n <p\n className=\"mt-1 break-words text-3xl font-bold tabular-nums text-status-success-text sm:text-4xl\"\n data-testid=\"savings-headline-amount\"\n >\n {headline ?? formatCurrency(0, 'USD')}\n </p>\n {monthly && (\n <p className=\"mt-1 text-sm tabular-nums text-text-secondary\">\n {tr('savings.headline.perMonth', 'About {{amount}} a month', { amount: monthly })}\n </p>\n )}\n </div>\n <dl className=\"grid shrink-0 grid-cols-3 gap-4 text-sm\">\n <div>\n <dt className=\"text-xs text-text-secondary\">\n {tr('savings.headline.actions', 'Actions')}\n </dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">{openCount}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-secondary\">\n {tr('savings.headline.fromDecisions', 'From recommendations')}\n </dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">\n {fromDecisions ?? '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-secondary\">\n {tr('savings.headline.fromBundles', 'From bundles')}\n </dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">\n {fromBundles ?? '—'}\n </dd>\n </div>\n </dl>\n </GlassCard>\n\n {overview.data?.truncated && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'savings.truncated',\n 'Your portfolio is large, so this total covers the newest recommendations and subscriptions only.'\n )}\n </p>\n )}\n {decisions.data?.pageInfo.hasMore && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'savings.moreDecisions',\n 'Showing the newest {{count}} recommendations. The full queue is on the AI Decisions page.',\n { count: decisions.data.items.length }\n )}\n </p>\n )}\n\n {notice && (\n <div\n role=\"status\"\n className=\"mt-4 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle px-4 py-2 text-sm text-status-success-text\"\n data-testid=\"savings-notice\"\n >\n {notice}\n </div>\n )}\n\n {openCount === 0 && bundles.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n title={tr('savings.empty.title', 'No savings found right now')}\n description={tr(\n 'savings.empty.description',\n 'Run your cost, renewal, vendor and discovery agents to look for savings. Running an agent never changes a subscription on its own.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('savings.empty.action', 'Go to agents')}\n </button>\n }\n />\n </div>\n ) : (\n <div className=\"mt-6\">\n {SAVINGS_SECTIONS.map((section) => {\n const items = grouped[section.id];\n if (items.length === 0) return null;\n const sum = joinAmounts(savingsByCurrency(items), formatCurrency);\n return (\n <Section\n key={section.id}\n title={`${tr(section.key, section.fallback)} (${items.length})`}\n description={tr(section.descriptionKey, section.descriptionFallback)}\n action={\n sum ? (\n <span className=\"text-sm font-semibold tabular-nums text-status-success-text\">\n {tr('savings.sectionTotal', '{{amount}} a year', { amount: sum })}\n </span>\n ) : undefined\n }\n >\n <div\n className=\"grid grid-cols-1 gap-3 lg:grid-cols-2\"\n data-testid={`savings-section-${section.id}`}\n >\n {items.map((d) => (\n <SavingsCard key={d.id} decision={d} />\n ))}\n </div>\n </Section>\n );\n })}\n\n <Section\n title={tr('savings.bundles.title', 'Bundle recommendations')}\n description={tr(\n 'savings.bundles.description',\n 'Subscriptions that overlap and could be merged onto a single plan.'\n )}\n >\n {bundles.length === 0 ? (\n <p\n className=\"flex items-center gap-2 rounded-lg border border-border-subtle px-4 py-3 text-sm text-text-secondary\"\n data-testid=\"bundles-empty\"\n >\n <Combine className=\"h-4 w-4\" aria-hidden />\n {tr(\n 'savings.bundles.empty',\n 'No overlapping subscriptions found. Duplicate accounts with one provider, different services tagged with the same capability, and subscriptions whose features another one already includes show up here.'\n )}\n </p>\n ) : (\n <div\n className=\"grid grid-cols-1 gap-3 lg:grid-cols-2\"\n data-testid=\"savings-bundles\"\n >\n {bundles.map((b) => (\n <BundleCard key={b.id} bundle={b} onConsolidated={onConsolidated} />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RelationshipDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA61BH,wBAAgB,sBAAsB,gCAsjBrC"}
1
+ {"version":3,"file":"RelationshipDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA20BH,wBAAgB,sBAAsB,gCA+jBrC"}