adminizer 5.0.0-build.8 → 5.1.0-build.24

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 (232) hide show
  1. package/assets/abnfDiagram-VCTEODGH-DFY3Pztx.js +1 -0
  2. package/assets/add-31tS-g4V.js +1 -0
  3. package/assets/add-group-Cf3zT7WE.js +1 -0
  4. package/assets/add-user-C62wOna-.js +1 -0
  5. package/assets/ai-assistant/agent.es.js +28464 -0
  6. package/assets/app-DPmI-MvN.css +1 -0
  7. package/assets/app-vdHbjOQX.js +138 -0
  8. package/assets/arc-DP4o1zz3.js +1 -0
  9. package/assets/architectureDiagram-5GKGNRK7-BjXW5Zu0.js +36 -0
  10. package/assets/asmcrypto-DZeQH0LC.js +1 -0
  11. package/assets/blockDiagram-I7D4REHJ-BUPnDwN0.js +129 -0
  12. package/assets/c4Diagram-7LVT6UL2-Cmb_dAhd.js +38 -0
  13. package/assets/{catalog-BEvJZYJP.js → catalog-C-y5rMaG.js} +3 -3
  14. package/assets/channel-CkJRUsq_.js +1 -0
  15. package/assets/chunk-2Q5K7J3B-CmS-dWwQ.js +1 -0
  16. package/assets/chunk-5VM5RSS4-Cqs2Ow64.js +15 -0
  17. package/assets/chunk-F27PBJKO-BoMCpJA1.js +1 -0
  18. package/assets/chunk-IMKFNOWR-DlDcPk1x.js +231 -0
  19. package/assets/chunk-JWPE2WC7-DzL_SU8s.js +1 -0
  20. package/assets/chunk-POPQ4Y6H-Cwq2-Vb8.js +1 -0
  21. package/assets/chunk-SVP7TREG-BfRHY5FU.js +88 -0
  22. package/assets/chunk-TICWLB2K-CRP7rHkW.js +206 -0
  23. package/assets/chunk-XXDRQBXY-Bg2jg-98.js +1 -0
  24. package/assets/classDiagram-ZZMXUADV-D4EF3HGj.js +1 -0
  25. package/assets/classDiagram-v2-VYDZK3BY-D4EF3HGj.js +1 -0
  26. package/assets/controls/ckeditor.es.js +9382 -9320
  27. package/assets/controls/handsontable.es.js +11971 -11938
  28. package/assets/controls/jsoneditor.es.js +10442 -10233
  29. package/assets/controls/toast-ui.es.js +1308 -1279
  30. package/assets/cose-bilkent-JH36ORCC-CCJ0SGyO.js +1 -0
  31. package/assets/cynefin-OW5HDTMX-B2Jx5fw3.js +166 -0
  32. package/assets/cynefinDiagram-5FMLGOSQ-DuK0Lb62.js +62 -0
  33. package/assets/cytoscape.esm-DzSz-X2X.js +321 -0
  34. package/assets/dagre-GXQ25YYZ-DnOYexET.js +4 -0
  35. package/assets/dashboard-X8k4X6Sw.js +3 -0
  36. package/assets/debounce-B3mXI_sx.js +1 -0
  37. package/assets/defaultLocale-DX6XiGOO.js +1 -0
  38. package/assets/diagram-S7CK7UJ4-DDgPBHsK.js +30 -0
  39. package/assets/diagram-UQ7AKVKN-qlL0GZs8.js +41 -0
  40. package/assets/diagram-VSXAHHWV-CQbiU1_c.js +3 -0
  41. package/assets/diagram-VX7I27RA-DFDI9_au.js +24 -0
  42. package/assets/diagram-Z3DM3KII-B5C9dtmJ.js +24 -0
  43. package/assets/docs-BiZOD7PP.js +1 -0
  44. package/assets/ebnfDiagram-PWID7BFC-CLMUsXEI.js +1 -0
  45. package/assets/erDiagram-RLTQ6QDP-ax9Xol5-.js +99 -0
  46. package/assets/flowDiagram-HODETNUW-CvaH7l3S.js +168 -0
  47. package/assets/ganttDiagram-EL5Y4UJY-Cga5U19j.js +292 -0
  48. package/assets/gitGraphDiagram-WWUBYQGX-B6o1T3-d.js +106 -0
  49. package/assets/history-CVBJUNSQ.js +1 -0
  50. package/assets/infoDiagram-27XIBGKW-BpWxgBCE.js +2 -0
  51. package/assets/init-Gi6I4Gst.js +1 -0
  52. package/assets/init-user-xSvnFTF0.js +1 -0
  53. package/assets/isObject-DL-BuaBl.js +1 -0
  54. package/assets/ishikawaDiagram-5VMMS53U-zE83db4H.js +70 -0
  55. package/assets/journeyDiagram-3NMN7TZE-DZNVFqrX.js +139 -0
  56. package/assets/kanban-definition-UXKFOSKX-CqxNgk27.js +89 -0
  57. package/assets/katex-HP8lGamR.js +257 -0
  58. package/assets/layout-CN3kt_jt.js +1 -0
  59. package/assets/linear-nvkaQ6BA.js +1 -0
  60. package/assets/{list-DhK0McVn.js → list-CrIU2eZz.js} +2 -2
  61. package/assets/login-CX9xVjCn.js +1 -0
  62. package/assets/manifest.json +834 -58
  63. package/assets/media-manager-BfYftLIH.js +6 -0
  64. package/assets/mermaid.core-C0V1NaVJ.js +310 -0
  65. package/assets/mindmap-definition-YA3MSWOX-Bh7_d9f1.js +96 -0
  66. package/assets/module-DPEvnad3.js +1 -0
  67. package/assets/notification-Dqpt5AIZ.js +20 -0
  68. package/assets/ordinal-Cboi1Yqb.js +1 -0
  69. package/assets/pegDiagram-XKGWAZYB-DMnjm6Z1.js +1 -0
  70. package/assets/pieDiagram-E7YTZNPT-D-RWCgYb.js +39 -0
  71. package/assets/quadrantDiagram-AXDQQJYC-BNBGP7RM.js +7 -0
  72. package/assets/railroadDiagram-O6MQD6OU-BIjVPT01.js +1 -0
  73. package/assets/register-CF4KQ97e.js +1 -0
  74. package/assets/requirementDiagram-BXWQKSXE-VPrtGdjM.js +84 -0
  75. package/assets/ru-CFJ4fysR.js +1 -0
  76. package/assets/sankeyDiagram-P5KCCOFB-ct9mLF3U.js +40 -0
  77. package/assets/sequenceDiagram-WJ2MYXX4-TC06Mbmw.js +162 -0
  78. package/assets/sizeCapture-INFHLROL-BMMwriCf.js +1 -0
  79. package/assets/stateDiagram-D77RDMKH-DtwYRPCX.js +1 -0
  80. package/assets/stateDiagram-v2-MP3YSRHH-C1yB0O5U.js +1 -0
  81. package/assets/swimlanes-42K2YHIH-DE3hRbbr.js +1 -0
  82. package/assets/swimlanesDiagram-VR7AAH4N-Ce4l1sQD.js +8 -0
  83. package/assets/timeline-definition-24CTP7MA-N7s_hfjj.js +120 -0
  84. package/assets/toNumber-DZb-r9sy.js +1 -0
  85. package/assets/use-appearance-DYTIKpT2.js +1 -0
  86. package/assets/user-filters-list-fR0YNJ3l.js +1 -0
  87. package/assets/vennDiagram-4TSXK5OY-Dj5bFQkR.js +34 -0
  88. package/assets/wardleyDiagram-VM6X3IG4-83ax25Z6.js +78 -0
  89. package/assets/welcome-v11MTsRK.js +1 -0
  90. package/assets/with-app-layout-CH6RIib1.js +31 -0
  91. package/assets/xychartDiagram-S5SC5T6Z-CW9Br5Xp.js +7 -0
  92. package/controllers/access-rights/permissionOptions.d.ts +2 -0
  93. package/controllers/access-rights/permissionOptions.js +23 -0
  94. package/controllers/add.js +2 -2
  95. package/controllers/addGroup.js +15 -6
  96. package/controllers/ai/AiAgentController.d.ts +66 -0
  97. package/controllers/ai/AiAgentController.js +435 -0
  98. package/controllers/ai/AiAssistantController.d.ts +0 -4
  99. package/controllers/ai/AiAssistantController.js +4 -78
  100. package/controllers/catalog/FrontendCatalogAdapter.js +7 -8
  101. package/controllers/docs/index.d.ts +22 -0
  102. package/controllers/docs/index.js +126 -0
  103. package/controllers/edit.js +2 -2
  104. package/controllers/editGroup.js +15 -6
  105. package/controllers/filter-fields/savedFilters.js +1 -1
  106. package/controllers/history-actions/HistoryController.js +7 -7
  107. package/controllers/inlineUpdate.js +10 -3
  108. package/controllers/list.js +1 -1
  109. package/controllers/login.js +1 -1
  110. package/controllers/notifications/NotificationController.js +14 -8
  111. package/controllers/view.js +3 -3
  112. package/controllers/widgets/filterInfo.js +1 -1
  113. package/helpers/accessRightsHelper.d.ts +30 -3
  114. package/helpers/accessRightsHelper.js +140 -12
  115. package/helpers/assetVersionHelper.d.ts +14 -0
  116. package/helpers/assetVersionHelper.js +42 -0
  117. package/helpers/configHelper.js +19 -18
  118. package/helpers/controllerHelper.d.ts +1 -0
  119. package/helpers/controllerHelper.js +25 -15
  120. package/helpers/fieldsHelper.d.ts +2 -1
  121. package/helpers/fieldsHelper.js +11 -5
  122. package/helpers/inertiaActionsHelper.d.ts +1 -1
  123. package/helpers/inertiaActionsHelper.js +4 -5
  124. package/helpers/inertiaAddHelper.d.ts +1 -1
  125. package/helpers/inertiaAddHelper.js +7 -6
  126. package/helpers/inertiaGroupHelper.js +10 -2
  127. package/helpers/inertiaListHelper.d.ts +1 -1
  128. package/helpers/inertiaListHelper.js +7 -8
  129. package/helpers/inertiaMenuHelper.d.ts +7 -2
  130. package/helpers/inertiaMenuHelper.js +12 -27
  131. package/helpers/menuHelper.d.ts +11 -1
  132. package/helpers/menuHelper.js +12 -0
  133. package/helpers/modelResourceHelper.d.ts +9 -0
  134. package/helpers/modelResourceHelper.js +43 -0
  135. package/helpers/navigationAccessHelper.d.ts +13 -0
  136. package/helpers/navigationAccessHelper.js +45 -0
  137. package/index.d.ts +11 -0
  138. package/index.js +11 -0
  139. package/interfaces/adminpanelConfig.d.ts +160 -2
  140. package/interfaces/queryCriteria.d.ts +13 -0
  141. package/interfaces/types.d.ts +169 -0
  142. package/lib/Adminizer.d.ts +8 -0
  143. package/lib/Adminizer.js +29 -2
  144. package/lib/DataAccessor.d.ts +100 -3
  145. package/lib/DataAccessor.js +454 -245
  146. package/lib/access-graph/AccessGraphResolver.d.ts +136 -0
  147. package/lib/access-graph/AccessGraphResolver.js +605 -0
  148. package/lib/access-graph/RecordAccessCache.d.ts +23 -0
  149. package/lib/access-graph/RecordAccessCache.js +30 -0
  150. package/lib/access-graph/RecordAccessResolver.d.ts +83 -0
  151. package/lib/access-graph/RecordAccessResolver.js +295 -0
  152. package/lib/access-graph/shared.d.ts +60 -0
  153. package/lib/access-graph/shared.js +121 -0
  154. package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
  155. package/lib/admin-links/AdminLinkHandler.js +183 -0
  156. package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
  157. package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
  158. package/lib/ai-assistant/AbstractAiModelService.d.ts +139 -2
  159. package/lib/ai-assistant/AbstractAiModelService.js +154 -1
  160. package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
  161. package/lib/ai-assistant/AiAgentRunManager.js +109 -0
  162. package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +61 -0
  163. package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +90 -0
  164. package/lib/ai-assistant/AiAssistantHandler.d.ts +1 -0
  165. package/lib/ai-assistant/AiAssistantHandler.js +4 -0
  166. package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
  167. package/lib/ai-assistant/AiAssistantUiMethodHandler.js +144 -0
  168. package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
  169. package/lib/ai-assistant/builtinAgentSkills.js +259 -0
  170. package/lib/ai-assistant/jsonSafe.d.ts +13 -0
  171. package/lib/ai-assistant/jsonSafe.js +81 -0
  172. package/lib/app-manager/AdminizerApp.d.ts +31 -2
  173. package/lib/app-manager/AppManager.js +43 -42
  174. package/lib/app-manager/ConfigLayerHandler.js +4 -0
  175. package/lib/docs/AbstractDocumentation.d.ts +100 -0
  176. package/lib/docs/AbstractDocumentation.js +108 -0
  177. package/lib/docs/DocumentationHandler.d.ts +100 -0
  178. package/lib/docs/DocumentationHandler.js +198 -0
  179. package/lib/docs/FileDocumentation.d.ts +47 -0
  180. package/lib/docs/FileDocumentation.js +189 -0
  181. package/lib/docs/docsAgentSkills.d.ts +10 -0
  182. package/lib/docs/docsAgentSkills.js +93 -0
  183. package/lib/filters/FilterService.js +4 -3
  184. package/lib/history-actions/AbstractHistoryAdapter.d.ts +26 -1
  185. package/lib/history-actions/AbstractHistoryAdapter.js +155 -25
  186. package/lib/media-manager/helpers/MediaManagerHelper.js +9 -7
  187. package/lib/model/AbstractModel.d.ts +48 -0
  188. package/lib/model/AbstractModel.js +14 -4
  189. package/lib/model/ModelHandler.d.ts +50 -2
  190. package/lib/model/ModelHandler.js +117 -8
  191. package/lib/model/adapter/sequelize.d.ts +43 -3
  192. package/lib/model/adapter/sequelize.js +317 -138
  193. package/lib/model/adapter/typeorm.d.ts +16 -0
  194. package/lib/model/adapter/typeorm.js +136 -9
  195. package/lib/notifications/GeneralNotificationService.js +6 -5
  196. package/lib/notifications/SystemNotificationService.js +1 -1
  197. package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
  198. package/lib/ui-i18n/uiTranslationKeys.js +42 -1
  199. package/lib/widgets/widgetHandler.js +10 -6
  200. package/models/Group.d.ts +2 -1
  201. package/package.json +35 -23
  202. package/policies/authPolicies.js +6 -6
  203. package/policies/permissionResolvers.d.ts +1 -0
  204. package/policies/permissionResolvers.js +3 -0
  205. package/system/Router.js +21 -3
  206. package/system/bindAccessRights.js +13 -3
  207. package/system/bindDocs.d.ts +13 -0
  208. package/system/bindDocs.js +77 -0
  209. package/system/bindInertia.js +30 -7
  210. package/system/buildInternalModelAccess.d.ts +8 -0
  211. package/system/buildInternalModelAccess.js +36 -4
  212. package/system/defaults.js +7 -1
  213. package/system/validateSystemModels.js +29 -9
  214. package/assets/add-DED_wpga.js +0 -1
  215. package/assets/add-group-CL80RdYb.js +0 -1
  216. package/assets/add-user-BRmwvQB8.js +0 -1
  217. package/assets/app-DNXiApCk.css +0 -1
  218. package/assets/app-layout-86yxiZOy.js +0 -1
  219. package/assets/app.js +0 -138
  220. package/assets/dashboard-ZP6K2s_8.js +0 -3
  221. package/assets/debounce-BMmXVQ06.js +0 -1
  222. package/assets/history-M61Bcv02.js +0 -1
  223. package/assets/init-user-8aUohVDq.js +0 -1
  224. package/assets/login-CO4laf6m.js +0 -1
  225. package/assets/media-manager-BDbpSSjH.js +0 -6
  226. package/assets/module-DfBneaUl.js +0 -1
  227. package/assets/notification-CVnQf3ef.js +0 -20
  228. package/assets/register-DBYIMXGQ.js +0 -1
  229. package/assets/ru-rYKdQbWF.js +0 -1
  230. package/assets/use-appearance-_EbbjHTh.js +0 -1
  231. package/assets/user-filters-list-DXhGLFTn.js +0 -1
  232. package/assets/welcome-CLxvXkcg.js +0 -1
@@ -0,0 +1,35 @@
1
+ import { AiAgentStreamEvent } from '../../interfaces/types.js';
2
+ interface AgentRun {
3
+ id: string;
4
+ userId: number;
5
+ modelId: string;
6
+ createdAt: number;
7
+ events: AiAgentStreamEvent[];
8
+ clients: Set<ResType>;
9
+ done: boolean;
10
+ }
11
+ /**
12
+ * Buffered SSE transport for agent runs.
13
+ *
14
+ * A run is started by a POST that returns immediately (so the browser can open
15
+ * the stream) and then writes its events here. Every event is stored with its
16
+ * index and used as the SSE event id, which lets EventSource resume through
17
+ * `Last-Event-ID` after a reconnect without replaying what the client already
18
+ * processed.
19
+ */
20
+ export declare class AiAgentRunManager {
21
+ private readonly runs;
22
+ create(userId: number, modelId: string): AgentRun;
23
+ /** Only the owner may read a run. */
24
+ get(runId: string, userId: number): AgentRun | undefined;
25
+ publish(run: AgentRun, event: AiAgentStreamEvent): void;
26
+ /** Marks the run finished and flushes the closing frame to every client. */
27
+ finish(run: AgentRun, event: AiAgentStreamEvent): void;
28
+ /** Streams a run to one client: replays what it missed, then follows live. */
29
+ subscribe(run: AgentRun, req: ReqType, res: ResType): void;
30
+ private write;
31
+ /** SSE payloads must be JSON-safe and bounded. */
32
+ private sanitize;
33
+ private cleanup;
34
+ }
35
+ export type { AgentRun };
@@ -0,0 +1,109 @@
1
+ /** Runs are kept only long enough for a reconnecting EventSource to catch up. */
2
+ const MAX_RUN_AGE_MS = 10 * 60 * 1000;
3
+ /** Hard cap per SSE frame, so a chatty tool result cannot blow up memory. */
4
+ const MAX_EVENT_JSON_CHARS = 64 * 1024;
5
+ /**
6
+ * Buffered SSE transport for agent runs.
7
+ *
8
+ * A run is started by a POST that returns immediately (so the browser can open
9
+ * the stream) and then writes its events here. Every event is stored with its
10
+ * index and used as the SSE event id, which lets EventSource resume through
11
+ * `Last-Event-ID` after a reconnect without replaying what the client already
12
+ * processed.
13
+ */
14
+ export class AiAgentRunManager {
15
+ runs = new Map();
16
+ create(userId, modelId) {
17
+ this.cleanup();
18
+ const id = `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
19
+ const run = {
20
+ id,
21
+ userId,
22
+ modelId,
23
+ createdAt: Date.now(),
24
+ events: [],
25
+ clients: new Set(),
26
+ done: false,
27
+ };
28
+ this.runs.set(id, run);
29
+ return run;
30
+ }
31
+ /** Only the owner may read a run. */
32
+ get(runId, userId) {
33
+ const run = this.runs.get(runId);
34
+ return run && run.userId === userId ? run : undefined;
35
+ }
36
+ publish(run, event) {
37
+ const safe = this.sanitize(event);
38
+ const index = run.events.push(safe) - 1;
39
+ for (const client of run.clients)
40
+ this.write(client, 'agent', safe, index);
41
+ }
42
+ /** Marks the run finished and flushes the closing frame to every client. */
43
+ finish(run, event) {
44
+ run.done = true;
45
+ this.publish(run, event);
46
+ for (const client of run.clients)
47
+ client.end();
48
+ run.clients.clear();
49
+ }
50
+ /** Streams a run to one client: replays what it missed, then follows live. */
51
+ subscribe(run, req, res) {
52
+ res.setHeader('Content-Type', 'text/event-stream');
53
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
54
+ res.setHeader('Connection', 'keep-alive');
55
+ res.setHeader('X-Accel-Buffering', 'no');
56
+ res.flushHeaders?.();
57
+ res.write('retry: 3000\n\n');
58
+ this.write(res, 'connected', { runId: run.id });
59
+ const lastEventId = Number(req.headers['last-event-id']);
60
+ const start = Number.isFinite(lastEventId) ? lastEventId + 1 : 0;
61
+ for (let i = start; i < run.events.length; i++) {
62
+ this.write(res, 'agent', run.events[i], i);
63
+ }
64
+ if (run.done) {
65
+ res.end();
66
+ return;
67
+ }
68
+ run.clients.add(res);
69
+ req.on('close', () => run.clients.delete(res));
70
+ }
71
+ write(res, event, data, id) {
72
+ if (id !== undefined)
73
+ res.write(`id: ${id}\n`);
74
+ res.write(`event: ${event}\n`);
75
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
76
+ }
77
+ /** SSE payloads must be JSON-safe and bounded. */
78
+ sanitize(event) {
79
+ if (!event || typeof event !== 'object')
80
+ return event;
81
+ let safe = event;
82
+ // A finished agent turn may carry the whole history — the panel never
83
+ // uses it and it would dwarf every other frame.
84
+ if (safe.type === 'done' && 'messages' in safe) {
85
+ const { messages: _messages, ...rest } = safe;
86
+ safe = rest;
87
+ }
88
+ if (safe.error instanceof Error) {
89
+ safe = { ...safe, error: safe.error.message };
90
+ }
91
+ const json = JSON.stringify(safe);
92
+ if (json.length <= MAX_EVENT_JSON_CHARS)
93
+ return safe;
94
+ return {
95
+ type: safe.type || 'event',
96
+ truncated: true,
97
+ originalChars: json.length,
98
+ message: `${safe.type || 'event'} payload was truncated to keep SSE memory bounded.`,
99
+ preview: `${json.slice(0, MAX_EVENT_JSON_CHARS - 1)}…`,
100
+ };
101
+ }
102
+ cleanup() {
103
+ const cutoff = Date.now() - MAX_RUN_AGE_MS;
104
+ for (const [id, run] of this.runs) {
105
+ if (run.createdAt < cutoff)
106
+ this.runs.delete(id);
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,61 @@
1
+ import type { User } from '../../models/User.js';
2
+ import type { Adminizer } from '../Adminizer.js';
3
+ import type { AppRuntime } from '../app-manager/AdminizerApp.js';
4
+ /** Identity of the admin user on whose behalf a skill runs. */
5
+ export interface AiAssistantSkillUser {
6
+ id?: number;
7
+ login: string;
8
+ fullName?: string;
9
+ email?: string;
10
+ locale?: string;
11
+ isAdministrator: boolean;
12
+ groups: string[];
13
+ }
14
+ export interface AiAssistantAgentSkillContext {
15
+ user: User;
16
+ /** The same user as a plain descriptor, safe to return in a tool result. */
17
+ userIdentity: AiAssistantSkillUser;
18
+ runtime: AppRuntime;
19
+ }
20
+ /** A server-side tool made available to every compatible registered agent. */
21
+ export interface AiAssistantAgentSkill {
22
+ id: string;
23
+ description: string;
24
+ inputSchema: Record<string, unknown>;
25
+ accessRightsToken?: string;
26
+ /**
27
+ * Advertise the calling user inside the input schema. The agent then sees
28
+ * who it acts for and echoes it back; the value is always overwritten with
29
+ * the authenticated identity, so it cannot be used to impersonate.
30
+ */
31
+ requiresUser?: boolean;
32
+ /**
33
+ * Per-user refinement of what the agent is told about this skill, e.g. an
34
+ * enum of the models this particular user may read.
35
+ */
36
+ describe?(user: User, adminizer: Adminizer): {
37
+ description?: string;
38
+ inputSchema?: Record<string, unknown>;
39
+ } | Promise<{
40
+ description?: string;
41
+ inputSchema?: Record<string, unknown>;
42
+ } | undefined> | undefined;
43
+ execute(input: Record<string, unknown>, context: AiAssistantAgentSkillContext): unknown | Promise<unknown>;
44
+ }
45
+ export type AiAssistantAgentSkillDescriptor = Omit<AiAssistantAgentSkill, 'execute' | 'describe'>;
46
+ /** Input property that carries the calling user for `requiresUser` skills. */
47
+ export declare const CURRENT_USER_PARAM = "currentUser";
48
+ export declare class AiAssistantAgentSkillHandler {
49
+ private readonly adminizer;
50
+ private readonly skills;
51
+ constructor(adminizer: Adminizer);
52
+ add(skill: AiAssistantAgentSkill, owner?: string): void;
53
+ remove(id: string, owner?: string): boolean;
54
+ /** Skills this user may call, described with their permissions applied. */
55
+ getAvailable(user: User): Promise<AiAssistantAgentSkillDescriptor[]>;
56
+ execute(id: string, input: Record<string, unknown>, user: User): Promise<unknown>;
57
+ /** Plain, JSON-safe identity of an admin user. */
58
+ describeUser(user: User): AiAssistantSkillUser;
59
+ private isPermitted;
60
+ private describe;
61
+ }
@@ -0,0 +1,90 @@
1
+ import { buildBuiltinAgentSkills } from './builtinAgentSkills.js';
2
+ /** Input property that carries the calling user for `requiresUser` skills. */
3
+ export const CURRENT_USER_PARAM = 'currentUser';
4
+ export class AiAssistantAgentSkillHandler {
5
+ adminizer;
6
+ skills = new Map();
7
+ constructor(adminizer) {
8
+ this.adminizer = adminizer;
9
+ // Built-in skills only read `adminizer` when they run, so registering
10
+ // them from the constructor is safe while Adminizer is still wiring up.
11
+ for (const skill of buildBuiltinAgentSkills(adminizer))
12
+ this.add(skill);
13
+ }
14
+ add(skill, owner = 'host') {
15
+ const id = skill.id.trim().toLowerCase();
16
+ if (!/^[a-z][a-z0-9_-]*$/.test(id))
17
+ throw new Error(`Invalid AI assistant skill id: ${skill.id}`);
18
+ if (!skill.description.trim())
19
+ throw new Error(`AI assistant skill "${id}" requires a description`);
20
+ if (this.skills.has(id))
21
+ throw new Error(`AI assistant skill "${id}" is already registered`);
22
+ this.skills.set(id, { ...skill, id, owner });
23
+ }
24
+ remove(id, owner) {
25
+ const skill = this.skills.get(id);
26
+ if (!skill || (owner && skill.owner !== owner))
27
+ return false;
28
+ return this.skills.delete(id);
29
+ }
30
+ /** Skills this user may call, described with their permissions applied. */
31
+ async getAvailable(user) {
32
+ const available = [];
33
+ for (const skill of this.skills.values()) {
34
+ if (await this.isPermitted(skill, user))
35
+ available.push(await this.describe(skill, user));
36
+ }
37
+ return available;
38
+ }
39
+ async execute(id, input, user) {
40
+ const skill = this.skills.get(id.trim().toLowerCase());
41
+ if (!skill || !await this.isPermitted(skill, user)) {
42
+ throw new Error(`AI assistant skill "${id}" is not available`);
43
+ }
44
+ const userIdentity = this.describeUser(user);
45
+ // Whatever the model passed as the user is discarded: a skill acts for
46
+ // the authenticated admin user only.
47
+ const payload = skill.requiresUser
48
+ ? { ...input, [CURRENT_USER_PARAM]: userIdentity.login }
49
+ : input;
50
+ return skill.execute(payload, {
51
+ user,
52
+ userIdentity,
53
+ runtime: this.adminizer.appManager.createRuntime(skill.owner),
54
+ });
55
+ }
56
+ /** Plain, JSON-safe identity of an admin user. */
57
+ describeUser(user) {
58
+ return {
59
+ id: user.id,
60
+ login: user.login,
61
+ fullName: user.fullName || undefined,
62
+ email: user.email || undefined,
63
+ locale: user.locale || undefined,
64
+ isAdministrator: Boolean(user.isAdministrator),
65
+ groups: (user.groups ?? []).map((group) => group?.name).filter(Boolean),
66
+ };
67
+ }
68
+ async isPermitted(skill, user) {
69
+ return !skill.accessRightsToken || this.adminizer.accessRightsHelper.hasPermission(skill.accessRightsToken, user);
70
+ }
71
+ async describe(skill, user) {
72
+ const refinement = await skill.describe?.(user, this.adminizer) ?? {};
73
+ const { execute: _execute, describe: _describe, owner: _owner, ...rest } = skill;
74
+ const inputSchema = { ...(refinement.inputSchema ?? skill.inputSchema) };
75
+ if (skill.requiresUser) {
76
+ const identity = this.describeUser(user);
77
+ const properties = { ...(inputSchema.properties ?? {}) };
78
+ properties[CURRENT_USER_PARAM] = {
79
+ type: 'string',
80
+ enum: [identity.login],
81
+ description: `Login of the admin user this conversation belongs to (id ${identity.id}`
82
+ + `${identity.fullName ? `, ${identity.fullName}` : ''}). Always pass "${identity.login}".`,
83
+ };
84
+ const required = new Set([...(inputSchema.required ?? []), CURRENT_USER_PARAM]);
85
+ inputSchema.properties = properties;
86
+ inputSchema.required = [...required];
87
+ }
88
+ return { ...rest, description: refinement.description ?? skill.description, inputSchema };
89
+ }
90
+ }
@@ -22,6 +22,7 @@ export declare class AiAssistantHandler {
22
22
  getModels(): AiAssistantModelInfo[];
23
23
  getHistory(userId: number, modelId: string): AiAssistantMessage[];
24
24
  resetHistory(userId: number, modelId: string): void;
25
+ replaceHistory(userId: number, modelId: string, history: AiAssistantMessage[]): void;
25
26
  sendMessage(user: User, modelId: string, prompt: string): Promise<{
26
27
  history: AiAssistantMessage[];
27
28
  }>;
@@ -16,6 +16,7 @@ export class AiAssistantHandler {
16
16
  if (this.models.has(service.id)) {
17
17
  Adminizer.log.warn(`AI assistant model with id "${service.id}" is already registered. Overwriting.`);
18
18
  }
19
+ service.attachAdminizer(this.adminizer);
19
20
  this.models.set(service.id, service);
20
21
  if (owner) {
21
22
  this.owners.set(service.id, owner);
@@ -55,6 +56,9 @@ export class AiAssistantHandler {
55
56
  resetHistory(userId, modelId) {
56
57
  this.history.delete(this.getSessionKey(userId, modelId));
57
58
  }
59
+ replaceHistory(userId, modelId, history) {
60
+ this.history.set(this.getSessionKey(userId, modelId), history);
61
+ }
58
62
  async sendMessage(user, modelId, prompt) {
59
63
  const service = this.getModel(modelId);
60
64
  if (!service) {
@@ -0,0 +1,60 @@
1
+ import type { User } from '../../models/User.js';
2
+ import type { Adminizer } from '../Adminizer.js';
3
+ import type { AdminLinkTemplateParam } from '../admin-links/AdminLinkHandler.js';
4
+ export interface AiAssistantAdminLink {
5
+ id: string;
6
+ title: string;
7
+ /** Concrete page, ready for `navigate`. Absent for templates. */
8
+ link?: string;
9
+ /** Parametrized page: pass `template` plus `params` to `navigate`. */
10
+ template?: string;
11
+ /** Placeholders that must be filled to open a template. */
12
+ params?: AdminLinkTemplateParam[];
13
+ description?: string;
14
+ section?: string;
15
+ }
16
+ /**
17
+ * A browser capability an AI service may expose as a tool. The agent emits
18
+ * `ui.method` with this id; the assistant panel performs the browser action.
19
+ */
20
+ export interface AiAssistantUiMethod {
21
+ id: string;
22
+ title: string;
23
+ description: string;
24
+ /** JSON Schema consumed by tool-capable model services. */
25
+ inputSchema: Record<string, unknown>;
26
+ /** Built-in browser executor understood by the shared assistant panel. */
27
+ action: 'navigate' | 'search-admin-links' | string;
28
+ accessRightsToken?: string;
29
+ }
30
+ /** What the agent may ask to open: a concrete link or a template plus values. */
31
+ export interface AiAssistantNavigationTarget {
32
+ href?: string;
33
+ template?: string;
34
+ params?: Record<string, unknown>;
35
+ }
36
+ export declare class AiAssistantUiMethodHandler {
37
+ private readonly adminizer;
38
+ private readonly methods;
39
+ private readonly owners;
40
+ constructor(adminizer: Adminizer);
41
+ register(method: AiAssistantUiMethod, owner?: string): void;
42
+ unregister(id: string, owner?: string): void;
43
+ getAvailable(user: User): Promise<AiAssistantUiMethod[]>;
44
+ /**
45
+ * Search exactly the navigation a user can open. This intentionally uses
46
+ * MenuHelper rather than client-side markup, therefore a tool result is
47
+ * permission-safe and works when the assistant panel is not visible.
48
+ *
49
+ * Parametrized pages are returned as templates, so a record page is
50
+ * reachable even though its URL only exists once the id is known.
51
+ */
52
+ searchAdminLinks(user: User, query?: string): Promise<AiAssistantAdminLink[]>;
53
+ /**
54
+ * Validates what an agent asked to open and returns the concrete admin URL.
55
+ * Templates are resolved against this user's own permissions, so the LLM
56
+ * can only reach pages that user could reach by clicking.
57
+ */
58
+ resolveNavigation(user: User, target: AiAssistantNavigationTarget): Promise<string>;
59
+ private slug;
60
+ }
@@ -0,0 +1,144 @@
1
+ import { listAccessibleMenuItems } from '../../helpers/navigationAccessHelper.js';
2
+ export class AiAssistantUiMethodHandler {
3
+ adminizer;
4
+ methods = new Map();
5
+ owners = new Map();
6
+ constructor(adminizer) {
7
+ this.adminizer = adminizer;
8
+ this.register({
9
+ id: 'navigate',
10
+ title: 'Open admin section',
11
+ description: 'Open an Adminizer page in the current browser tab using the Inertia router.'
12
+ + ' Pass `href` for a concrete link, or `template` plus `params` for a parametrized page'
13
+ + ' such as a single record. Both come from search-admin-links.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ href: { type: 'string', description: 'Adminizer-relative URL of a concrete page to open.' },
18
+ template: { type: 'string', description: 'Id of a link template returned by search-admin-links.' },
19
+ params: {
20
+ type: 'object',
21
+ description: 'Values for the template placeholders, e.g. {"id": "3860d321-…"}.',
22
+ additionalProperties: { type: 'string' },
23
+ },
24
+ },
25
+ additionalProperties: false,
26
+ },
27
+ action: 'navigate',
28
+ });
29
+ this.register({
30
+ id: 'search-admin-links',
31
+ title: 'Search admin sections and links',
32
+ description: 'Search the current user\'s accessible Adminizer navigation sections, links and'
33
+ + ' link templates (record pages, catalog items and other parametrized pages).',
34
+ inputSchema: {
35
+ type: 'object',
36
+ properties: { query: { type: 'string', description: 'Words to search in section, link and template titles.' } },
37
+ required: ['query'], additionalProperties: false,
38
+ },
39
+ action: 'search-admin-links',
40
+ });
41
+ }
42
+ register(method, owner) {
43
+ const id = method.id.trim().toLowerCase();
44
+ if (!/^[a-z][a-z0-9_-]*$/.test(id))
45
+ throw new Error(`Invalid AI assistant UI method id: ${method.id}`);
46
+ if (!method.title.trim() || !method.description.trim())
47
+ throw new Error(`AI assistant UI method "${id}" requires title and description`);
48
+ this.methods.set(id, { ...method, id });
49
+ if (owner)
50
+ this.owners.set(id, owner);
51
+ else
52
+ this.owners.delete(id);
53
+ }
54
+ unregister(id, owner) {
55
+ const normalized = id.trim().toLowerCase();
56
+ if (owner && this.owners.get(normalized) !== owner)
57
+ return;
58
+ this.methods.delete(normalized);
59
+ this.owners.delete(normalized);
60
+ }
61
+ async getAvailable(user) {
62
+ const available = [];
63
+ for (const method of this.methods.values()) {
64
+ if (method.accessRightsToken
65
+ && !await this.adminizer.accessRightsHelper.hasPermission(method.accessRightsToken, user)) {
66
+ continue;
67
+ }
68
+ available.push({ ...method, inputSchema: { ...method.inputSchema } });
69
+ }
70
+ return available;
71
+ }
72
+ /**
73
+ * Search exactly the navigation a user can open. This intentionally uses
74
+ * MenuHelper rather than client-side markup, therefore a tool result is
75
+ * permission-safe and works when the assistant panel is not visible.
76
+ *
77
+ * Parametrized pages are returned as templates, so a record page is
78
+ * reachable even though its URL only exists once the id is known.
79
+ */
80
+ async searchAdminLinks(user, query = '') {
81
+ const needle = this.slug(query);
82
+ const result = [];
83
+ const matches = (...values) => !needle || this.slug(values.filter(Boolean).join(' ')).includes(needle);
84
+ const add = (item, section) => {
85
+ if (!item?.link || !item?.title)
86
+ return;
87
+ const link = {
88
+ id: String(item.id || item.title), title: String(item.title), link: String(item.link),
89
+ section: item.section || section,
90
+ };
91
+ // A link with placeholders is surfaced as a template instead.
92
+ if (!/:[A-Za-z0-9_]+/.test(link.link) && matches(link.title, link.id, link.section))
93
+ result.push(link);
94
+ for (const child of item.actions ?? item.subItems ?? [])
95
+ add(child, link.section);
96
+ };
97
+ // Exactly the menu the sidebar renders for this user, sub-items included.
98
+ for (const item of await listAccessibleMenuItems(this.adminizer, user))
99
+ add(item, item.section);
100
+ for (const template of await this.adminizer.adminLinkHandler.listTemplates(user)) {
101
+ if (!matches(template.title, template.id, template.section, template.description, template.template))
102
+ continue;
103
+ result.push({
104
+ id: template.id,
105
+ title: template.title,
106
+ template: template.template,
107
+ params: template.params,
108
+ description: template.description,
109
+ section: template.section,
110
+ // A template without placeholders is already a usable link.
111
+ link: template.params.length ? undefined : template.template,
112
+ });
113
+ }
114
+ return result;
115
+ }
116
+ /**
117
+ * Validates what an agent asked to open and returns the concrete admin URL.
118
+ * Templates are resolved against this user's own permissions, so the LLM
119
+ * can only reach pages that user could reach by clicking.
120
+ */
121
+ async resolveNavigation(user, target) {
122
+ const template = target.template?.trim();
123
+ if (template) {
124
+ return this.adminizer.adminLinkHandler.resolveTemplate(user, template, target.params ?? {});
125
+ }
126
+ const href = target.href?.trim();
127
+ if (!href)
128
+ throw new Error('Navigation requires either href or template');
129
+ const prefix = (this.adminizer.config.routePrefix || '').replace(/\/+$/, '');
130
+ if (href !== prefix && !href.startsWith(`${prefix}/`)) {
131
+ throw new Error('Only Adminizer-relative links may be opened.');
132
+ }
133
+ if (/:[A-Za-z0-9_]+/.test(href)) {
134
+ throw new Error('This link is a template: pass it as `template` together with `params`.');
135
+ }
136
+ if (this.adminizer.adminLinkHandler.isDestructivePath(href)) {
137
+ throw new Error('This page changes data and cannot be opened by the assistant.');
138
+ }
139
+ return href;
140
+ }
141
+ slug(value) {
142
+ return String(value ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '-');
143
+ }
144
+ }
@@ -0,0 +1,9 @@
1
+ import type { Adminizer } from '../Adminizer.js';
2
+ import type { AiAssistantAgentSkill } from './AiAssistantAgentSkillHandler.js';
3
+ /**
4
+ * Skills every agent gets for free: who it is talking to, and read/edit access
5
+ * scoped to exactly the data that user may reach in the admin panel. Because
6
+ * every call re-checks the user's permissions, an agent built on them can be
7
+ * offered to non-administrator accounts as well.
8
+ */
9
+ export declare function buildBuiltinAgentSkills(adminizer: Adminizer): AiAssistantAgentSkill[];