@digimuza/system-core 0.2.0 → 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.
- package/INTEGRATION.md +209 -3
- package/README.md +23 -0
- package/dist/ai/api.d.ts +120 -0
- package/dist/ai/api.d.ts.map +1 -0
- package/dist/ai/api.js +446 -0
- package/dist/ai/api.js.map +1 -0
- package/dist/ai/card.d.ts +27 -0
- package/dist/ai/card.d.ts.map +1 -0
- package/dist/ai/card.js +84 -0
- package/dist/ai/card.js.map +1 -0
- package/dist/ai/contract.d.ts +364 -0
- package/dist/ai/contract.d.ts.map +1 -0
- package/dist/ai/contract.js +101 -0
- package/dist/ai/contract.js.map +1 -0
- package/dist/ai/display.d.ts +20 -0
- package/dist/ai/display.d.ts.map +1 -0
- package/dist/ai/display.js +35 -0
- package/dist/ai/display.js.map +1 -0
- package/dist/ai/index.d.ts +7 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +10 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/manifest.d.ts +25 -0
- package/dist/ai/manifest.d.ts.map +1 -0
- package/dist/ai/manifest.js +15 -0
- package/dist/ai/manifest.js.map +1 -0
- package/dist/ai/runner.d.ts +71 -0
- package/dist/ai/runner.d.ts.map +1 -0
- package/dist/ai/runner.js +246 -0
- package/dist/ai/runner.js.map +1 -0
- package/dist/ai/tools.d.ts +42 -0
- package/dist/ai/tools.d.ts.map +1 -0
- package/dist/ai/tools.js +117 -0
- package/dist/ai/tools.js.map +1 -0
- package/dist/audit/csv.d.ts +31 -0
- package/dist/audit/csv.d.ts.map +1 -0
- package/dist/audit/csv.js +39 -0
- package/dist/audit/csv.js.map +1 -0
- package/dist/audit/index.d.ts +5 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +6 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/convex/functions.d.ts +39 -0
- package/dist/convex/functions.d.ts.map +1 -1
- package/dist/convex/functions.js +38 -6
- package/dist/convex/functions.js.map +1 -1
- package/dist/convex/index.d.ts +1 -1
- package/dist/convex/index.d.ts.map +1 -1
- package/dist/convex/index.js.map +1 -1
- package/dist/core/access.d.ts +14 -0
- package/dist/core/access.d.ts.map +1 -1
- package/dist/core/access.js +62 -0
- package/dist/core/access.js.map +1 -1
- package/dist/core/audit-module.d.ts +5 -0
- package/dist/core/audit-module.d.ts.map +1 -0
- package/dist/core/audit-module.js +25 -0
- package/dist/core/audit-module.js.map +1 -0
- package/dist/core/audit.d.ts +31 -0
- package/dist/core/audit.d.ts.map +1 -1
- package/dist/core/audit.js +46 -2
- package/dist/core/audit.js.map +1 -1
- package/dist/core/catalog.d.ts +41 -0
- package/dist/core/catalog.d.ts.map +1 -1
- package/dist/core/catalog.js +45 -0
- package/dist/core/catalog.js.map +1 -1
- package/dist/core/context.d.ts +8 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +4 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/notify.d.ts +37 -0
- package/dist/core/notify.d.ts.map +1 -0
- package/dist/core/notify.js +16 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/roles.d.ts.map +1 -1
- package/dist/core/roles.js +5 -1
- package/dist/core/roles.js.map +1 -1
- package/dist/core/system-modules.d.ts +4 -0
- package/dist/core/system-modules.d.ts.map +1 -1
- package/dist/core/system-modules.js +11 -0
- package/dist/core/system-modules.js.map +1 -1
- package/dist/finance/ai-tools.d.ts +55 -0
- package/dist/finance/ai-tools.d.ts.map +1 -0
- package/dist/finance/ai-tools.js +65 -0
- package/dist/finance/ai-tools.js.map +1 -0
- package/dist/finance/index.d.ts +87 -1
- package/dist/finance/index.d.ts.map +1 -1
- package/dist/finance/index.js +92 -38
- package/dist/finance/index.js.map +1 -1
- package/dist/finance/search.d.ts +15 -0
- package/dist/finance/search.d.ts.map +1 -0
- package/dist/finance/search.js +51 -0
- package/dist/finance/search.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -1
- package/dist/modules/index.js.map +1 -1
- package/dist/notifications/api.d.ts +130 -0
- package/dist/notifications/api.d.ts.map +1 -0
- package/dist/notifications/api.js +384 -0
- package/dist/notifications/api.js.map +1 -0
- package/dist/notifications/contract.d.ts +132 -0
- package/dist/notifications/contract.d.ts.map +1 -0
- package/dist/notifications/contract.js +59 -0
- package/dist/notifications/contract.js.map +1 -0
- package/dist/notifications/index.d.ts +6 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +9 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/search/index.d.ts +95 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +151 -0
- package/dist/search/index.js.map +1 -0
- package/package.json +41 -2
- package/src/ai/api.ts +535 -0
- package/src/ai/card.ts +91 -0
- package/src/ai/contract.ts +245 -0
- package/src/ai/display.ts +38 -0
- package/src/ai/index.ts +9 -0
- package/src/ai/manifest.ts +16 -0
- package/src/ai/runner.ts +275 -0
- package/src/ai/tools.ts +128 -0
- package/src/audit/csv.ts +60 -0
- package/src/audit/index.ts +7 -0
- package/src/convex/functions.ts +54 -7
- package/src/convex/index.ts +1 -1
- package/src/core/access.ts +55 -1
- package/src/core/audit-module.ts +26 -0
- package/src/core/audit.ts +68 -2
- package/src/core/catalog.ts +63 -0
- package/src/core/context.ts +13 -2
- package/src/core/index.ts +2 -0
- package/src/core/notify.ts +52 -0
- package/src/core/roles.ts +5 -1
- package/src/core/system-modules.ts +12 -0
- package/src/finance/ai-tools.ts +68 -0
- package/src/finance/index.ts +119 -42
- package/src/finance/search.ts +58 -0
- package/src/index.ts +2 -0
- package/src/modules/index.ts +3 -0
- package/src/notifications/api.ts +477 -0
- package/src/notifications/contract.ts +142 -0
- package/src/notifications/index.ts +8 -0
- 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` |
|
|
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
|
|
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.
|
package/dist/ai/api.d.ts
ADDED
|
@@ -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"}
|