@fayz-ai/saas 0.8.1 → 0.8.2

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 (233) hide show
  1. package/dist/access/context.d.ts +47 -0
  2. package/dist/access/context.d.ts.map +1 -0
  3. package/dist/access/headless.d.ts +21 -0
  4. package/dist/access/headless.d.ts.map +1 -0
  5. package/dist/access/index.d.ts +12 -0
  6. package/dist/access/index.d.ts.map +1 -0
  7. package/dist/access/limits-registry.d.ts +33 -0
  8. package/dist/access/limits-registry.d.ts.map +1 -0
  9. package/dist/access/module-nav.d.ts +15 -0
  10. package/dist/access/module-nav.d.ts.map +1 -0
  11. package/dist/access/resolver.d.ts +2 -0
  12. package/dist/access/resolver.d.ts.map +1 -0
  13. package/dist/access/types.d.ts +43 -0
  14. package/dist/access/types.d.ts.map +1 -0
  15. package/dist/access/upgrade-modal-store.d.ts +23 -0
  16. package/dist/access/upgrade-modal-store.d.ts.map +1 -0
  17. package/dist/app/AdminShell.d.ts +3 -0
  18. package/dist/app/AdminShell.d.ts.map +1 -1
  19. package/dist/app/admin-app.d.ts +3 -1
  20. package/dist/app/admin-app.d.ts.map +1 -1
  21. package/dist/app/config.d.ts +47 -2
  22. package/dist/app/config.d.ts.map +1 -1
  23. package/dist/app/core-entities.d.ts +25 -0
  24. package/dist/app/core-entities.d.ts.map +1 -0
  25. package/dist/app/derive-contract.d.ts +8 -0
  26. package/dist/app/derive-contract.d.ts.map +1 -0
  27. package/dist/app/scaffold.d.ts.map +1 -1
  28. package/dist/archetype-lookup.d.ts.map +1 -1
  29. package/dist/billing/index.cjs +6 -2
  30. package/dist/billing/index.d.ts +2 -0
  31. package/dist/billing/index.d.ts.map +1 -1
  32. package/dist/billing/index.js +1 -1
  33. package/dist/billing/plan-badge.d.ts +17 -0
  34. package/dist/billing/plan-badge.d.ts.map +1 -0
  35. package/dist/billing/store.d.ts +6 -1
  36. package/dist/billing/store.d.ts.map +1 -1
  37. package/dist/{chunk-3M25GRRU.js → chunk-I3DY47N5.js} +3 -4
  38. package/dist/chunk-I3DY47N5.js.map +1 -0
  39. package/dist/chunk-NNFGMUF2.cjs +39 -0
  40. package/dist/chunk-NNFGMUF2.cjs.map +1 -0
  41. package/dist/{chunk-4XTQ4V5L.cjs → chunk-SAZLW54Y.cjs} +16 -12
  42. package/dist/chunk-SAZLW54Y.cjs.map +1 -0
  43. package/dist/{chunk-IPWCHJPE.cjs → chunk-VKYZACG5.cjs} +5 -6
  44. package/dist/chunk-VKYZACG5.cjs.map +1 -0
  45. package/dist/chunk-VN5NTK26.js +36 -0
  46. package/dist/chunk-VN5NTK26.js.map +1 -0
  47. package/dist/{chunk-XNWCGJYH.js → chunk-VTEP2AQX.js} +15 -11
  48. package/dist/chunk-VTEP2AQX.js.map +1 -0
  49. package/dist/components/shared/ContactPicker.d.ts +92 -0
  50. package/dist/components/shared/ContactPicker.d.ts.map +1 -0
  51. package/dist/components/shared/PersonLink.d.ts +12 -1
  52. package/dist/components/shared/PersonLink.d.ts.map +1 -1
  53. package/dist/components/shared/create-person.d.ts +20 -0
  54. package/dist/components/shared/create-person.d.ts.map +1 -0
  55. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  56. package/dist/crud/CrudFormPage.d.ts.map +1 -1
  57. package/dist/crud/CrudListView.d.ts.map +1 -1
  58. package/dist/crud/CrudPage.d.ts.map +1 -1
  59. package/dist/crud/archetypes/AddressesTab.d.ts +21 -0
  60. package/dist/crud/archetypes/AddressesTab.d.ts.map +1 -0
  61. package/dist/crud/archetypes/PersonDetailTabs.d.ts +2 -1
  62. package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
  63. package/dist/crud/createCrudPage.d.ts.map +1 -1
  64. package/dist/crud/index.d.ts +1 -0
  65. package/dist/crud/index.d.ts.map +1 -1
  66. package/dist/index.cjs +4281 -716
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.ts +17 -2
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4067 -542
  71. package/dist/index.js.map +1 -1
  72. package/dist/org/index.cjs +11 -11
  73. package/dist/org/index.js +2 -2
  74. package/dist/org/store.d.ts.map +1 -1
  75. package/dist/permissions/context.d.ts +2 -0
  76. package/dist/permissions/context.d.ts.map +1 -1
  77. package/dist/permissions/index.cjs +7 -7
  78. package/dist/permissions/index.js +1 -1
  79. package/dist/shell/components/auth/ProtectedRoute.d.ts.map +1 -1
  80. package/dist/shell/components/billing/SoftLimitBanner.d.ts +5 -0
  81. package/dist/shell/components/billing/SoftLimitBanner.d.ts.map +1 -0
  82. package/dist/shell/components/billing/SubscriptionPage.d.ts +5 -0
  83. package/dist/shell/components/billing/SubscriptionPage.d.ts.map +1 -0
  84. package/dist/shell/components/billing/UpgradeModal.d.ts +3 -0
  85. package/dist/shell/components/billing/UpgradeModal.d.ts.map +1 -0
  86. package/dist/shell/components/billing/UpgradePrompt.d.ts +10 -0
  87. package/dist/shell/components/billing/UpgradePrompt.d.ts.map +1 -0
  88. package/dist/shell/components/billing/access-contract.d.ts +18 -0
  89. package/dist/shell/components/billing/access-contract.d.ts.map +1 -0
  90. package/dist/shell/components/billing/gates.d.ts +14 -0
  91. package/dist/shell/components/billing/gates.d.ts.map +1 -0
  92. package/dist/shell/components/billing/index.d.ts +9 -2
  93. package/dist/shell/components/billing/index.d.ts.map +1 -1
  94. package/dist/shell/components/chat/ChatFab.d.ts +3 -1
  95. package/dist/shell/components/chat/ChatFab.d.ts.map +1 -1
  96. package/dist/shell/components/chat/ChatPanel.d.ts +3 -1
  97. package/dist/shell/components/chat/ChatPanel.d.ts.map +1 -1
  98. package/dist/shell/components/chat/ConfirmActionCard.d.ts +13 -0
  99. package/dist/shell/components/chat/ConfirmActionCard.d.ts.map +1 -0
  100. package/dist/shell/components/chat/markdown.d.ts +5 -0
  101. package/dist/shell/components/chat/markdown.d.ts.map +1 -0
  102. package/dist/shell/components/organization/TeamTab.d.ts.map +1 -1
  103. package/dist/shell/components/organization/TenantOnboarding.d.ts.map +1 -1
  104. package/dist/shell/components/organization/index.d.ts +0 -1
  105. package/dist/shell/components/organization/index.d.ts.map +1 -1
  106. package/dist/shell/components/settings/ConnectedCompanySettings.d.ts.map +1 -1
  107. package/dist/shell/components/settings/ConnectedFieldRulesSettings.d.ts.map +1 -1
  108. package/dist/shell/components/settings/ConnectedSecuritySettings.d.ts.map +1 -1
  109. package/dist/shell/components/settings/LocationsCrudPage.d.ts +1 -1
  110. package/dist/shell/components/settings/LocationsCrudPage.d.ts.map +1 -1
  111. package/dist/shell/components/settings/SettingsPage.d.ts +23 -2
  112. package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -1
  113. package/dist/shell/hooks/index.d.ts +3 -2
  114. package/dist/shell/hooks/index.d.ts.map +1 -1
  115. package/dist/shell/hooks/useAITools.d.ts +2 -0
  116. package/dist/shell/hooks/useAITools.d.ts.map +1 -1
  117. package/dist/shell/hooks/useChat.d.ts +22 -0
  118. package/dist/shell/hooks/useChat.d.ts.map +1 -1
  119. package/dist/shell/hooks/useSystemPermission.d.ts +3 -0
  120. package/dist/shell/hooks/useSystemPermission.d.ts.map +1 -0
  121. package/dist/shell/hooks/useTenantPluginSettings.d.ts +12 -0
  122. package/dist/shell/hooks/useTenantPluginSettings.d.ts.map +1 -0
  123. package/dist/shell/lib/ai-tool-handlers.d.ts +119 -0
  124. package/dist/shell/lib/ai-tool-handlers.d.ts.map +1 -0
  125. package/dist/shell/lib/core-ai-tools.d.ts +14 -0
  126. package/dist/shell/lib/core-ai-tools.d.ts.map +1 -1
  127. package/dist/shell/lib/create-client-orders-provider.d.ts.map +1 -1
  128. package/dist/shell/lib/fayz-agent.d.ts +23 -0
  129. package/dist/shell/lib/fayz-agent.d.ts.map +1 -0
  130. package/dist/shell/lib/i18n.d.ts.map +1 -1
  131. package/dist/shell/stores/chat.store.d.ts +69 -0
  132. package/dist/shell/stores/chat.store.d.ts.map +1 -1
  133. package/dist/shell/types/billing.d.ts +7 -0
  134. package/dist/shell/types/billing.d.ts.map +1 -1
  135. package/dist/shell/types/plugins.d.ts +2 -45
  136. package/dist/shell/types/plugins.d.ts.map +1 -1
  137. package/dist/stores/createCrudStore.d.ts.map +1 -1
  138. package/package.json +14 -11
  139. package/src/access/context.tsx +228 -0
  140. package/src/access/headless.ts +71 -0
  141. package/src/access/index.ts +27 -0
  142. package/src/access/limits-registry.ts +86 -0
  143. package/src/access/module-nav.test.ts +78 -0
  144. package/src/access/module-nav.ts +82 -0
  145. package/src/access/resolver.test.ts +143 -0
  146. package/src/access/resolver.ts +5 -0
  147. package/src/access/types.ts +47 -0
  148. package/src/access/upgrade-modal-store.ts +30 -0
  149. package/src/app/AdminShell.tsx +451 -68
  150. package/src/app/admin-app.tsx +46 -9
  151. package/src/app/config.ts +54 -1
  152. package/src/app/core-entities.ts +110 -0
  153. package/src/app/derive-contract.test.ts +155 -0
  154. package/src/app/derive-contract.ts +214 -0
  155. package/src/app/scaffold.tsx +4 -0
  156. package/src/archetype-lookup.ts +8 -2
  157. package/src/billing/index.ts +2 -0
  158. package/src/billing/plan-badge.ts +32 -0
  159. package/src/billing/store.ts +8 -1
  160. package/src/components/shared/ContactPicker.tsx +394 -0
  161. package/src/components/shared/PersonLink.tsx +11 -3
  162. package/src/components/shared/create-person.ts +74 -0
  163. package/src/crud/CrudDetailPage.tsx +5 -1
  164. package/src/crud/CrudFormPage.tsx +113 -6
  165. package/src/crud/CrudListView.tsx +16 -1
  166. package/src/crud/CrudPage.tsx +27 -5
  167. package/src/crud/DeleteConfirmDialog.tsx +1 -1
  168. package/src/crud/archetypes/AddressesTab.tsx +387 -0
  169. package/src/crud/archetypes/PersonDetailTabs.tsx +5 -0
  170. package/src/crud/createCrudPage.tsx +2 -0
  171. package/src/crud/index.ts +6 -0
  172. package/src/env.d.ts +7 -0
  173. package/src/index.ts +49 -1
  174. package/src/org/store.ts +9 -1
  175. package/src/permissions/context.tsx +29 -15
  176. package/src/shell/components/auth/ProtectedRoute.tsx +12 -2
  177. package/src/shell/components/billing/SoftLimitBanner.tsx +81 -0
  178. package/src/shell/components/billing/SubscriptionPage.tsx +372 -0
  179. package/src/shell/components/billing/UpgradeModal.tsx +102 -0
  180. package/src/shell/components/billing/UpgradePrompt.tsx +229 -0
  181. package/src/shell/components/billing/access-contract.ts +64 -0
  182. package/src/shell/components/billing/gates.tsx +62 -0
  183. package/src/shell/components/billing/index.ts +18 -2
  184. package/src/shell/components/chat/ChatFab.tsx +4 -2
  185. package/src/shell/components/chat/ChatPanel.tsx +174 -23
  186. package/src/shell/components/chat/ConfirmActionCard.tsx +57 -0
  187. package/src/shell/components/chat/markdown.tsx +102 -0
  188. package/src/shell/components/organization/TeamTab.tsx +21 -4
  189. package/src/shell/components/organization/TenantOnboarding.tsx +3 -1
  190. package/src/shell/components/organization/index.ts +0 -1
  191. package/src/shell/components/settings/ConnectedCompanySettings.tsx +4 -1
  192. package/src/shell/components/settings/ConnectedFieldRulesSettings.tsx +3 -1
  193. package/src/shell/components/settings/ConnectedSecuritySettings.tsx +3 -1
  194. package/src/shell/components/settings/LocationsCrudPage.ts +6 -1
  195. package/src/shell/components/settings/SettingsPage.tsx +63 -21
  196. package/src/shell/hooks/index.ts +3 -2
  197. package/src/shell/hooks/useAITools.ts +27 -23
  198. package/src/shell/hooks/useChat.ts +381 -16
  199. package/src/shell/hooks/useSystemPermission.ts +23 -0
  200. package/src/shell/hooks/useTenantPluginSettings.ts +127 -0
  201. package/src/shell/lib/ai-tool-handlers.ts +910 -0
  202. package/src/shell/lib/core-ai-tools.ts +234 -2
  203. package/src/shell/lib/create-client-orders-provider.ts +24 -2
  204. package/src/shell/lib/fayz-agent.ts +56 -0
  205. package/src/shell/lib/i18n.ts +71 -0
  206. package/src/shell/stores/chat.store.ts +118 -1
  207. package/src/shell/styles.css +30 -0
  208. package/src/shell/types/billing.ts +8 -0
  209. package/src/shell/types/plugins.ts +13 -50
  210. package/src/stores/createCrudStore.ts +9 -0
  211. package/dist/chunk-3M25GRRU.js.map +0 -1
  212. package/dist/chunk-4XTQ4V5L.cjs.map +0 -1
  213. package/dist/chunk-5GYQK5IW.js +0 -21
  214. package/dist/chunk-5GYQK5IW.js.map +0 -1
  215. package/dist/chunk-IPWCHJPE.cjs.map +0 -1
  216. package/dist/chunk-KYID2MR6.cjs +0 -23
  217. package/dist/chunk-KYID2MR6.cjs.map +0 -1
  218. package/dist/chunk-XNWCGJYH.js.map +0 -1
  219. package/dist/shell/components/billing/BillingPage.d.ts +0 -7
  220. package/dist/shell/components/billing/BillingPage.d.ts.map +0 -1
  221. package/dist/shell/components/billing/PaywallGate.d.ts +0 -11
  222. package/dist/shell/components/billing/PaywallGate.d.ts.map +0 -1
  223. package/dist/shell/components/organization/PermissionGate.d.ts +0 -11
  224. package/dist/shell/components/organization/PermissionGate.d.ts.map +0 -1
  225. package/dist/shell/hooks/useBilling.d.ts +0 -14
  226. package/dist/shell/hooks/useBilling.d.ts.map +0 -1
  227. package/dist/shell/hooks/usePermission.d.ts +0 -16
  228. package/dist/shell/hooks/usePermission.d.ts.map +0 -1
  229. package/src/shell/components/billing/BillingPage.tsx +0 -56
  230. package/src/shell/components/billing/PaywallGate.tsx +0 -80
  231. package/src/shell/components/organization/PermissionGate.tsx +0 -16
  232. package/src/shell/hooks/useBilling.ts +0 -151
  233. package/src/shell/hooks/usePermission.ts +0 -101
@@ -0,0 +1,214 @@
1
+ import type {
2
+ AgentContract,
3
+ AIToolContract,
4
+ EntityContract,
5
+ EntityDef,
6
+ FeatureDeclaration,
7
+ LimitDeclaration,
8
+ PluginAITool,
9
+ PluginManifest,
10
+ RegisteredEntity,
11
+ } from '@fayz-ai/core'
12
+ import { getAllEntities, deriveEntityKey } from '@fayz-ai/core'
13
+ import { mergeLimitDeclarations } from '@fayz-ai/core/access'
14
+ import { coreAITools, buildDataPrimitiveTools } from '../shell/lib/core-ai-tools'
15
+ import { CORE_LIMIT_DECLARATIONS, entityDerivedLimitDeclarations } from './../access/limits-registry'
16
+ import { CORE_QUERY_ENTITIES } from './core-entities'
17
+ import type { FayzAppConfig } from './config'
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // deriveAgentContract — FayzAppConfig → the manifest v3 `agent` section +
21
+ // resolved `limitDeclarations`. This is the "capabilities are derived, never
22
+ // configured" principle applied to the server side: everything the Fayz broker
23
+ // needs to operate the app without a browser is PROJECTED from what the app
24
+ // already declares (entities, plugins, permissions, billing), never re-typed.
25
+ // Pure data out — safe to run at defineSaas time and in `fayz manifest emit`.
26
+ // ---------------------------------------------------------------------------
27
+
28
+ function projectEntity(key: string, label: string, labelPlural: string | undefined, def: EntityDef): EntityContract | null {
29
+ const data = def.data
30
+ if (!data?.table) return null
31
+ return {
32
+ key,
33
+ label,
34
+ ...(labelPlural ? { labelPlural } : {}),
35
+ ...(def.limitKey ? { limitKey: def.limitKey } : {}),
36
+ ...(def.permission ? { permission: def.permission } : {}),
37
+ data: {
38
+ table: data.table,
39
+ ...(data.schema ? { schema: data.schema } : {}),
40
+ ...(data.tenantScoped !== undefined ? { tenantScoped: data.tenantScoped } : {}),
41
+ ...(data.tenantIdColumn ? { tenantIdColumn: data.tenantIdColumn } : {}),
42
+ ...(data.searchColumns ? { searchColumns: data.searchColumns } : {}),
43
+ ...(data.selectColumns ? { selectColumns: data.selectColumns } : {}),
44
+ ...(data.columnMap ? { columnMap: data.columnMap } : {}),
45
+ ...(data.archetype ? { archetype: data.archetype } : {}),
46
+ ...(data.archetypeKind ? { archetypeKind: data.archetypeKind } : {}),
47
+ ...(data.filters ? { filters: data.filters } : {}),
48
+ },
49
+ fields: (def.fields ?? []).map((f) => ({
50
+ key: f.key,
51
+ label: f.label,
52
+ type: f.type,
53
+ ...(f.required ? { required: true } : {}),
54
+ ...(f.searchable ? { searchable: true } : {}),
55
+ // The hint is the only place a field says what it EXPECTS rather than
56
+ // what it is called — an owner id to look up, a default not to restate.
57
+ // Dropping it here is what left the agent guessing at write time.
58
+ ...(f.hint ? { hint: f.hint } : {}),
59
+ ...(Array.isArray(f.options) && f.options.length
60
+ ? { options: f.options.map((o) => (typeof o === 'string' ? o : o.value)) }
61
+ : {}),
62
+ ...(f.relation?.table ? { relationTable: f.relation.table } : {}),
63
+ })),
64
+ }
65
+ }
66
+
67
+ function projectTool(tool: PluginAITool, pluginId?: string): AIToolContract {
68
+ return {
69
+ id: tool.id,
70
+ name: tool.name,
71
+ description: tool.description,
72
+ mode: tool.mode,
73
+ ...(tool.parameters ? { parameters: tool.parameters } : {}),
74
+ ...(tool.permission ? { permission: tool.permission } : {}),
75
+ ...(tool.limitKey ? { limitKey: tool.limitKey } : {}),
76
+ ...(tool.execution ? { execution: tool.execution } : {}),
77
+ // Persist tools default to human confirmation — an agent never clicks a
78
+ // gated button, so the contract is where the guard is declared.
79
+ ...(tool.mode === 'persist' ? { requiresConfirmation: tool.requiresConfirmation ?? true } : {}),
80
+ ...(pluginId ? { pluginId } : {}),
81
+ ...(tool.category ? { category: tool.category } : {}),
82
+ }
83
+ }
84
+
85
+ /** Merge plugin-declared features with the app's own, app wins by id — the
86
+ * same rule PermissionsProvider applies to the live matrix. */
87
+ function mergeFeatureDeclarations(
88
+ plugins: PluginManifest[],
89
+ appFeatures: FeatureDeclaration[] | undefined,
90
+ ): FeatureDeclaration[] {
91
+ const byId = new Map<string, FeatureDeclaration>()
92
+ for (const plugin of plugins) {
93
+ for (const f of plugin.declaredFeatures ?? []) byId.set(f.id, f)
94
+ }
95
+ for (const f of appFeatures ?? []) byId.set(f.id, f)
96
+ return Array.from(byId.values())
97
+ }
98
+
99
+ export interface DerivedAgentSections {
100
+ agent: AgentContract
101
+ limitDeclarations: LimitDeclaration[]
102
+ }
103
+
104
+ export function deriveAgentContract(
105
+ config: FayzAppConfig,
106
+ entities: RegisteredEntity[] = getAllEntities(),
107
+ ): DerivedAgentSections {
108
+ const plugins = config.plugins ?? []
109
+
110
+ // --- Entities: registered CRUD entities + plugin registry entities --------
111
+ const entityContracts: EntityContract[] = []
112
+ for (const e of entities) {
113
+ if (!e.entityDef) continue
114
+ const projected = projectEntity(e.entityKey, e.label, e.labelPlural, e.entityDef)
115
+ if (projected) entityContracts.push(projected)
116
+ }
117
+ const registries: NonNullable<AgentContract['registries']> = []
118
+ for (const plugin of plugins) {
119
+ for (const registry of plugin.registries ?? []) {
120
+ const entityKey = `${plugin.id}:${registry.id}`
121
+ registries.push({
122
+ pluginId: plugin.id,
123
+ id: registry.id,
124
+ entityKey,
125
+ ...(registry.readOnly ? { readOnly: true } : {}),
126
+ })
127
+ const projected = projectEntity(
128
+ entityKey,
129
+ registry.entity.name,
130
+ registry.entity.namePlural,
131
+ registry.entity,
132
+ )
133
+ if (projected) entityContracts.push(projected)
134
+ }
135
+ for (const q of plugin.queryEntities ?? []) {
136
+ const projected = projectEntity(q.key, q.entity.name, q.entity.namePlural, q.entity)
137
+ if (projected) entityContracts.push(projected)
138
+ }
139
+ }
140
+ for (const q of config.agentContract?.queryEntities ?? []) {
141
+ const projected = projectEntity(q.key, q.entity.name, q.entity.namePlural, q.entity)
142
+ if (projected) entityContracts.push(projected)
143
+ }
144
+ // Spine tables whose surface is a detail TAB, not a page — no app declares
145
+ // them as CRUD entities, so without this the agent cannot see them at all.
146
+ for (const q of CORE_QUERY_ENTITIES) {
147
+ const projected = projectEntity(q.key, q.entity.name, q.entity.namePlural, q.entity)
148
+ if (projected) entityContracts.push(projected)
149
+ }
150
+
151
+ // --- Tools: core + derived reads + plugin-declared --------------------------
152
+ const tools: AIToolContract[] = []
153
+ // Core handlers run in the surface (org store / router) — client plane.
154
+ for (const tool of coreAITools) {
155
+ tools.push({ ...projectTool(tool), execution: { plane: 'client' } })
156
+ }
157
+ // The data primitives: one search + one aggregate over every entity/registry.
158
+ // `entity: '*'` = the target arrives as the call's `entity` argument; the
159
+ // executor resolves it against `agent.entities` and enforces that entity's
160
+ // own read permission.
161
+ const registryMap = new Map(plugins.map((p) => [p.id, p.registries ?? []]))
162
+ const queryEntities = [
163
+ ...plugins.flatMap((p) => p.queryEntities ?? []),
164
+ ...(config.agentContract?.queryEntities ?? []),
165
+ // CORE_QUERY_ENTITIES is NOT listed here — buildDataPrimitiveTools merges
166
+ // it in itself, for every caller. It is projected into `entityContracts`
167
+ // above because that list is what the server-plane executor resolves a
168
+ // tool call's `entity` argument against.
169
+ ]
170
+ for (const tool of buildDataPrimitiveTools({ entities, registries: registryMap, queryEntities })) {
171
+ tools.push({ ...projectTool(tool), execution: { plane: 'server', kind: 'entity_read', entity: '*' } })
172
+ }
173
+ // Plugin-authored tools keep their declared execution (persist tools bind to
174
+ // RPCs via `execution: { kind:'rpc' }`; absent ⇒ client-plane executor).
175
+ for (const plugin of plugins) {
176
+ for (const tool of plugin.aiTools ?? []) {
177
+ tools.push(projectTool(tool, plugin.id))
178
+ }
179
+ }
180
+
181
+ // --- Limits: the same 4-layer merge the AccessProvider publishes ----------
182
+ const limitDeclarations = mergeLimitDeclarations(
183
+ CORE_LIMIT_DECLARATIONS,
184
+ entityDerivedLimitDeclarations(entities),
185
+ plugins.flatMap((p) => p.declaredLimits ?? []),
186
+ config.billing?.limitDeclarations,
187
+ )
188
+
189
+ // --- RPCs: plugin-shipped + app-shipped ------------------------------------
190
+ const rpcs = [
191
+ ...plugins.flatMap((p) => p.declaredRpcs ?? []),
192
+ ...(config.agentContract?.rpcs ?? []),
193
+ ]
194
+
195
+ const agent: AgentContract = {
196
+ ...(config.chat?.title || config.chat?.systemPrompt
197
+ ? {
198
+ persona: {
199
+ ...(config.chat.title ? { name: config.chat.title } : {}),
200
+ ...(config.chat.systemPrompt ? { systemPrompt: config.chat.systemPrompt } : {}),
201
+ },
202
+ }
203
+ : {}),
204
+ executionPlane: config.agentContract?.executionPlane ?? 'client',
205
+ entities: entityContracts,
206
+ tools,
207
+ ...(registries.length ? { registries } : {}),
208
+ declaredFeatures: mergeFeatureDeclarations(plugins, config.permissions?.features),
209
+ ...(rpcs.length ? { rpcs } : {}),
210
+ ...(config.agentContract?.knowledge ? { domainKnowledge: config.agentContract.knowledge } : {}),
211
+ }
212
+
213
+ return { agent, limitDeclarations }
214
+ }
@@ -14,6 +14,7 @@ import type {
14
14
  } from '@fayz-ai/core'
15
15
  import { AdminProviders, getAuthShellProps } from './admin-app'
16
16
  import { AdminShell } from './AdminShell'
17
+ import { deriveAgentContract } from './derive-contract'
17
18
  import type { FayzAppConfig, CustomPage } from './config'
18
19
 
19
20
  // ---------------------------------------------------------------------------
@@ -49,6 +50,7 @@ export function defineSaas(config: FayzAppConfig): AppManifest {
49
50
  const id = slug(config.name)
50
51
  const configRef = getLiveConfigRef(id)
51
52
  liveSaasConfigs.set(configRef, config)
53
+ const { agent, limitDeclarations } = deriveAgentContract(config)
52
54
  return defineApp({
53
55
  id,
54
56
  name: config.name,
@@ -59,6 +61,8 @@ export function defineSaas(config: FayzAppConfig): AppManifest {
59
61
  theme: config.theme as Record<string, unknown> | undefined,
60
62
  permissions: config.permissions,
61
63
  billing: config.billing as ManifestBillingConfig | undefined,
64
+ limitDeclarations,
65
+ agent,
62
66
  surfaces: {
63
67
  admin: {
64
68
  scaffold: 'admin',
@@ -107,10 +107,13 @@ export function createArchetypeLookup(config: ArchetypeLookupConfig): EntityLook
107
107
  let qb = supabase
108
108
  .from(display.table)
109
109
  .select('*')
110
- .eq('is_active', true)
111
110
  .order(display.labelField, { ascending: true })
112
111
  .limit(limit)
113
112
 
113
+ qb = config.archetype === 'service'
114
+ ? qb.or('is_active.eq.true,is_active.is.null')
115
+ : qb.eq('is_active', true)
116
+
114
117
  if (query) qb = qb.ilike(display.labelField, `%${query}%`)
115
118
  qb = applyKindFilter(qb)
116
119
 
@@ -127,10 +130,13 @@ export function createArchetypeLookup(config: ArchetypeLookupConfig): EntityLook
127
130
  let qb = supabase
128
131
  .from(display.table)
129
132
  .select('*')
130
- .eq('is_active', true)
131
133
  .order(display.labelField, { ascending: true })
132
134
  .limit(limit)
133
135
 
136
+ qb = config.archetype === 'service'
137
+ ? qb.or('is_active.eq.true,is_active.is.null')
138
+ : qb.eq('is_active', true)
139
+
134
140
  qb = applyKindFilter(qb)
135
141
 
136
142
  const { data } = await qb
@@ -5,3 +5,5 @@ export type {
5
5
  Subscription,
6
6
  Invoice,
7
7
  } from './store'
8
+ export { resolvePlanBadge } from './plan-badge'
9
+ export type { PlanBadgeInfo } from './plan-badge'
@@ -0,0 +1,32 @@
1
+ import type { Plan } from '@fayz-ai/core'
2
+
3
+ export interface PlanBadgeInfo {
4
+ /** Display label, e.g. 'Free' or 'Pro'. */
5
+ label: string
6
+ /** Resolved plan id (falls back to the raw org.plan string). */
7
+ planId: string
8
+ /** Paid plan (price > 0) — drives the Crown icon + gold styling in the menus. */
9
+ paid: boolean
10
+ }
11
+
12
+ function capitalize(s: string): string {
13
+ return s ? s.charAt(0).toUpperCase() + s.slice(1) : s
14
+ }
15
+
16
+ /**
17
+ * Resolve an org's `plan` string against the configured billing plans into the
18
+ * badge shown next to the user in both menus. Falls back to a capitalized
19
+ * version of the raw string when the id isn't in config (defensive — e.g. a
20
+ * legacy 'free' tenant before billing was wired).
21
+ */
22
+ export function resolvePlanBadge(
23
+ planId: string | undefined | null,
24
+ plans: Plan[],
25
+ ): PlanBadgeInfo {
26
+ const id = planId || 'free'
27
+ const match = plans.find((p) => p.id === id)
28
+ if (match) {
29
+ return { label: match.name, planId: match.id, paid: match.price > 0 }
30
+ }
31
+ return { label: capitalize(id), planId: id, paid: false }
32
+ }
@@ -1,5 +1,5 @@
1
1
  import { create } from 'zustand'
2
- import type { Plan } from '@fayz-ai/core'
2
+ import type { Plan, BillingCheckoutFn } from '@fayz-ai/core'
3
3
 
4
4
  // ---------------------------------------------------------------------------
5
5
  // Types
@@ -31,6 +31,10 @@ export interface BillingState {
31
31
  invoices: Invoice[]
32
32
  plans: Plan[]
33
33
  loading: boolean
34
+ /** Payment-gateway seam seeded from `config.billing.onCheckout` (see core
35
+ * BillingConfig). When set, the Subscription page routes plan changes through
36
+ * it instead of writing `plan` straight onto the org. */
37
+ checkout: BillingCheckoutFn | null
34
38
  }
35
39
 
36
40
  // ---------------------------------------------------------------------------
@@ -42,6 +46,7 @@ export interface BillingStore extends BillingState {
42
46
  setInvoices: (invoices: Invoice[]) => void
43
47
  setPlans: (plans: Plan[]) => void
44
48
  setLoading: (loading: boolean) => void
49
+ setCheckout: (checkout: BillingCheckoutFn | null) => void
45
50
  reset: () => void
46
51
  }
47
52
 
@@ -50,6 +55,7 @@ const initialState: BillingState = {
50
55
  invoices: [],
51
56
  plans: [],
52
57
  loading: false,
58
+ checkout: null,
53
59
  }
54
60
 
55
61
  export const useBillingStore = create<BillingStore>((set) => ({
@@ -58,5 +64,6 @@ export const useBillingStore = create<BillingStore>((set) => ({
58
64
  setInvoices: (invoices) => set({ invoices }),
59
65
  setPlans: (plans) => set({ plans }),
60
66
  setLoading: (loading) => set({ loading }),
67
+ setCheckout: (checkout) => set({ checkout }),
61
68
  reset: () => set(initialState),
62
69
  }))