@digimuza/system-core 0.2.0-rc.2 → 0.3.0-rc.1

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 (151) hide show
  1. package/INTEGRATION.md +209 -3
  2. package/README.md +23 -0
  3. package/dist/ai/api.d.ts +120 -0
  4. package/dist/ai/api.d.ts.map +1 -0
  5. package/dist/ai/api.js +446 -0
  6. package/dist/ai/api.js.map +1 -0
  7. package/dist/ai/card.d.ts +27 -0
  8. package/dist/ai/card.d.ts.map +1 -0
  9. package/dist/ai/card.js +84 -0
  10. package/dist/ai/card.js.map +1 -0
  11. package/dist/ai/contract.d.ts +364 -0
  12. package/dist/ai/contract.d.ts.map +1 -0
  13. package/dist/ai/contract.js +101 -0
  14. package/dist/ai/contract.js.map +1 -0
  15. package/dist/ai/display.d.ts +20 -0
  16. package/dist/ai/display.d.ts.map +1 -0
  17. package/dist/ai/display.js +35 -0
  18. package/dist/ai/display.js.map +1 -0
  19. package/dist/ai/index.d.ts +7 -0
  20. package/dist/ai/index.d.ts.map +1 -0
  21. package/dist/ai/index.js +10 -0
  22. package/dist/ai/index.js.map +1 -0
  23. package/dist/ai/manifest.d.ts +25 -0
  24. package/dist/ai/manifest.d.ts.map +1 -0
  25. package/dist/ai/manifest.js +15 -0
  26. package/dist/ai/manifest.js.map +1 -0
  27. package/dist/ai/runner.d.ts +71 -0
  28. package/dist/ai/runner.d.ts.map +1 -0
  29. package/dist/ai/runner.js +246 -0
  30. package/dist/ai/runner.js.map +1 -0
  31. package/dist/ai/tools.d.ts +42 -0
  32. package/dist/ai/tools.d.ts.map +1 -0
  33. package/dist/ai/tools.js +117 -0
  34. package/dist/ai/tools.js.map +1 -0
  35. package/dist/audit/csv.d.ts +31 -0
  36. package/dist/audit/csv.d.ts.map +1 -0
  37. package/dist/audit/csv.js +39 -0
  38. package/dist/audit/csv.js.map +1 -0
  39. package/dist/audit/index.d.ts +5 -0
  40. package/dist/audit/index.d.ts.map +1 -0
  41. package/dist/audit/index.js +6 -0
  42. package/dist/audit/index.js.map +1 -0
  43. package/dist/convex/functions.d.ts +39 -0
  44. package/dist/convex/functions.d.ts.map +1 -1
  45. package/dist/convex/functions.js +38 -6
  46. package/dist/convex/functions.js.map +1 -1
  47. package/dist/convex/index.d.ts +1 -1
  48. package/dist/convex/index.d.ts.map +1 -1
  49. package/dist/convex/index.js.map +1 -1
  50. package/dist/core/access.d.ts +14 -0
  51. package/dist/core/access.d.ts.map +1 -1
  52. package/dist/core/access.js +62 -0
  53. package/dist/core/access.js.map +1 -1
  54. package/dist/core/audit-module.d.ts +5 -0
  55. package/dist/core/audit-module.d.ts.map +1 -0
  56. package/dist/core/audit-module.js +25 -0
  57. package/dist/core/audit-module.js.map +1 -0
  58. package/dist/core/audit.d.ts +31 -0
  59. package/dist/core/audit.d.ts.map +1 -1
  60. package/dist/core/audit.js +46 -2
  61. package/dist/core/audit.js.map +1 -1
  62. package/dist/core/catalog.d.ts +41 -0
  63. package/dist/core/catalog.d.ts.map +1 -1
  64. package/dist/core/catalog.js +45 -0
  65. package/dist/core/catalog.js.map +1 -1
  66. package/dist/core/context.d.ts +8 -2
  67. package/dist/core/context.d.ts.map +1 -1
  68. package/dist/core/context.js +4 -1
  69. package/dist/core/context.js.map +1 -1
  70. package/dist/core/index.d.ts +2 -0
  71. package/dist/core/index.d.ts.map +1 -1
  72. package/dist/core/index.js +2 -0
  73. package/dist/core/index.js.map +1 -1
  74. package/dist/core/notify.d.ts +37 -0
  75. package/dist/core/notify.d.ts.map +1 -0
  76. package/dist/core/notify.js +16 -0
  77. package/dist/core/notify.js.map +1 -0
  78. package/dist/core/roles.d.ts.map +1 -1
  79. package/dist/core/roles.js +5 -1
  80. package/dist/core/roles.js.map +1 -1
  81. package/dist/core/system-modules.d.ts +4 -0
  82. package/dist/core/system-modules.d.ts.map +1 -1
  83. package/dist/core/system-modules.js +11 -0
  84. package/dist/core/system-modules.js.map +1 -1
  85. package/dist/finance/ai-tools.d.ts +55 -0
  86. package/dist/finance/ai-tools.d.ts.map +1 -0
  87. package/dist/finance/ai-tools.js +65 -0
  88. package/dist/finance/ai-tools.js.map +1 -0
  89. package/dist/finance/index.d.ts +87 -1
  90. package/dist/finance/index.d.ts.map +1 -1
  91. package/dist/finance/index.js +92 -38
  92. package/dist/finance/index.js.map +1 -1
  93. package/dist/finance/search.d.ts +15 -0
  94. package/dist/finance/search.d.ts.map +1 -0
  95. package/dist/finance/search.js +51 -0
  96. package/dist/finance/search.js.map +1 -0
  97. package/dist/index.d.ts +2 -0
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +2 -0
  100. package/dist/index.js.map +1 -1
  101. package/dist/modules/index.d.ts +1 -1
  102. package/dist/modules/index.d.ts.map +1 -1
  103. package/dist/modules/index.js +1 -1
  104. package/dist/modules/index.js.map +1 -1
  105. package/dist/notifications/api.d.ts +130 -0
  106. package/dist/notifications/api.d.ts.map +1 -0
  107. package/dist/notifications/api.js +384 -0
  108. package/dist/notifications/api.js.map +1 -0
  109. package/dist/notifications/contract.d.ts +132 -0
  110. package/dist/notifications/contract.d.ts.map +1 -0
  111. package/dist/notifications/contract.js +59 -0
  112. package/dist/notifications/contract.js.map +1 -0
  113. package/dist/notifications/index.d.ts +6 -0
  114. package/dist/notifications/index.d.ts.map +1 -0
  115. package/dist/notifications/index.js +9 -0
  116. package/dist/notifications/index.js.map +1 -0
  117. package/dist/search/index.d.ts +95 -0
  118. package/dist/search/index.d.ts.map +1 -0
  119. package/dist/search/index.js +151 -0
  120. package/dist/search/index.js.map +1 -0
  121. package/package.json +41 -2
  122. package/src/ai/api.ts +535 -0
  123. package/src/ai/card.ts +91 -0
  124. package/src/ai/contract.ts +245 -0
  125. package/src/ai/display.ts +38 -0
  126. package/src/ai/index.ts +9 -0
  127. package/src/ai/manifest.ts +16 -0
  128. package/src/ai/runner.ts +275 -0
  129. package/src/ai/tools.ts +128 -0
  130. package/src/audit/csv.ts +60 -0
  131. package/src/audit/index.ts +7 -0
  132. package/src/convex/functions.ts +54 -7
  133. package/src/convex/index.ts +1 -1
  134. package/src/core/access.ts +55 -1
  135. package/src/core/audit-module.ts +26 -0
  136. package/src/core/audit.ts +68 -2
  137. package/src/core/catalog.ts +63 -0
  138. package/src/core/context.ts +13 -2
  139. package/src/core/index.ts +2 -0
  140. package/src/core/notify.ts +52 -0
  141. package/src/core/roles.ts +5 -1
  142. package/src/core/system-modules.ts +12 -0
  143. package/src/finance/ai-tools.ts +68 -0
  144. package/src/finance/index.ts +119 -42
  145. package/src/finance/search.ts +58 -0
  146. package/src/index.ts +2 -0
  147. package/src/modules/index.ts +3 -0
  148. package/src/notifications/api.ts +477 -0
  149. package/src/notifications/contract.ts +142 -0
  150. package/src/notifications/index.ts +8 -0
  151. package/src/search/index.ts +198 -0
package/INTEGRATION.md CHANGED
@@ -21,10 +21,16 @@ Pin the version per tenant app (`"@digimuza/system-core": "0.1.0"`, or `~0.1.0`)
21
21
  | `@digimuza/system-core/next` | Next.js server | `createSystemServerClient()` (session token, tracing, typed errors), `createAgentClient()`. |
22
22
  | `@digimuza/system-core/react` | Client components | `<SystemProvider>`, `useCan`, `useVisibleModules`, `useCurrentUser`, `useFeatureFlag`, `useRoleView`, `<Can>`. |
23
23
  | `@digimuza/system-core/finance` | Convex | The finance business module: `finance` manifest, `financeTables`, `defineFinance(system)` functions, `FinancePermissions`, `InvoiceView`. |
24
- | `@digimuza/system-core` | Any | `SystemError`, redaction, logger, tracer, shared types. |
24
+ | `@digimuza/system-core/ai` | Convex + browser (types) | The AI assistant module: `ai` manifest, `aiTables`, `defineAi(system, options)` functions, `defineAiTool`, `defineAiAgent`, `executeAiTool`, views (`ThreadView`, `MessageView`, `AgentView`, …). No Mastra / AI SDK imports. Finance tools: `financeTools` from `./finance`. |
25
+ | `@digimuza/system-core/ai/card` | Browser (no dependencies) | `cardDocument`, `AI_CARD_CSP`, `AI_CARD_THEME_VARS`, `AI_CARD_RESIZE_MESSAGE`: renders `ai.show_card` HTML in a sandboxed iframe. |
26
+ | `@digimuza/system-core/ai/runner` | Convex Node runtime only (`"use node"`) | `defineAiRunner(options)` → `{ runAgent }`, the Mastra runner (OpenAI or any OpenAI-compatible Chat Completions API, e.g. 9Router). Needs the optional peers `@mastra/core`, `@ai-sdk/openai`, `ai`. |
27
+ | `@digimuza/system-core/search` | Convex | Global search: `defineSearchProvider`, `defineSearch(system, { providers })` → `{ search }`, `SearchHit`, `SearchResult`. Finance provider: `financeSearch` from `./finance`. |
28
+ | `@digimuza/system-core/notifications` | Convex + browser (types) | `notificationTables`, `notificationsHook()`, `defineNotifications(system, options)`, `notify`, `defineNotificationType`, views (`NotificationView`, `NotificationPreferenceView`). |
29
+ | `@digimuza/system-core/audit` | Browser (no dependencies) | `auditToCsv`, `csvCell`, `AUDIT_CSV_COLUMNS`, `auditModuleOf`, audit types. |
30
+ | `@digimuza/system-core` | Any | `SystemError`, redaction, logger, tracer, shared types, `auditToCsv`. |
25
31
  | `@digimuza/system-core/testing` | Tests | `MemoryStore` (same semantics as Convex, serializable transactions). |
26
32
 
27
- These seven subpaths, listed in `package.json#exports`, are the whole public surface. Deep imports
33
+ These thirteen subpaths, listed in `package.json#exports`, are the whole public surface. Deep imports
28
34
  (`@digimuza/system-core/dist/...`) are not supported. Server and browser entries stay separate:
29
35
  `.`, `./modules` and `./react` never import `./config`, `./convex` or `./next`, and never read
30
36
  `process.env`. `npm run check:pack` enforces this in CI. Removing or renaming an entry or export,
@@ -86,7 +92,7 @@ export const {
86
92
  assignRoles, revokeRole, setDelegationScope,
87
93
  listInvites, getInvite, revokeInvite, createInvite, resendInvite, _createInvite, _resendInvite, _recordInviteDelivery,
88
94
  listMyApiKeys, setApiKeyRoles, revokeApiKey, createApiKey, rotateApiKey, _createApiKey, _rotateApiKey,
89
- listFeatureFlags, setFeatureFlag, resetFeatureFlag, listAudit,
95
+ listFeatureFlags, setFeatureFlag, resetFeatureFlag, listAudit, listAuditFacets, recordAuditExport,
90
96
  roleViewStatus, startRoleView, stopRoleView,
91
97
  } = system.functions
92
98
  ```
@@ -121,6 +127,206 @@ Each function guards itself (permission, module enabled, role view), checks the
121
127
  object (existence, state transition), writes, and records an audit event in one
122
128
  transaction. The UI stays in the app.
123
129
 
130
+ ### AI assistant (`@digimuza/system-core/ai`)
131
+
132
+ A reusable chat with an assistant that can call the app's business functions as
133
+ tools, as the signed-in person and with that person's permissions. Threads,
134
+ messages and runs live in Convex (live queries); a Node action runs the model
135
+ (Mastra, OpenAI-compatible Chat Completions) and writes the answer back in small steps.
136
+
137
+ Install (five files):
138
+
139
+ ```ts
140
+ // convex/schema.ts
141
+ import { aiTables } from "@digimuza/system-core/ai"
142
+ export default defineSchema({ ...systemTables, ...financeTables, ...aiTables })
143
+
144
+ // modules/index.ts (or wherever the app lists its modules for defineConvexSystem)
145
+ import { ai } from "@digimuza/system-core/ai"
146
+ export const appModules = [ai, finance /* , … */]
147
+
148
+ // convex/aiConfig.ts: shared by ai.ts and aiRunner.ts, so NO "use node" here
149
+ import { defineAiAgent, type AiOptions } from "@digimuza/system-core/ai"
150
+ import { financeTools } from "@digimuza/system-core/finance"
151
+ export const aiOptions: AiOptions = {
152
+ defaultModel: "cc/claude-haiku-4-5-20251001", // must exist on the provider (here 9Router)
153
+ agents: [defineAiAgent({ id: "assistant", name: "Assistant", description: "…", instructions: "…", tools: [...financeTools] })],
154
+ }
155
+
156
+ // convex/ai.ts
157
+ import { defineAi } from "@digimuza/system-core/ai"
158
+ import { aiOptions } from "./aiConfig"
159
+ import { system } from "./system"
160
+ export const { listAgents, listThreads, getThread, listMessages, createThread, sendMessage, stopRun, respondToToolCall, renameThread, archiveThread,
161
+ _startRun, _appendText, _addToolCall, _executeTool, _setToolCallState, _finishRun, _isStopRequested } = defineAi(system, aiOptions).functions
162
+
163
+ // convex/aiRunner.ts
164
+ "use node"
165
+ import { defineAiRunner } from "@digimuza/system-core/ai/runner"
166
+ import { aiOptions } from "./aiConfig"
167
+ export const { runAgent } = defineAiRunner(aiOptions)
168
+ ```
169
+
170
+ Add the runner's dependencies to the app (`@mastra/core`, `@ai-sdk/openai`, `ai`;
171
+ optional peers of this package). Function names and file names are fixed by
172
+ `AI_PUBLIC_FUNCTIONS`, `AI_INTERNAL_FUNCTIONS` and `AI_RUNNER_FUNCTION`; change
173
+ the files with `functionsModule` / `runnerModule` in the options.
174
+
175
+ Environment, on the **Convex deployment** (not in Next.js):
176
+
177
+ | Variable | Required | Meaning |
178
+ | - | - | - |
179
+ | `OPENAI_API_KEY` | No | API key of the provider (OpenAI, or the 9Router key). Without it every answer ends with `UNAVAILABLE` "AI model is not configured". Never logged or returned. |
180
+ | `OPENAI_BASE_URL` | No | Base URL of an OpenAI-compatible API, e.g. the 9Router proxy `https://router.digimuza.ai/v1`. Default: api.openai.com. Or pass `baseURL` to `defineAiRunner`. |
181
+ | `AI_MODEL` | No | Overrides `defaultModel` for agents without their own `model`. Any string the provider accepts (router names like `cc/claude-haiku-4-5-20251001`). Read when a message is sent; the run keeps its model. |
182
+
183
+ The runner uses the **Chat Completions API** (`POST <baseURL>/chat/completions`,
184
+ streaming, tools), not OpenAI's Responses API, so any OpenAI-compatible proxy works.
185
+ Model ids are plain strings and must exist on that provider. Tool ids are sent as
186
+ function names with dots replaced by underscores (`finance.list_invoices` →
187
+ `finance_list_invoices`). Runner options: `openAiApiKey`, `baseURL`, `fetch`
188
+ (request middleware / tests) and the test hook `model`.
189
+
190
+ **Permissions.** `ai` is a business module: it registers disabled and a Super
191
+ enables it. `ai:chat:use` (subrole `ai:user`) lets a person chat and see their own
192
+ threads; nobody, Super included, sees someone else's thread (NOT_FOUND). The chat
193
+ is human-only (API keys get FORBIDDEN). What the assistant may *do* is decided per
194
+ tool by the tool's own permission: the model is only offered the tools the actor
195
+ holds right now, `listAgents` shows only those, and every call is re-authorized
196
+ with `authorizeJob` (module enabled, role view, suspended user…) just before it runs.
197
+
198
+ **Writing a tool.** A module exposes its actions with `defineAiTool`, next to the
199
+ module's own functions (see `src/finance/ai-tools.ts`):
200
+
201
+ ```ts
202
+ export const crmListLeadsTool = defineAiTool({
203
+ id: "crm.list_leads", // module.verb_object
204
+ description: "Lists leads, newest first.", // for the model: what and when
205
+ permission: "crm:lead:read", // the module's own permission
206
+ input: z.object({ limit: z.number().int().min(1).max(50).optional() }),
207
+ handler: async ({ db, sys, actor }, input) => listLeadViews(db, input), // JSON result
208
+ })
209
+ ```
210
+
211
+ Rules:
212
+
213
+ - `permission` is the owning module's own permission; never `ai:chat:use`.
214
+ - Anything that writes sets `requiresApproval: true`.
215
+ - Reuse the module's shared operations (the same code the module's Convex
216
+ functions call) so the business rules and audit events are identical.
217
+ - **Validate everything before the first write.** The handler runs inside the
218
+ `_executeTool` mutation. A failure (thrown `SystemError` or anything else) is
219
+ returned to the model as `{ ok: false, code }`, not thrown, so the `ai.tool.run`
220
+ audit event is kept. The consequence: writes the handler made before it failed
221
+ are committed too. Check existence, state, uniqueness and limits first, then
222
+ write once (the finance tools do).
223
+ - Throw `SystemError` (`notFound`, `conflict`, …) for expected failures; the code
224
+ reaches the model and the UI, the details do not.
225
+
226
+ **Approvals.** A `requiresApproval` call is stored as `awaiting-approval`, the run
227
+ parks (`waiting-approval`) and the UI shows the call with its arguments.
228
+ `respondToToolCall({ messageId, toolCallId, approve })` records the answer
229
+ (audit `ai.tool.approve` / `ai.tool.reject`); a second answer for the same call is
230
+ CONFLICT. When nothing is pending the run is queued again and the runner executes
231
+ approved calls and tells the model about rejected ones. `stopRun` rejects pending
232
+ calls. `_executeTool` refuses an approval-gated call that is not approved.
233
+
234
+ **Errors.** A failed answer has `MessageView.error = { code, message, operationId }`;
235
+ `operationId` is the run's, show it so support can find the logs.
236
+
237
+ **Audit actions.** `ai.thread.create`, `ai.message.send`, `ai.tool.run` (success,
238
+ failed or denied, with the tool call id), `ai.tool.approve`, `ai.tool.reject`,
239
+ `ai.run.stop`, plus the business module's own events from the tool handler
240
+ (e.g. `finance.invoice.create`).
241
+
242
+ Cost: the provider bills per token; every message sends the whole thread history.
243
+
244
+ #### HTML cards (`ai.show_card`)
245
+
246
+ `displayTools` adds `ai.show_card` (permission `ai:chat:use`, no approval, no data access): the model sends
247
+ `{ title?, html }` and the chat shows it as a card. Add it to an agent with `tools: [...financeTools, ...displayTools]`.
248
+
249
+ The HTML is model output, so treat it as untrusted (prompt injection can reach it through tool results). Render it
250
+ **only** as `<iframe sandbox="allow-scripts" srcDoc={cardDocument({ html, id, theme, dark })}>` (import from `@digimuza/system-core/ai/card` in client code), never with
251
+ `allow-same-origin`. `cardDocument` puts `AI_CARD_CSP` first (inline styles/scripts only, `img-src data:`, no
252
+ network, fonts, frames or forms), passes the app's theme variables (`AI_CARD_THEME_VARS`), and posts the content
253
+ height to the parent as `{ type: AI_CARD_RESIZE_MESSAGE, id, height }`; accept it only from that iframe's window and
254
+ clamp it.
255
+
256
+ ### Global search (`@digimuza/system-core/search`)
257
+
258
+ ```ts
259
+ // convex/search.ts
260
+ import { defineSearch } from "@digimuza/system-core/search"
261
+ import { financeSearch } from "@digimuza/system-core/finance"
262
+ import { system } from "./system"
263
+ export const { search } = defineSearch(system, { providers: [financeSearch] }).functions
264
+ ```
265
+
266
+ `search({ query, limit? })` → `{ query, groups: [{ providerId, moduleId, label, hits: SearchHit[] }], total }`,
267
+ `SearchHit = { providerId, id, title, subtitle?, href, kind? }`. Human sessions only (API key: FORBIDDEN). The query is
268
+ trimmed (whitespace collapsed) and must be 2–100 characters (else VALIDATION, `fields: ["query"]`). `limit` is hits per
269
+ provider (default 5, max 20), at most 50 in total. A provider runs only when its module is enabled (or the caller is an
270
+ effective Super) and `decide` allows its permission now, so role view applies. Hits with an href that is not app-relative
271
+ (`/…`, not `//…`) are dropped; a provider that throws is skipped and logged (the query is never logged). Reads are not audited.
272
+
273
+ A module adds a provider with `defineSearchProvider({ id: "crm.leads", moduleId: "crm", label, permission: "crm:lead:read",
274
+ search: async ({ db, sys, actor }, { query, limit }) => hits })`. `financeSearch` (`finance:invoice:read`) matches invoice
275
+ numbers by prefix (`by_number`) and customers by full text (new search index `search_customer` on `invoices`, schema
276
+ compatible), href `/app/finance?invoice=<number>`.
277
+
278
+ ### Audit filters and CSV export
279
+
280
+ `listAudit` also takes `moduleId` (`finance` matches `finance.*`; core actions map to their system module, e.g.
281
+ `role.assign` → `roles`, see `auditModuleOf`) and `actionPrefix`. `listAuditFacets()` (same permission
282
+ `audit:event:read`) → `{ actions, moduleIds, scanned, complete }` of the newest 1000 events. For a CSV the UI pages
283
+ through `listAudit`, calls `auditToCsv(items, { bom: true })` (from `@digimuza/system-core/audit`; RFC 4180, CRLF,
284
+ columns `at, action, result, actor, apiKey, target, operationId, details`, cells starting with `= + - @ tab CR` get a
285
+ leading `'`) and then `recordAuditExport({ filter, count })` (audit action `audit.export`). AI events already carry what
286
+ the UI needs: `ai.tool.run` has `targetId` = tool id, `result`, `reason` = error code, `changes.toolCallId`;
287
+ `ai.tool.approve/reject` have `targetId` = tool call id and `changes: { runId, toolName }`.
288
+
289
+ ### Notifications (`@digimuza/system-core/notifications`)
290
+
291
+ ```ts
292
+ // convex/schema.ts
293
+ import { notificationTables } from "@digimuza/system-core/notifications"
294
+ export default defineSchema({ ...systemTables, ...financeTables, ...notificationTables })
295
+
296
+ // convex/system.ts: the hook makes every mutation's SystemContext able to notify
297
+ import { notificationsHook } from "@digimuza/system-core/notifications"
298
+ export const system = defineConvexSystem({ modules, notifications: notificationsHook() })
299
+
300
+ // convex/notifications.ts
301
+ import { defineNotifications } from "@digimuza/system-core/notifications"
302
+ export const {
303
+ listNotifications, unreadNotificationCount, markNotificationRead, markAllNotificationsRead,
304
+ getNotificationPreferences, setNotificationPreference,
305
+ _deliverNotificationEmail, _claimNotificationEmail, _recordNotificationEmail,
306
+ } = defineNotifications(system, {
307
+ renderEmail: (type, params, locale, { href }) => ({ subject: "…", text: `… ${process.env.SITE_URL}${href ?? ""}` }),
308
+ deliverEmail: async ({ to, subject, text, operationId }) => { /* Resend, SMTP …; throw on failure */ },
309
+ }).functions
310
+ ```
311
+
312
+ `notificationsHook({ functionsModule?: "notifications", email?: true, types?: [], defaultLocale?: "lt" })`. Set
313
+ `email: false` when the app has no email adapter. Without `renderEmail`/`deliverEmail` email rows become `skipped`.
314
+ `SITE_URL` (Convex env) is read by the app's adapter, not by core. Delivery runs in the default Convex runtime (fetch
315
+ works); a failure marks the row `failed` and never throws.
316
+
317
+ Types are declared on module manifests (`notificationTypes: [defineNotificationType({ id, moduleId, defaultChannels,
318
+ description?, params? })]`), so the system knows them in every Convex file. Built in: `roles.role_assigned`
319
+ (`{ roleNames }`, sent by `assignRoles`) and `finance.invoice_awaiting_approval` (`{ number, customer, amountCents,
320
+ currency }`, href `/app/finance?invoice=<number>`, sent to holders of `finance:invoice:approve` except the creator).
321
+ Both are no-ops when the hook is not installed.
322
+
323
+ A module notifies from a mutation with `notify(ctx, sys, { to: { userIds } | { permission }, type, params, href?,
324
+ actor?, includeActor? })`; it returns the number of recipients (at most 100). `to.permission` resolves active users who
325
+ hold it now through `decide` and leaves out the acting user (an agent's owner). Each recipient's preference applies:
326
+ both channels off means no row, in-app off gives an email-only row that is never listed. Access is own data, like the
327
+ profile: a human session plus `profile:own:read` / `profile:own:update`, which every active user holds through the
328
+ implicit Profile role.
329
+
124
330
  ### View as role (Super)
125
331
 
126
332
  A real Super (verified email in `SUPER_USER_EMAILS`) can preview the system as
package/README.md CHANGED
@@ -19,6 +19,9 @@ Pirmoji versija platinama vienu versijuojamu NPM paketu su atskirais importo kel
19
19
  | Convex aplikacija | `@digimuza/system-core/convex` | Sistemos registraciją, užklausų ir mutacijų pagalbines funkcijas, teisių patikrą |
20
20
  | Next.js serveris | `@digimuza/system-core/next` | Autentifikuotos sesijos perdavimą ir tipizuotą kreipimąsi į Convex |
21
21
  | Next.js kliento komponentai | `@digimuza/system-core/react` | Dabartinio vartotojo, matomų modulių ir leidžiamų veiksmų duomenis kliento kuriamam UI |
22
+ | Paieška | `@digimuza/system-core/search` | Modulių paieškos tiekėjus ir bendrą `search` užklausą |
23
+ | Pranešimai | `@digimuza/system-core/notifications` | Pranešimų lenteles, tipus, `notify` ir vartotojo funkcijas |
24
+ | Audito eksportas | `@digimuza/system-core/audit` | Naršyklei saugų `auditToCsv` |
22
25
 
23
26
  Šie pavadinimai yra projektuojama v1 sutartis. Paleidimo konfigūracija, Infisical klientas ir paslaptys pasiekiami tik serverio importuose. Kliento komponentų importavimas neturi reikalauti serverio aplinkos kintamųjų.
24
27
 
@@ -98,6 +101,26 @@ Tikras `Super` (patvirtintas el. paštas `SUPER_USER_EMAILS` sąraše) gali laik
98
101
 
99
102
  `@digimuza/system-core/finance` yra pirmasis pakartotinai naudojamas verslo modulis: manifestas, `financeTables`, `defineFinance(system)` funkcijos ir teisės. Aplikacija tik įtraukia lenteles į schemą ir eksportuoja funkcijas; vartotojo sąsaja lieka aplikacijoje.
100
103
 
104
+ # AI asistentas
105
+
106
+ `@digimuza/system-core/ai` yra verslo modulis `ai`: pokalbis su asistentu, kuris gali naudoti aplikacijos funkcijas kaip įrankius. Asistentas veikia prisijungusio žmogaus vardu ir tik su jo teisėmis. Modulis registruojamas išjungtas; jį įjungia `Super`.
107
+
108
+ 1. `ai:chat:use` (subrolė `ai:user`) leidžia kalbėtis su asistentu ir matyti tik savo pokalbius. Kito žmogaus pokalbio nemato niekas, ir `Super` taip pat. API raktai kalbėtis negali.
109
+ 2. Ką asistentas gali *daryti*, lemia kiekvieno įrankio teisė (pvz. `finance:invoice:read`). Modeliui siūlomi tik tie įrankiai, kurių teisę veikėjas turi dabar, o prieš kiekvieną kvietimą teisė tikrinama iš naujo. Asistentas neturi savo rolių: atėmus rolę, kitas įrankio kvietimas atmetamas (`FORBIDDEN`), o `Super`, peržiūrintis kaip rolė, asistentu gali tik tai, ką gali ta rolė.
110
+ 3. Įrankį apibrėžia modulis su `defineAiTool`: teisė yra paties modulio teisė, rašantys veiksmai reikalauja žmogaus patvirtinimo (`requiresApproval`), o tvarkyklė viską patikrina prieš pirmą įrašymą, nes nepavykusios tvarkyklės ankstesni įrašai lieka.
111
+ 4. Kiekvienas įrankio kvietimas, patvirtinimas ir atmetimas įrašomas į auditą. Nepavykęs atsakymas rodo klaidos kodą ir `operationId`.
112
+ 5. Modelis kviečiamas Convex Node veiksme (Mastra, OpenAI suderinamas Chat Completions API). `OPENAI_BASE_URL` nurodo tarpinį serverį (pvz. 9Router `https://router.digimuza.ai/v1`), `OPENAI_API_KEY` yra jo raktas, `AI_MODEL` yra jame esantis modelio pavadinimas. Be `OPENAI_API_KEY` asistentas atsako, kad nesukonfigūruotas. Naudojimas apmokamas pagal žetonus.
113
+
114
+ Įdiegimo žingsniai ir įrankio pavyzdys: `INTEGRATION.md`, skyrius „AI assistant“.
115
+
116
+ # Paieška, auditas ir pranešimai
117
+
118
+ `@digimuza/system-core/search`: viena paieška per visus modulius. Kiekvienas modulis pateikia tiekėją su savo teise. Užklausa vykdoma tik tiems tiekėjams, kurių modulis įjungtas ir kurių teisę žmogus turi dabar (`Super`, peržiūrintis kaip rolė, gauna tos rolės rezultatus). Užklausa yra 2–100 simbolių, rezultatų skaičius ribojamas. API raktai paieškos nenaudoja. Skaitymas į auditą neįrašomas.
119
+
120
+ Auditą galima filtruoti pagal modulį ir veiksmo prefiksą. `listAuditFacets` grąžina esamus veiksmus ir modulius filtrams. CSV eksportą sudaro naršyklėje `auditToCsv` (RFC 4180, apsauga nuo formulių įterpimo), o pats eksportas įrašomas į auditą (`audit.export`).
121
+
122
+ `@digimuza/system-core/notifications` yra bendras pranešimų pagrindas. Branduolys saugo tipą ir JSON parametrus, o tekstą (LT/EN, UI ir el. laiške) kuria aplikacija. Gavėjai nurodomi vartotojų ID arba teise; teisės turėtojai nustatomi tuo metu galiojančiu prieigos sprendimu, veiksmo autorius praleidžiamas. Kiekvienas vartotojas mato ir tvarko tik savo pranešimus ir nustatymus (kaip profilį). El. laiškus siunčia aplikacijos adapteris; nepavykęs siuntimas pažymimas `failed` ir verslo operacijos nesugadina. Pranešimai siunčiami priskyrus roles ir sukūrus sąskaitą, kurią reikia patvirtinti; be įdiegtų pranešimų šie veiksmai veikia kaip anksčiau.
123
+
101
124
  # Rolės ir modulio registracija
102
125
 
103
126
  Vienintelė įmonės valdomų suteikimų grandinė yra gavėjas → rolė → subrolė → teisė. Gavėjas yra vartotojas arba agento API raktas. Kiekviename lygyje leidžiama turėti kelias sąsajas. Viena subrolė priklauso vienam moduliui. Rolė gali sujungti kelių modulių subroles. Tiesioginis subrolės ar teisės suteikimas gavėjui yra neleistina operacija.
@@ -0,0 +1,120 @@
1
+ import { type ConvexSystem } from "../convex/functions.js";
2
+ import { type AgentView, type AiMessageId, type AiOptions, type AiRunId, type AiThreadId, type MessageView, type RunContext, type ThreadView, type ToolExecutionResult } from "./contract.js";
3
+ export declare const AI_MESSAGE_MAX = 8000;
4
+ export declare const AI_TITLE_MAX = 120;
5
+ /**
6
+ * The AI module's Convex functions, bound to the app's system:
7
+ * // convex/ai.ts
8
+ * export const { listAgents, listThreads, …, _startRun, … } = defineAi(system, aiOptions).functions
9
+ * Names: AI_PUBLIC_FUNCTIONS + AI_INTERNAL_FUNCTIONS in contract.ts.
10
+ */
11
+ export declare function defineAi(system: ConvexSystem, options: AiOptions): {
12
+ functions: {
13
+ listAgents: import("convex/server").RegisteredQuery<"public", {
14
+ apiKey?: string | undefined;
15
+ operationId?: string | undefined;
16
+ }, Promise<AgentView[]>>;
17
+ listThreads: import("convex/server").RegisteredQuery<"public", {
18
+ apiKey?: string | undefined;
19
+ cursor?: string | null | undefined;
20
+ limit?: number | undefined;
21
+ operationId?: string | undefined;
22
+ }, Promise<{
23
+ items: ThreadView[];
24
+ cursor: string | null;
25
+ done: boolean;
26
+ }>>;
27
+ getThread: import("convex/server").RegisteredQuery<"public", {
28
+ apiKey?: string | undefined;
29
+ operationId?: string | undefined;
30
+ threadId: import("convex/values").GenericId<"aiThreads">;
31
+ }, Promise<ThreadView>>;
32
+ listMessages: import("convex/server").RegisteredQuery<"public", {
33
+ apiKey?: string | undefined;
34
+ operationId?: string | undefined;
35
+ threadId: import("convex/values").GenericId<"aiThreads">;
36
+ }, Promise<MessageView[]>>;
37
+ createThread: import("convex/server").RegisteredMutation<"public", {
38
+ agentId?: string | undefined;
39
+ apiKey?: string | undefined;
40
+ operationId?: string | undefined;
41
+ title?: string | undefined;
42
+ }, Promise<{
43
+ threadId: AiThreadId;
44
+ }>>;
45
+ sendMessage: import("convex/server").RegisteredMutation<"public", {
46
+ apiKey?: string | undefined;
47
+ operationId?: string | undefined;
48
+ text: string;
49
+ threadId: import("convex/values").GenericId<"aiThreads">;
50
+ }, Promise<{
51
+ messageId: AiMessageId;
52
+ runId: AiRunId;
53
+ }>>;
54
+ stopRun: import("convex/server").RegisteredMutation<"public", {
55
+ apiKey?: string | undefined;
56
+ operationId?: string | undefined;
57
+ threadId: import("convex/values").GenericId<"aiThreads">;
58
+ }, Promise<{
59
+ stopped: boolean;
60
+ }>>;
61
+ respondToToolCall: import("convex/server").RegisteredMutation<"public", {
62
+ apiKey?: string | undefined;
63
+ approve: boolean;
64
+ messageId: import("convex/values").GenericId<"aiMessages">;
65
+ operationId?: string | undefined;
66
+ toolCallId: string;
67
+ }, Promise<{
68
+ runId: AiRunId;
69
+ }>>;
70
+ renameThread: import("convex/server").RegisteredMutation<"public", {
71
+ apiKey?: string | undefined;
72
+ operationId?: string | undefined;
73
+ threadId: import("convex/values").GenericId<"aiThreads">;
74
+ title: string;
75
+ }, Promise<null>>;
76
+ archiveThread: import("convex/server").RegisteredMutation<"public", {
77
+ apiKey?: string | undefined;
78
+ operationId?: string | undefined;
79
+ threadId: import("convex/values").GenericId<"aiThreads">;
80
+ }, Promise<null>>;
81
+ _startRun: import("convex/server").RegisteredMutation<"internal", {
82
+ runId: import("convex/values").GenericId<"aiRuns">;
83
+ }, Promise<RunContext | null>>;
84
+ _appendText: import("convex/server").RegisteredMutation<"internal", {
85
+ messageId: import("convex/values").GenericId<"aiMessages">;
86
+ text: string;
87
+ }, Promise<null>>;
88
+ _addToolCall: import("convex/server").RegisteredMutation<"internal", {
89
+ args: any;
90
+ messageId: import("convex/values").GenericId<"aiMessages">;
91
+ state: "approved" | "awaiting-approval" | "done" | "error" | "rejected" | "running";
92
+ toolCallId: string;
93
+ toolName: string;
94
+ }, Promise<null>>;
95
+ _setToolCallState: import("convex/server").RegisteredMutation<"internal", {
96
+ error?: string | undefined;
97
+ messageId: import("convex/values").GenericId<"aiMessages">;
98
+ result?: any;
99
+ state: "approved" | "awaiting-approval" | "done" | "error" | "rejected" | "running";
100
+ toolCallId: string;
101
+ }, Promise<null>>;
102
+ _executeTool: import("convex/server").RegisteredMutation<"internal", {
103
+ runId: import("convex/values").GenericId<"aiRuns">;
104
+ toolCallId: string;
105
+ }, Promise<ToolExecutionResult>>;
106
+ _finishRun: import("convex/server").RegisteredMutation<"internal", {
107
+ error?: {
108
+ code: string;
109
+ message: string;
110
+ } | undefined;
111
+ runId: import("convex/values").GenericId<"aiRuns">;
112
+ status: "done" | "error" | "stopped" | "waiting-approval";
113
+ }, Promise<null>>;
114
+ _isStopRequested: import("convex/server").RegisteredQuery<"internal", {
115
+ runId: import("convex/values").GenericId<"aiRuns">;
116
+ }, Promise<boolean>>;
117
+ };
118
+ };
119
+ export type AiSystem = ReturnType<typeof defineAi>;
120
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/ai/api.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,YAAY,EAAc,MAAM,wBAAwB,CAAA;AACrF,OAAO,EAEL,KAAK,SAAS,EAEd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,UAAU,EAGf,KAAK,WAAW,EAChB,KAAK,UAAU,EAGf,KAAK,UAAU,EAEf,KAAK,mBAAmB,EAEzB,MAAM,eAAe,CAAA;AAItB,eAAO,MAAM,cAAc,OAAO,CAAA;AAClC,eAAO,MAAM,YAAY,MAAM,CAAA;AA8G/B;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS;;;;;;;;;;;;mBAqFjB,UAAU,EAAE;oBAAU,MAAM,GAAG,IAAI;kBAAQ,OAAO;;;;;;;;;;;;;;;;;;sBAoC/C,UAAU;;;;;;;;uBAeT,WAAW;mBAAS,OAAO;;;;;;;qBAmC7B,OAAO;;;;;;;;;mBAoBT,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8LtD;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAA"}