@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3043.1a796c3920

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 (163) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +13 -1
  3. package/dist/helpers/integration/api.js +29 -16
  4. package/dist/helpers/integration/api.js.map +2 -2
  5. package/dist/helpers/integration/auth.js +11 -6
  6. package/dist/helpers/integration/auth.js.map +3 -3
  7. package/dist/modules/auth/commands/roles.js +9 -12
  8. package/dist/modules/auth/commands/roles.js.map +2 -2
  9. package/dist/modules/catalog/ai-agents-context.js +147 -0
  10. package/dist/modules/catalog/ai-agents-context.js.map +7 -0
  11. package/dist/modules/catalog/ai-agents.js +383 -0
  12. package/dist/modules/catalog/ai-agents.js.map +7 -0
  13. package/dist/modules/catalog/ai-tools/_shared.js +318 -0
  14. package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
  15. package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
  16. package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
  17. package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
  18. package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
  19. package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
  20. package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
  21. package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
  22. package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
  23. package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
  24. package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
  25. package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
  26. package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
  27. package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
  28. package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
  29. package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
  30. package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
  31. package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
  32. package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
  33. package/dist/modules/catalog/ai-tools/types.js +10 -0
  34. package/dist/modules/catalog/ai-tools/types.js.map +7 -0
  35. package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
  36. package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
  37. package/dist/modules/catalog/ai-tools.js +28 -0
  38. package/dist/modules/catalog/ai-tools.js.map +7 -0
  39. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
  40. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
  41. package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
  42. package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
  43. package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
  44. package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
  45. package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
  46. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  47. package/dist/modules/catalog/events.js +7 -4
  48. package/dist/modules/catalog/events.js.map +2 -2
  49. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
  50. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
  51. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
  52. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
  53. package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
  54. package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
  55. package/dist/modules/catalog/widgets/injection-table.js +13 -1
  56. package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
  57. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
  58. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
  59. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
  60. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
  61. package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
  62. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  63. package/dist/modules/customers/ai-agents-context.js +96 -0
  64. package/dist/modules/customers/ai-agents-context.js.map +7 -0
  65. package/dist/modules/customers/ai-agents.js +244 -0
  66. package/dist/modules/customers/ai-agents.js.map +7 -0
  67. package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
  68. package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
  69. package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
  70. package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
  71. package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
  72. package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
  73. package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
  74. package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
  75. package/dist/modules/customers/ai-tools/people-pack.js +261 -0
  76. package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
  77. package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
  78. package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
  79. package/dist/modules/customers/ai-tools/types.js +10 -0
  80. package/dist/modules/customers/ai-tools/types.js.map +7 -0
  81. package/dist/modules/customers/ai-tools.js +20 -0
  82. package/dist/modules/customers/ai-tools.js.map +7 -0
  83. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
  84. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
  85. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
  86. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
  87. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
  88. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
  89. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
  90. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
  91. package/dist/modules/customers/widgets/injection-table.js +26 -0
  92. package/dist/modules/customers/widgets/injection-table.js.map +7 -0
  93. package/dist/modules/inbox_ops/ai-tools.js +4 -0
  94. package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
  95. package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
  96. package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
  97. package/dist/modules/notifications/setup.js +13 -0
  98. package/dist/modules/notifications/setup.js.map +7 -0
  99. package/jest.config.cjs +1 -0
  100. package/jest.setup.ts +18 -0
  101. package/package.json +5 -3
  102. package/src/helpers/integration/api.ts +38 -16
  103. package/src/helpers/integration/auth.ts +13 -6
  104. package/src/modules/auth/commands/roles.ts +10 -12
  105. package/src/modules/catalog/AGENTS.md +11 -0
  106. package/src/modules/catalog/ai-agents-context.ts +239 -0
  107. package/src/modules/catalog/ai-agents.ts +525 -0
  108. package/src/modules/catalog/ai-tools/_shared.ts +487 -0
  109. package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
  110. package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
  111. package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
  112. package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
  113. package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
  114. package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
  115. package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
  116. package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
  117. package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
  118. package/src/modules/catalog/ai-tools/types.ts +81 -0
  119. package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
  120. package/src/modules/catalog/ai-tools.ts +78 -0
  121. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
  122. package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
  123. package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
  124. package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
  125. package/src/modules/catalog/events.ts +7 -4
  126. package/src/modules/catalog/i18n/de.json +17 -0
  127. package/src/modules/catalog/i18n/en.json +17 -0
  128. package/src/modules/catalog/i18n/es.json +17 -0
  129. package/src/modules/catalog/i18n/pl.json +17 -0
  130. package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
  131. package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
  132. package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
  133. package/src/modules/catalog/widgets/injection-table.ts +12 -0
  134. package/src/modules/customer_accounts/i18n/de.json +5 -0
  135. package/src/modules/customer_accounts/i18n/en.json +5 -0
  136. package/src/modules/customer_accounts/i18n/es.json +5 -0
  137. package/src/modules/customer_accounts/i18n/pl.json +5 -0
  138. package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
  139. package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
  140. package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
  141. package/src/modules/customers/AGENTS.md +13 -0
  142. package/src/modules/customers/ai-agents-context.ts +150 -0
  143. package/src/modules/customers/ai-agents.ts +355 -0
  144. package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
  145. package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
  146. package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
  147. package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
  148. package/src/modules/customers/ai-tools/people-pack.ts +369 -0
  149. package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
  150. package/src/modules/customers/ai-tools/types.ts +76 -0
  151. package/src/modules/customers/ai-tools.ts +34 -0
  152. package/src/modules/customers/i18n/de.json +25 -0
  153. package/src/modules/customers/i18n/en.json +25 -0
  154. package/src/modules/customers/i18n/es.json +25 -0
  155. package/src/modules/customers/i18n/pl.json +25 -0
  156. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
  157. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
  158. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
  159. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
  160. package/src/modules/customers/widgets/injection-table.ts +41 -0
  161. package/src/modules/inbox_ops/ai-tools.ts +4 -0
  162. package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
  163. package/src/modules/notifications/setup.ts +11 -0
@@ -0,0 +1,191 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Step 5.15 — Backend AiChat injection widget for the deal detail page.
5
+ *
6
+ * Mirrors the Step 4.10 People-list trigger, but targets a record-level
7
+ * surface: the deal detail page's header injection spot
8
+ * (`detail:customers.deal:header`). The page is NOT modified beyond
9
+ * adding the shared `<InjectionSpot>` mount point.
10
+ *
11
+ * `pageContext` shape (spec §10.1):
12
+ *
13
+ * { view: 'customers.deal.detail',
14
+ * recordType: 'deal',
15
+ * recordId: <dealId>,
16
+ * extra: { stage: string | null, pipelineStageId: string | null } }
17
+ *
18
+ * The agent is `customers.account_assistant`, feature-gated at the widget
19
+ * metadata layer behind `customers.deals.view` + `ai_assistant.view`.
20
+ * The read-only agent serves information requests today; when the tenant
21
+ * opts into Step 5.4's mutation-policy override, the agent unlocks the
22
+ * Step 5.13 `customers.update_deal_stage` tool behind the pending-action
23
+ * contract.
24
+ */
25
+
26
+ import * as React from 'react'
27
+ import { Sparkles } from 'lucide-react'
28
+ import { AiChat } from '@open-mercato/ui/ai/AiChat'
29
+ import { Button } from '@open-mercato/ui/primitives/button'
30
+ import {
31
+ Dialog,
32
+ DialogContent,
33
+ DialogDescription,
34
+ DialogHeader,
35
+ DialogTitle,
36
+ } from '@open-mercato/ui/primitives/dialog'
37
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
38
+ import { cn } from '@open-mercato/shared/lib/utils'
39
+
40
+ export const CUSTOMERS_AI_DEAL_DETAIL_AGENT_ID = 'customers.account_assistant'
41
+
42
+ export interface CustomersAiDealDetailPageContext {
43
+ view: 'customers.deal.detail'
44
+ recordType: 'deal'
45
+ recordId: string
46
+ extra: {
47
+ stage: string | null
48
+ pipelineStageId: string | null
49
+ }
50
+ }
51
+
52
+ interface HostInjectionContext {
53
+ dealId?: string
54
+ recordId?: string
55
+ stage?: string | null
56
+ pipelineStageId?: string | null
57
+ data?: {
58
+ deal?: {
59
+ id?: string
60
+ status?: string | null
61
+ pipelineStage?: string | null
62
+ pipelineStageId?: string | null
63
+ }
64
+ }
65
+ }
66
+
67
+ interface AiDealDetailTriggerProps {
68
+ context?: HostInjectionContext
69
+ data?: HostInjectionContext['data']
70
+ }
71
+
72
+ function readString(value: unknown): string | null {
73
+ return typeof value === 'string' && value.length > 0 ? value : null
74
+ }
75
+
76
+ function buildPageContext(
77
+ context: HostInjectionContext | undefined,
78
+ data: HostInjectionContext['data'] | undefined,
79
+ ): CustomersAiDealDetailPageContext | null {
80
+ const dealRecord = data?.deal ?? context?.data?.deal
81
+ const dealId =
82
+ readString(context?.dealId) ??
83
+ readString(context?.recordId) ??
84
+ readString(dealRecord?.id) ??
85
+ null
86
+ if (!dealId) return null
87
+ const stage =
88
+ readString(context?.stage) ??
89
+ readString(dealRecord?.status) ??
90
+ readString(dealRecord?.pipelineStage) ??
91
+ null
92
+ const pipelineStageId =
93
+ readString(context?.pipelineStageId) ??
94
+ readString(dealRecord?.pipelineStageId) ??
95
+ null
96
+ return {
97
+ view: 'customers.deal.detail',
98
+ recordType: 'deal',
99
+ recordId: dealId,
100
+ extra: { stage, pipelineStageId },
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Exposed for unit tests so the page-context derivation is exercisable
106
+ * without mounting the widget.
107
+ */
108
+ export function computeCustomersAiDealDetailPageContext(
109
+ context: HostInjectionContext | undefined,
110
+ data?: HostInjectionContext['data'],
111
+ ): CustomersAiDealDetailPageContext | null {
112
+ return buildPageContext(context, data)
113
+ }
114
+
115
+ export default function AiDealDetailTriggerWidget({ context, data }: AiDealDetailTriggerProps) {
116
+ const t = useT()
117
+ const [open, setOpen] = React.useState(false)
118
+ const pageContext = React.useMemo(() => buildPageContext(context, data), [context, data])
119
+ const handleClick = React.useCallback(() => {
120
+ setOpen(true)
121
+ }, [])
122
+
123
+ if (!pageContext) return null
124
+
125
+ return (
126
+ <>
127
+ <Button
128
+ type="button"
129
+ variant="outline"
130
+ size="sm"
131
+ onClick={handleClick}
132
+ data-ai-customers-deal-trigger=""
133
+ data-ai-customers-deal-id={pageContext.recordId}
134
+ aria-label={t(
135
+ 'customers.ai_assistant.dealDetail.trigger.ariaLabel',
136
+ 'Open AI assistant for this deal',
137
+ )}
138
+ >
139
+ <Sparkles className="size-4" aria-hidden />
140
+ <span>{t('customers.ai_assistant.dealDetail.trigger.label', 'Ask AI')}</span>
141
+ </Button>
142
+ <Dialog open={open} onOpenChange={setOpen}>
143
+ <DialogContent
144
+ className={cn(
145
+ 'sm:max-w-xl sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0',
146
+ 'sm:h-screen sm:max-h-screen sm:rounded-none sm:rounded-l-2xl',
147
+ 'flex flex-col gap-3 p-4 z-[70]',
148
+ )}
149
+ data-ai-customers-deal-sheet=""
150
+ >
151
+ <DialogHeader>
152
+ <div className="flex items-center justify-between gap-3">
153
+ <DialogTitle>
154
+ {t(
155
+ 'customers.ai_assistant.dealDetail.sheet.title',
156
+ 'Customers AI assistant — deal',
157
+ )}
158
+ </DialogTitle>
159
+ {pageContext.extra.stage ? (
160
+ <span
161
+ className="inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
162
+ data-ai-customers-deal-stage-pill=""
163
+ data-ai-customers-deal-stage={pageContext.extra.stage}
164
+ >
165
+ {pageContext.extra.stage}
166
+ </span>
167
+ ) : null}
168
+ </div>
169
+ <DialogDescription>
170
+ {t(
171
+ 'customers.ai_assistant.dealDetail.sheet.description',
172
+ 'Ask about this deal. With the per-tenant mutation-policy override enabled, the assistant can also propose a stage change that you confirm before anything is saved.',
173
+ )}
174
+ </DialogDescription>
175
+ </DialogHeader>
176
+ <div className="min-h-0 flex-1" data-ai-customers-deal-chat-container="">
177
+ <AiChat
178
+ agent={CUSTOMERS_AI_DEAL_DETAIL_AGENT_ID}
179
+ pageContext={pageContext as unknown as Record<string, unknown>}
180
+ className="h-full"
181
+ placeholder={t(
182
+ 'customers.ai_assistant.dealDetail.sheet.composerPlaceholder',
183
+ 'Ask about this deal, the stage, pipeline...',
184
+ )}
185
+ />
186
+ </div>
187
+ </DialogContent>
188
+ </Dialog>
189
+ </>
190
+ )
191
+ }
@@ -0,0 +1,37 @@
1
+ import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'
2
+ import AiDealDetailTriggerWidget from './widget.client'
3
+
4
+ /**
5
+ * Step 5.15 (Phase 3 WS-D) — Customers Deal detail AiChat injection.
6
+ *
7
+ * Extends the Step 4.10 pattern to a record-level surface. Drops an
8
+ * "Ask AI" trigger on the deal detail page (`detail:customers.deal:header`)
9
+ * and opens a sheet embedding
10
+ * `<AiChat agent="customers.account_assistant" pageContext={…} />` with a
11
+ * deal-scoped `pageContext` shape:
12
+ *
13
+ * { view: 'customers.deal.detail',
14
+ * recordType: 'deal',
15
+ * recordId: <dealId>,
16
+ * extra: { stage, pipelineStageId } }
17
+ *
18
+ * Wires the stable conversation id so the Step 5.13
19
+ * `customers.update_deal_stage` mutation tool's idempotency hash stays
20
+ * constant across repeated confirms / retries within the same chat.
21
+ *
22
+ * Feature-gated behind `customers.deals.view` + `ai_assistant.view`.
23
+ */
24
+ const widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
25
+ metadata: {
26
+ id: 'customers.injection.ai-deal-detail-trigger',
27
+ title: 'Customers AI Deal Detail Trigger',
28
+ description:
29
+ 'Renders an "Ask AI" button in the deal detail header that opens a sheet embedding the customers account assistant with deal-scoped page context.',
30
+ features: ['customers.deals.view', 'ai_assistant.view'],
31
+ priority: 100,
32
+ enabled: true,
33
+ },
34
+ Widget: AiDealDetailTriggerWidget,
35
+ }
36
+
37
+ export default widget
@@ -0,0 +1,41 @@
1
+ import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
2
+
3
+ /**
4
+ * Step 4.10 / Step 5.15 — customers module injection table.
5
+ *
6
+ * - Step 4.10 drops the `ai-assistant-trigger` widget on the People-list
7
+ * `DataTable` `:search-trailing` slot, which renders adjacent to the
8
+ * list search input. The previous mount point was the `:header` slot
9
+ * (separate row); the round icon-only trigger now lives next to the
10
+ * search box for a tighter, single-row toolbar.
11
+ * - Step 5.15 (Phase 3 WS-D) adds the `ai-deal-detail-trigger` widget on
12
+ * the Deal detail page header spot (`detail:customers.deal:header`).
13
+ *
14
+ * Both widgets embed `<AiChat agent="customers.account_assistant" …>`
15
+ * with a selection- or record-aware `pageContext`. The page files
16
+ * themselves only register the shared `<InjectionSpot>` mount point —
17
+ * the trigger, sheet, and chat surface live entirely in the injection
18
+ * widgets so third-party modules can copy the pattern unchanged.
19
+ */
20
+ export const injectionTable: ModuleInjectionTable = {
21
+ 'data-table:customers.people.list:search-trailing': [
22
+ {
23
+ widgetId: 'customers.injection.ai-assistant-trigger',
24
+ priority: 100,
25
+ },
26
+ ],
27
+ 'data-table:customers.companies.list:search-trailing': [
28
+ {
29
+ widgetId: 'customers.injection.ai-assistant-trigger',
30
+ priority: 100,
31
+ },
32
+ ],
33
+ 'detail:customers.deal:header': [
34
+ {
35
+ widgetId: 'customers.injection.ai-deal-detail-trigger',
36
+ priority: 100,
37
+ },
38
+ ],
39
+ }
40
+
41
+ export default injectionTable
@@ -278,6 +278,10 @@ Returns on error: error message with appropriate detail.`,
278
278
  actionId: z.string().uuid().describe('The UUID of the action to accept'),
279
279
  }),
280
280
  requiredFeatures: ['inbox_ops.proposals.manage'],
281
+ // Accepting an inbox action creates downstream entities (orders, contacts,
282
+ // etc.) in target modules — must surface as a write so any agent that
283
+ // whitelists it routes through the approval card.
284
+ isMutation: true,
281
285
  handler: async (input: { proposalId: string; actionId: string }, ctx: ToolContext) => {
282
286
  const scope = requireTenantContext(ctx)
283
287
  if (!ctx.userId) {
@@ -1,4 +1,6 @@
1
1
  import { generateObject } from 'ai'
2
+ import type { AwilixContainer } from 'awilix'
3
+ import { createContainer } from 'awilix'
2
4
  import {
3
5
  resolveFirstConfiguredOpenCodeProvider,
4
6
  resolveOpenCodeModel,
@@ -6,6 +8,11 @@ import {
6
8
  resolveOpenCodeProviderId,
7
9
  type OpenCodeProviderId,
8
10
  } from '@open-mercato/shared/lib/ai/opencode-provider'
11
+ import {
12
+ AiModelFactoryError,
13
+ createModelFactory,
14
+ type AiModelFactory,
15
+ } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/model-factory'
9
16
  import { extractionOutputSchema } from '../data/validators'
10
17
 
11
18
  // Vercel AI SDK provider factories return LanguageModelV1 but generateObject()
@@ -16,6 +23,24 @@ function asAiModel(model: unknown): AiModel {
16
23
  return model as AiModel
17
24
  }
18
25
 
26
+ /**
27
+ * Step 5.1 — thin backward-compatibility shim. The public surface of this
28
+ * module (`resolveExtractionProviderId`, `createStructuredModel`,
29
+ * `withTimeout`, `runExtractionWithConfiguredProvider`) is unchanged so
30
+ * `ai-tools.ts`, `translationProvider.ts`, and `extractionWorker.ts` continue
31
+ * to compile and pass their existing tests.
32
+ *
33
+ * The model-instantiation path inside {@link runExtractionWithConfiguredProvider}
34
+ * now delegates to the shared {@link createModelFactory} so every
35
+ * AI-runtime caller shares one resolution order. The legacy
36
+ * `OPENCODE_MODEL` / `OPENCODE_PROVIDER` envs remain honored via
37
+ * {@link resolveExtractionProviderId} and {@link resolveOpenCodeModel} so
38
+ * inbox_ops deployments do not see a behavior change — the factory is
39
+ * consulted first (honoring `INBOX_OPS_AI_MODEL` + `input.modelOverride`),
40
+ * with the legacy path as the fallback when no registry provider is
41
+ * configured (preserving the historical error messages).
42
+ */
43
+
19
44
  export function resolveExtractionProviderId(): OpenCodeProviderId {
20
45
  const configuredProvider = process.env.OPENCODE_PROVIDER
21
46
  if (configuredProvider && configuredProvider.trim().length > 0) {
@@ -73,6 +98,48 @@ export async function withTimeout<T>(
73
98
  }
74
99
  }
75
100
 
101
+ /**
102
+ * Test-only seam for the factory-delegation regression suite. Production
103
+ * callers MUST use {@link runExtractionWithConfiguredProvider} directly; the
104
+ * suite overrides this binding via `jest.spyOn` to assert the shim actually
105
+ * reaches `createModelFactory` without stubbing `@open-mercato/ai-assistant`.
106
+ */
107
+ export const __inboxOpsLlmProviderInternal = {
108
+ createModelFactory,
109
+ createContainer,
110
+ }
111
+
112
+ function tryFactoryResolution(input: {
113
+ modelOverride?: string | null
114
+ }): { modelId: string; providerId: OpenCodeProviderId; model: AiModel } | null {
115
+ let factory: AiModelFactory
116
+ try {
117
+ const container = __inboxOpsLlmProviderInternal.createContainer()
118
+ factory = __inboxOpsLlmProviderInternal.createModelFactory(container as AwilixContainer)
119
+ } catch {
120
+ return null
121
+ }
122
+ try {
123
+ const resolution = factory.resolveModel({
124
+ moduleId: 'inbox_ops',
125
+ callerOverride: input.modelOverride ?? undefined,
126
+ })
127
+ const providerId = resolveOpenCodeProviderId(resolution.providerId)
128
+ return {
129
+ modelId: resolution.modelId,
130
+ providerId,
131
+ model: asAiModel(resolution.model),
132
+ }
133
+ } catch (err) {
134
+ if (err instanceof AiModelFactoryError) {
135
+ // Fall back to the legacy path so the shim keeps throwing the original
136
+ // OPENCODE_*-era error messages existing tests/consumers rely on.
137
+ return null
138
+ }
139
+ throw err
140
+ }
141
+ }
142
+
76
143
  export async function runExtractionWithConfiguredProvider(input: {
77
144
  systemPrompt: string
78
145
  userPrompt: string
@@ -83,13 +150,22 @@ export async function runExtractionWithConfiguredProvider(input: {
83
150
  totalTokens: number
84
151
  modelWithProvider: string
85
152
  }> {
86
- const providerId = resolveExtractionProviderId()
87
- const apiKey = requireOpenCodeProviderApiKey(providerId)
153
+ const factoryResolution = tryFactoryResolution({ modelOverride: input.modelOverride })
88
154
 
89
- const modelConfig = resolveOpenCodeModel(providerId, {
90
- overrideModel: input.modelOverride,
91
- })
92
- const model = await createStructuredModel(providerId, apiKey, modelConfig.modelId)
155
+ let model: AiModel
156
+ let modelWithProvider: string
157
+ if (factoryResolution) {
158
+ model = factoryResolution.model
159
+ modelWithProvider = `${factoryResolution.providerId}/${factoryResolution.modelId}`
160
+ } else {
161
+ const providerId = resolveExtractionProviderId()
162
+ const apiKey = requireOpenCodeProviderApiKey(providerId)
163
+ const modelConfig = resolveOpenCodeModel(providerId, {
164
+ overrideModel: input.modelOverride,
165
+ })
166
+ model = await createStructuredModel(providerId, apiKey, modelConfig.modelId)
167
+ modelWithProvider = modelConfig.modelWithProvider
168
+ }
93
169
 
94
170
  const result = await withTimeout(
95
171
  generateObject({
@@ -106,6 +182,6 @@ export async function runExtractionWithConfiguredProvider(input: {
106
182
  return {
107
183
  object: result.object,
108
184
  totalTokens: Number(result.usage?.totalTokens ?? 0) || 0,
109
- modelWithProvider: modelConfig.modelWithProvider,
185
+ modelWithProvider,
110
186
  }
111
187
  }
@@ -0,0 +1,11 @@
1
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
+
3
+ export const setup: ModuleSetupConfig = {
4
+ defaultRoleFeatures: {
5
+ superadmin: ['notifications.*'],
6
+ admin: ['notifications.*'],
7
+ employee: ['notifications.view'],
8
+ },
9
+ }
10
+
11
+ export default setup