pgkiln 0.51.0 → 0.53.0

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 (124) hide show
  1. package/README.md +1 -1
  2. package/SECURITY.md +10 -1
  3. package/db/migrations/121_ai_agents.sql +94 -0
  4. package/db/migrations/122_team_development.sql +186 -0
  5. package/db/migrations/123_workflow_outcomes.sql +407 -0
  6. package/db/migrations/124_component_details.sql +51 -0
  7. package/db/migrations/125_app_change.sql +23 -0
  8. package/db/migrations/126_mail_queue_network_access.sql +119 -0
  9. package/docs/apex-feature-parity.md +50 -19
  10. package/docs/guide/01-installation.md +8 -4
  11. package/docs/guide/03-builder.md +197 -10
  12. package/docs/guide/04-pages-and-regions.md +71 -21
  13. package/docs/guide/05-items.md +7 -1
  14. package/docs/guide/06-processing.md +129 -9
  15. package/docs/guide/07-dynamic-actions.md +34 -0
  16. package/docs/guide/08-security.md +55 -0
  17. package/docs/guide/09-reference.md +5 -2
  18. package/docs/guide/11-from-apex.md +3 -1
  19. package/docs/guide/12-development.md +87 -16
  20. package/docs/guide/14-globalization.md +49 -1
  21. package/docs/guide/18-cli.md +21 -1
  22. package/docs/guide/19-rest-data-sources.md +12 -0
  23. package/docs/guide/20-ai-agents.md +12 -4
  24. package/examples/hr/README.md +2 -0
  25. package/examples/hr/hr_64_ai_agent.sql +97 -0
  26. package/examples/hr/hr_65_ask_reports.sql +21 -0
  27. package/examples/hr/hr_66_team_development.sql +58 -0
  28. package/examples/hr/hr_67_workflow_outcomes.sql +113 -0
  29. package/examples/hr/hr_68_component_details.sql +96 -0
  30. package/examples/hr/hr_70_data_security.sql +79 -0
  31. package/package.json +1 -1
  32. package/public/app.css +135 -21
  33. package/public/app.js +134 -20
  34. package/public/builder.css +133 -1
  35. package/public/builder.js +51 -0
  36. package/src/ai/chat.ts +18 -18
  37. package/src/ai/service.ts +35 -11
  38. package/src/ai/types.ts +7 -2
  39. package/src/app.ts +15 -0
  40. package/src/builder/advisor.ts +6 -3
  41. package/src/builder/ai-agents.ts +372 -0
  42. package/src/builder/ai-builder.ts +3 -3
  43. package/src/builder/ai.ts +13 -9
  44. package/src/builder/app-updates.ts +55 -0
  45. package/src/builder/assistant.ts +22 -7
  46. package/src/builder/components.ts +38 -9
  47. package/src/builder/data-security.ts +182 -0
  48. package/src/builder/designer.ts +8 -3
  49. package/src/builder/feedback.ts +6 -1
  50. package/src/builder/home.ts +16 -5
  51. package/src/builder/locks.ts +7 -1
  52. package/src/builder/mail-queue.ts +212 -0
  53. package/src/builder/mail.ts +72 -45
  54. package/src/builder/network-access.ts +140 -0
  55. package/src/builder/object-actions.ts +1 -0
  56. package/src/builder/object-browser.ts +1 -1
  57. package/src/builder/region-help.ts +2 -2
  58. package/src/builder/rest-catalogs.ts +19 -4
  59. package/src/builder/routes.ts +4 -1
  60. package/src/builder/shared.ts +1 -1
  61. package/src/builder/sql.ts +2 -0
  62. package/src/builder/team.ts +991 -0
  63. package/src/builder/themeroller.ts +47 -6
  64. package/src/builder/ui.ts +7 -1
  65. package/src/builder/websources.ts +1 -1
  66. package/src/builder/workflow-migrate.ts +210 -0
  67. package/src/builder/workflows.ts +29 -6
  68. package/src/builder/workspace-home.ts +2 -0
  69. package/src/builder/workspace-mail.ts +121 -0
  70. package/src/builder/workspace-transfer.ts +1 -1
  71. package/src/builder/workspaces.ts +1 -0
  72. package/src/cli/main.ts +25 -3
  73. package/src/cli/mcp.ts +40 -4
  74. package/src/cli/only.ts +52 -0
  75. package/src/cli/replace.ts +11 -1
  76. package/src/datasec.ts +343 -0
  77. package/src/i18n/ar.ts +17 -0
  78. package/src/i18n/cs.ts +17 -0
  79. package/src/i18n/da.ts +17 -0
  80. package/src/i18n/de.ts +17 -0
  81. package/src/i18n/el.ts +17 -0
  82. package/src/i18n/es.ts +17 -0
  83. package/src/i18n/fi.ts +17 -0
  84. package/src/i18n/fr.ts +17 -0
  85. package/src/i18n/he.ts +17 -0
  86. package/src/i18n/it.ts +17 -0
  87. package/src/i18n/ja.ts +17 -0
  88. package/src/i18n/ko.ts +17 -0
  89. package/src/i18n/nb.ts +17 -0
  90. package/src/i18n/pl.ts +17 -0
  91. package/src/i18n/pt.ts +17 -0
  92. package/src/i18n/ru.ts +17 -0
  93. package/src/i18n/sv.ts +17 -0
  94. package/src/i18n/tr.ts +17 -0
  95. package/src/i18n/uk.ts +17 -0
  96. package/src/i18n/zh.ts +17 -0
  97. package/src/i18n.ts +34 -0
  98. package/src/instance.ts +2 -0
  99. package/src/mail/mail.ts +50 -11
  100. package/src/mail/providers.ts +6 -3
  101. package/src/metadata.ts +11 -3
  102. package/src/network-acl.ts +126 -0
  103. package/src/objectstore.ts +3 -0
  104. package/src/runtime/ai-agents.ts +365 -0
  105. package/src/runtime/ai-filter.ts +385 -107
  106. package/src/runtime/assistant.ts +64 -11
  107. package/src/runtime/context.ts +2 -0
  108. package/src/runtime/engine.ts +3 -1
  109. package/src/runtime/ir-phrases.ts +812 -0
  110. package/src/runtime/ir-plan.ts +449 -0
  111. package/src/runtime/items.ts +1 -1
  112. package/src/runtime/regions.ts +6 -2
  113. package/src/runtime/render.ts +5 -3
  114. package/src/runtime/report.ts +1 -1
  115. package/src/runtime/styles.ts +54 -12
  116. package/src/runtime/tasks.ts +63 -12
  117. package/src/task-events.ts +232 -0
  118. package/src/team-samples.ts +74 -0
  119. package/src/team.ts +342 -0
  120. package/src/webclient.ts +45 -4
  121. package/src/webrequests.ts +5 -0
  122. package/src/websources.ts +30 -12
  123. package/src/workflow.ts +135 -18
  124. package/src/workspace-export.ts +16 -1
@@ -158,9 +158,11 @@ applications' files. Choose what goes in:
158
158
  | REST source catalogs | Every [catalog](19-rest-data-sources.md#rest-source-catalogs) with its services |
159
159
  | SQL scripts, blueprints, data generators | The SQL Workshop's saved work |
160
160
  | Data Reporter datasets | Every [dataset](#data-reporter-datasets-and-reporting-applications) (sprint 110) |
161
+ | Team Development | [Label groups, labels, milestones, templates and issues](#team-development) with their comments and history; an issue's application by alias (sprint 113) |
161
162
 
162
- Never in the file: the workspace's name, its [isolation](#isolated-workspaces) and database roles, passwords and
163
- secrets, script runs and other history. **Applications are exported on their own** (an application's *Export*).
163
+ Never in the file: the workspace's name, its [isolation](#isolated-workspaces) and database roles, its
164
+ [mail service](#a-workspaces-own-mail-service) and [network access list](#network-access) (installation-specific, like
165
+ isolation), passwords and secrets, script runs and other history. **Applications are exported on their own** (an application's *Export*).
164
166
 
165
167
  **Import** reads such a file into the current workspace, for the components you choose. Components with a name the
166
168
  workspace already has are kept, or replaced with *Replace components with the same name*; nothing is deleted. A
@@ -275,7 +277,8 @@ JavaScript. The [default-password banner](#signing-in-and-developer-accounts) st
275
277
  - **System message**: the pgkiln version the server runs and, when the [release check](#the-release-check) is on and
276
278
  a newer release is out, **pgkiln X is available** with a link to its release notes and how to upgrade (back up,
277
279
  install the new version, `pgkiln migrate`, restart).
278
- - **A side column**: About, **Learn more** (this guide, the [tutorial](10-tutorial.md),
280
+ - **A side column**: About, **Team Development** ([open issues](#team-development), those assigned to you, unseen
281
+ mentions of you, or *Set up Team Development* before it is set up), **Learn more** (this guide, the [tutorial](10-tutorial.md),
279
282
  [Coming from APEX](11-from-apex.md), the changelog and the discussions on GitHub) and Resources.
280
283
 
281
284
  ### The release check
@@ -300,9 +303,16 @@ The App Builder's home page, `/builder/apps` (`/builder` shows it too, unless th
300
303
  developers, the SQL Workshop, and for administrators [workspaces](#workspaces), AI services and the
301
304
  [installation log](#installation)). The list, the dashboard and Recent show the current workspace.
302
305
  - **The applications**, as a report (application id, name, alias, pages, page views of the last 24
303
- hours, sign-in, last updated, and Edit / Run) or as cards. **Search** filters the list as you type
306
+ hours, sign-in, last updated, **updated by**, **working copies**, and Edit / Run) or as cards. **Search** filters the list as you type
304
307
  (by name, alias or id; without JavaScript, press Enter). Click a column heading, or use
305
308
  **Actions**, to sort. The view and the sort are remembered until you sign out.
309
+ - (sprint 116) **Updated by** is the developer who changed the application last in the builder (any change to its
310
+ pages, shared components or settings; locks and comments don't count) or who created, imported or copied it;
311
+ *Updated* is the later of that change and the application's own settings. An import with `pgkiln import` or the
312
+ MCP server changes *Updated* only. **Working copies** counts the application's [working copies](#working-copies)
313
+ and links to them. A **lock icon** after the name shows that the application is [locked](#page-locks-and-comments),
314
+ with its owner in the tooltip (and for screen readers). The cards show the same as small badges. On phones the
315
+ report keeps the id, the name with the lock and the actions.
306
316
  - **A side column** (on the right on wide screens, below the list otherwise): About, **Recent** (the
307
317
  applications you opened last) and Tasks.
308
318
  - **The workspace message**, when the workspace has one, above the tiles.
@@ -753,6 +763,10 @@ author or an administrator deletes it. The page list counts each page's comments
753
763
  another number keeps its lock and comments. Locks and comments belong to this installation and
754
764
  are not exported.
755
765
 
766
+ **Make an issue** under a comment turns it into a [Team Development](#team-development) issue about the same page;
767
+ the comment then links to its issue. A page with open issues shows them above the designer's tabs (*1 open issue:
768
+ …*, with *New issue* for that page), and the application's page list tags such pages (*2 issues*).
769
+
756
770
  ### Arranging the layout
757
771
 
758
772
  Regions are placed in sequence order; a region's *column span* (1–12) sets its width, and a
@@ -840,7 +854,7 @@ out, other keys are kept, columns the query no longer returns stay listed so you
840
854
  | `smart_filters` | The same per-column facets, the suggestions per facet and the search field's placeholder |
841
855
  | `display_selector` | Tabs or a select list, "Show all", remember the choice; per other region of the page: in a tab and the tab name (saved in that region's settings) |
842
856
  | `ai_assistant` | The AI service (those the application may use), system prompt, welcome text, placeholder, error message, tool rounds per question, questions per conversation, public or not, and the context queries and tools as JSON (checked when saving: query shapes, parameter types, REST data sources and authorization schemes that exist) ([AI assistant](04-pages-and-regions.md#ai_assistant-ai-assistant)) |
843
- | `report` (also) | *Ask in your own words (AI)*: the AI service and placeholder of the [natural-language filters](04-pages-and-regions.md#natural-language-filters-ask-in-your-own-words) |
857
+ | `report` (also) | *Ask in your own words (AI)*: off, the built-in phrases or an AI service, and the placeholder of [Ask in your own words](04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports) |
844
858
  | `data_reporter` | Data sources (a table or view each: static id, label, description, offered columns with labels and format masks; or (sprint 110) a dataset of the workspace), who may share, build and moderate reports, publishing, rows per page ([Data Reporter](04-pages-and-regions.md#data_reporter-data-reporter)) |
845
859
  | `form_designer` | Who may design forms (an authorization scheme), data sources (a table or view each: static id, label, description, key column, may add / change / delete rows, offered columns with labels and lists of values), rows per page ([Form Designer](04-pages-and-regions.md#form_designer-form-designer)) |
846
860
 
@@ -958,6 +972,48 @@ user, page, rating and browser), set its status (*Open*, *In progress*, *Done*,
958
972
  an answer the user sees. Answering is allowed while the application is locked or Run only. The
959
973
  setting travels with the export; the feedback itself belongs to the installation.
960
974
 
975
+ **Make an issue** turns an entry into a [Team Development](#team-development) issue of the application's workspace
976
+ (title from its first line, the feedback quoted, linked to its page, labelled *Bug* for a problem and *Feature* for
977
+ an idea when the workspace has those labels); open feedback becomes *In progress*, and the entry links to its issue.
978
+
979
+ ## Team Development
980
+
981
+ (sprint 113, migration 122; APEX: Team Development) An issue tracker for the developers of a
982
+ [workspace](#workspaces): **Team Development** on the icon rail (`/builder/team`) and a block in the
983
+ [Workspace home](#workspace-home)'s side column with the number of open issues. Everything is of the current
984
+ workspace and works without JavaScript.
985
+
986
+ - **Set up** installs sample data, like APEX's set-up wizard: the label groups *Type* (Bug, Feature, Task) and
987
+ *Priority* (High, Medium, Low), issue templates (*Bug report*, *Feature request*, *Task*), canned responses and the
988
+ milestones *1.0*, *1.1* and *Backlog*. What the workspace has already (by name) is kept, so it can run again.
989
+ The samples are in English; other languages are added in `src/team-samples.ts`.
990
+ - **Issues** have a title, a description in Markdown (rendered safely: raw HTML is shown as text, links are http,
991
+ https, mailto or relative), *Open* or *Closed*, labels, a milestone, assignees (the workspace's developers and the
992
+ installation's administrators), and optionally an application of the workspace and one of its pages. Comments are
993
+ Markdown too; *Comment and close* does both. **@name** mentions a developer of the workspace: they see it under
994
+ *Mentions of you* (and on the Workspace home) until they open the issue or mark them seen; no mail is sent. Every
995
+ change is in the issue's history (who changed the title, labels, milestone, assignees, link or status, and when).
996
+ An issue is deleted by its author or a workspace administrator.
997
+ - **The list** filters by status, label, milestone, assignee (*Me*, *Nobody*), application and a search of the title
998
+ and text (or `#12` for issue 12). **Save view** keeps the filters under a name, for you only.
999
+ - **The board** shows the issues as columns of cards, by the labels of one label group (a card moved to *High*
1000
+ loses *Medium*; *None of these* removes the group's labels) or by milestone. Each card has a *Move* form, so it
1001
+ works without dragging.
1002
+ - **Labels** belong to label groups and have a colour from a fixed palette (red, orange, yellow, green, teal, blue,
1003
+ purple, gray). **Milestones** have a due date and are open or closed, with the share of their issues closed.
1004
+ **Templates** are issue templates (title, labels, text: *Start from a template* on *New issue*) and canned
1005
+ responses (*Canned response* → *Insert* above an issue's comment field). Any developer of the workspace adds and
1006
+ changes them; only its administrators delete labels, label groups and milestones.
1007
+ - Issues come from [feedback](#feedback) and [developer comments](#page-locks-and-comments) with **Make an issue**,
1008
+ linked both ways. The page designer shows a page's open issues and App Home counts them per page (*Open issues* in
1009
+ its Tasks).
1010
+
1011
+ Issues and the rest are workspace data: they are not part of an application's export (an in-place import keeps them)
1012
+ but travel in the workspace's [Export and import](#export-and-import) (*Team Development*): labels, milestones and
1013
+ templates match by name, an issue by its title and creation time; assignees are kept when they are developers of the
1014
+ importing workspace and the application link when it has an application with that alias. GitHub synchronisation is
1015
+ not part of it.
1016
+
961
1017
  ## Working copies
962
1018
 
963
1019
  APEX's working copies: a second application to change in isolation, then merge back. On an
@@ -1024,8 +1080,8 @@ is the earlier page: every component in one rail, with **Access control** next t
1024
1080
  | **Shortcuts** | Named texts: `"NAME"` in static content, help texts and confirm messages ([chapter 2](02-concepts.md#shortcuts-name)) |
1025
1081
  | **Application computations** | Computations of (application) items for every page: at sign-in, before every page, after every submit ([chapter 6](06-processing.md#application-computations)) |
1026
1082
  | **REST modules** | REST endpoints (method, path, SQL) served by pgkiln, with an OpenAPI description ([chapter 13](13-rest-api.md#rest-modules-in-the-builder)) |
1027
- | **Workflows** | Multi-step processes of tasks, SQL, web service calls (invoke API), decisions, waits and parallel branches, with versions and a diagram ([chapter 6](06-processing.md#workflows)) |
1028
- | **Task definitions** | Approvals and action tasks: subject, owners, administrators, due date, the SQL that runs on completion ([chapter 6](06-processing.md#approvals-and-the-task-list)) |
1083
+ | **Workflows** | Multi-step processes of tasks, SQL, web service calls (invoke API), e-mail, decisions, waits and parallel branches, with versions, draft steps, migration of running instances and a diagram (SVG / PNG download) ([chapter 6](06-processing.md#workflows)) |
1084
+ | **Task definitions** | Approvals and action tasks: subject, owners, administrators, due date, custom outcomes, the SQL that runs on completion, events and e-mail notifications ([chapter 6](06-processing.md#approvals-and-the-task-list)) |
1029
1085
  | **Document templates** | Letters, invoices and other PDFs filled from a query ([chapter 16](16-files.md#document-templates)), with a preview |
1030
1086
  | **Build options** | Include / exclude switches for features; pages and their components name one in their *Build option* property, and *Used in* lists them ([chapter 6](06-processing.md#build-options)) |
1031
1087
  | **Web credentials** | How the application signs in to web services (basic, API key header, bearer token, OAuth2 client credentials); the secret is write-only and encrypted ([chapter 19](19-rest-data-sources.md#web-credentials)) |
@@ -1287,13 +1343,79 @@ provider grant it the application permission *Mail.Send*; for SMTP with OAuth2 t
1287
1343
  *SMTP.SendAsApp* and register its service principal in Exchange Online. In both cases limit it to the
1288
1344
  sending mailboxes (an application access policy or RBAC for Applications).
1289
1345
 
1346
+
1347
+ ### Mail queue
1348
+
1349
+ Administration menu → **Mail queue** (`/builder/mail-queue`; APEX 26.2: Administration → Mail queue): the delivery
1350
+ health of the mail queue, for administrators (every workspace, or one) and a workspace's administrators (the current
1351
+ workspace's applications only; other developers get 403). Filter by period (24 hours, 48 hours, 7 days), workspace and
1352
+ application:
1353
+
1354
+ - **figures**: queued now, retrying now (failed with an error that may pass, waiting for the next of its 5 attempts),
1355
+ being sent, sent and failed in the period, the share delivered and the average provider call;
1356
+ - **Per hour**: queued, sent, failed and retrying mail per hour (UTC);
1357
+ - **Slowest provider calls**: the 10 longest hand-overs of the period (how long the provider took to accept or refuse
1358
+ the mail), with application, service and outcome;
1359
+ - **Per workspace and application**: sent, failed, retrying and queued mail, the average and slowest call;
1360
+ - **Failures**: every failed mail (kept 30 days) with the provider's answer, e.g. the SMTP server's
1361
+ `550 5.1.1 no such user`. **Retry** queues one again with fresh attempts, **Purge** deletes it; *Retry all failed*
1362
+ and *Purge all failed* act on the failures of the filter (never outside the developer's scope).
1363
+
1364
+ Bodies and attachments are never shown here. Every action checks again on the server that the mail's application is in
1365
+ the developer's scope, and is written to the activity log.
1366
+
1367
+ ### A workspace's own mail service
1368
+
1369
+ A workspace's page → *Mail and network access* → **mail service** (`/builder/workspaces/<id>/mail`; APEX 26.1:
1370
+ workspace-level SMTP credentials), for the workspace's administrators: a mail service of the workspace's own, with the
1371
+ same providers and checks as [above](#mail-services). The applications of the workspace that an administrator gave no
1372
+ service send through it (an application an administrator gave one keeps it; a service of another workspace is never
1373
+ used). Its secret is stored encrypted with `PGKILN_SECRET_KEY` like a web credential's: write-only, never shown, never
1374
+ exported. It differs from an installation service in two ways that keep it from reaching what the installation
1375
+ doesn't allow:
1376
+
1377
+ - it uses the provider's own API: no API base URL and no token URL (OAuth2 goes to Microsoft Entra of the tenant);
1378
+ - an SMTP server must be on the installation's allow-list (`PGKILN_REST_ALLOWED_HOSTS`, e.g. `smtp.example.com:587`)
1379
+ and the workspace's [network access](#network-access) list, and resolve to a public address (unless
1380
+ `PGKILN_REST_PRIVATE_HOSTS` lists it). The server resolves the host once and connects to the address it checked,
1381
+ with the host name kept for TLS, on every send and test.
1382
+
1383
+ ### Network access
1384
+
1385
+ Administration menu → **Network access** (`/builder/network`; APEX 26.2: workspace network ACLs), for administrators
1386
+ (any workspace, `?ws=`) and a workspace's administrators (their workspaces): which web services a workspace's
1387
+ applications may call. The installation's list (`PGKILN_REST_ALLOWED_HOSTS`, set in the server's environment, shown
1388
+ read-only; the private hosts to administrators only) applies to every workspace. A workspace can choose **Only the
1389
+ hosts below**: then every outgoing call made for one of its applications must match one of its entries *and* the
1390
+ installation's list. A workspace's list only narrows; it can never widen the installation's, and private, loopback and
1391
+ link-local addresses stay refused unless the installation allows them. Entries are `api.example.com`, `*.example.com`
1392
+ (its subdomains) or `api.example.com:8443` (that port only); restricted with no entries means no outgoing calls.
1393
+
1394
+ It is enforced on the server for every outgoing web call of the workspace: `meta.web_request` and
1395
+ `meta.web_request_source`, REST data sources (regions, write-back, synchronisation, the assistant), the Invoke API
1396
+ process and workflow step, web credential token endpoints, object storage of file items, the builder's *Test* of a
1397
+ source, REST source catalogs read from a URL, and the SMTP server of the workspace's own mail service. Every redirect
1398
+ hop is checked again, and a REST source's cached response is not served once its host is off the list. A refused call
1399
+ fails with *The host … is not on the network access list of workspace …*, is written to the application's debug log
1400
+ (level 2, warning, for `meta.web_request` in a page process; level 1 for regions and processes) and appears under
1401
+ **Refused calls** (kept 7 days). **Check a URL** says whether a URL may be called, without calling it.
1402
+
1403
+ Not covered by a workspace's list: the installation's own services, which only administrators configure (installation
1404
+ mail services, AI services, the geocoder, sign-in providers) and push notification services (their own list,
1405
+ `PGKILN_PUSH_HOSTS`).
1406
+
1290
1407
  ## AI services
1291
1408
 
1292
1409
  Workspace utilities → **AI services** (administrators only; APEX: *Generative AI services*) lists the
1293
1410
  large language model services of this installation. A service has:
1294
1411
 
1295
1412
  - a **name** (upper case, e.g. `CLAUDE`) by which processes, dynamic actions and SQL refer to it;
1296
- - a **provider**: *Claude (Anthropic)* or *OpenAI*, called through their official SDKs;
1413
+ - a **provider**: *Claude (Anthropic)* or *OpenAI*, called through their official SDKs; *Gemini*
1414
+ (Google, through its OpenAI-compatible endpoint `https://generativelanguage.googleapis.com/v1beta/openai/`,
1415
+ with the `openai` SDK); or *OpenAI-compatible*: any endpoint that speaks OpenAI's Chat Completions,
1416
+ such as Ollama (`http://localhost:11434/v1`), vLLM, Azure OpenAI (`https://<resource>.openai.azure.com/openai/v1/`)
1417
+ or a gateway. An OpenAI-compatible service needs a base URL; its key may stay empty for endpoints
1418
+ without keys (a local Ollama);
1297
1419
  - a **model**, exactly as the provider names it (new Claude services start with `claude-opus-5-5`);
1298
1420
  pgkiln never changes or downgrades it;
1299
1421
  - for Claude, the **effort** (how deeply the model thinks; current Claude models always think, lower
@@ -1305,8 +1427,9 @@ large language model services of this installation. A service has:
1305
1427
  - an optional **base URL** for a gateway or proxy that speaks the provider's API (calls to the AI
1306
1428
  service don't go through `PGKILN_REST_ALLOWED_HOSTS`: only administrators set this URL);
1307
1429
  - the **API key**: stored encrypted with `PGKILN_SECRET_KEY`, write-only (never shown again, never
1308
- exported or logged, not readable by applications). Without one the server's `ANTHROPIC_API_KEY` /
1309
- `OPENAI_API_KEY` is used.
1430
+ exported or logged, not readable by applications, never sent to the browser). Without one the
1431
+ server's `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` or `OPENAI_COMPATIBLE_API_KEY`
1432
+ (by provider) is used.
1310
1433
 
1311
1434
  On a service's page, **Applications** sets which applications may use it, each with optional daily
1312
1435
  limits (requests and input + output tokens per UTC day; a request over a limit fails with a message
@@ -1314,11 +1437,71 @@ and is logged as *limited*), and **Test** sends a prompt (logged as a builder te
1314
1437
  on the list page for all services, the usage log: the last requests and the totals per application
1315
1438
  and service of the last 30 days.
1316
1439
 
1440
+ An interactive report's *Ask in your own words* doesn't need a service: without one (or when the
1441
+ service is disabled or not allowed for the application) pgkiln reads the request with its built-in
1442
+ phrases ([chapter 4](04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports)).
1443
+
1317
1444
  **Prompts go to the provider.** Whatever a prompt contains, including the item values substituted
1318
1445
  into it, is sent to the chosen provider (or the gateway of the base URL). Choose providers and
1319
1446
  models that your organisation allows for that data. See
1320
1447
  [Generate text with AI](06-processing.md#generate-text-with-ai).
1321
1448
 
1449
+ ## AI agents
1450
+
1451
+ Shared Components → Generative AI → **AI Agents** (APEX 26.1 / 26.2: AI Agents and AI Tools) holds an
1452
+ application's agents and the tools they may use. An agent reasons over a user's request in an
1453
+ [AI assistant region](04-pages-and-regions.md#ai_assistant-ai-assistant) and acts **only through the
1454
+ tools you approve**; pgkiln prepares the context, runs each tool call itself under the checks below,
1455
+ and composes the conversation.
1456
+
1457
+ An **agent** has:
1458
+
1459
+ - a **name** (upper case, e.g. `SALES_AGENT`) that regions refer to, and a description;
1460
+ - an **AI service** the application may use, and a **system prompt** (`&ITEM.` is replaced by the item's
1461
+ value as delimited data, as in assistant regions);
1462
+ - a **reasoning effort**: *low*, *medium* or *high*, or empty for the service's own. For Claude it is
1463
+ the output effort (thinking depth, `output_config.effort`); for OpenAI, Gemini and OpenAI-compatible
1464
+ services it is sent as `reasoning_effort` (leave it empty for models without reasoning: they refuse it);
1465
+ - the **tools** it may use (ticked from the application's tools);
1466
+ - **maximum steps** (rounds of tool calls per question, 1–20, default 5) and **maximum output tokens**
1467
+ per call (never above the service's maximum);
1468
+ - an **authorization scheme**: other users see *You may not use this assistant* instead of the message
1469
+ box, and their questions are refused before anything is sent to the AI service.
1470
+
1471
+ A **tool** has a name the model calls it by, a description that tells the model what it does and when
1472
+ to use it, optional **parameters** (JSON: `{"DNAME": {"type": "string", "description": "…", "optional": true}}`;
1473
+ types string, integer, number, boolean, date, `enum` for a list of texts), an optional authorization
1474
+ scheme (only users who pass it are offered the tool) and one of these types:
1475
+
1476
+ | Type | What runs | Checks |
1477
+ |---|---|---|
1478
+ | **SQL query** | one `SELECT` (or `WITH`, `VALUES`, `TABLE`) with the parameters as `:NAME` bind values, plus `:APP_USER` and items | as the application's database role (grants and row level security apply), in a **read-only transaction** (functions it calls can't write either), with a time limit (1–60 s, default 15) and at most *maximum rows* rows (default 50) back to the model |
1479
+ | **PL/pgSQL function** | `schema.function` called with named arguments: each parameter name in lower case is an argument name (`P_EMPNO` → `p_empno => …`) | as the application's role; it **may change data** (committed); same time limit and rows |
1480
+ | **REST data source** | a REST data source of the application; the parameters become its parameters | as in [assistant regions](04-pages-and-regions.md#ai_assistant-ai-assistant) |
1481
+ | **JavaScript** | nothing on the server: after the answer the browser calls the function registered with `pgkiln.actions.register(action)` (with `{value: arguments}`) and the dynamic actions of the **custom event** of that name (the arguments as JSON) | once, right after the answer; the arguments are escaped data in the page |
1482
+ | **Navigate** | the model picks one of the tool's **pages**, a link text and item values of that page; pgkiln builds the URL with the item values' **checksum** (bound to application, page and user) and shows the link under the answer | only pages the user may open are offered (the page's authorization scheme and sign-in); only items of that page; the model's values are never substituted (`&ITEM.`); nothing opens the link by itself |
1483
+
1484
+ The model's arguments are checked against the parameters before a tool runs; they are bind values
1485
+ (escaped literals), never SQL text, and can't replace the server's own names (`:APP_USER`, `:APP_ID`, …).
1486
+ Tool results go back to the model as data, with the instruction never to follow instructions inside them.
1487
+
1488
+ **Every tool call is logged** with the arguments the model gave, its status (*ok*, *error*, or *refused*
1489
+ for a page the user may not open) and duration: in the page's debug messages (level 4) and under
1490
+ Activity → **AI usage** → *Tool calls* (`meta.ai_tool_call`, kept 30 days; arguments may hold user data,
1491
+ only developers see them). The token usage of each call to the model is logged as before.
1492
+
1493
+ Agents and tools are kept in the application (`meta.app.ai_agents`, `meta.app.ai_tools`) and **exported
1494
+ with it**; an export made before they existed imports with none. The forms check each one before
1495
+ saving (a SQL tool must be a query, a function `schema.name`, a navigate tool's pages must exist); the
1496
+ runtime checks them again and shows *This assistant is not set up yet* for a broken one. Renaming a
1497
+ tool keeps it in its agents; a tool an agent uses can't be deleted; renaming an agent renames it in
1498
+ the regions that use it.
1499
+
1500
+ The HR example (`examples/hr/hr_64_ai_agent.sql`) has an agent `HR_AGENT` on page 46: ask *Who in Sales
1501
+ earns most?* and it calls the SQL tool `top_earners` as the signed-in user's role, then offers
1502
+ *Open BLAKE's record*, a link to the employee form (page 3) with a valid checksum, only to users who
1503
+ may open that page.
1504
+
1322
1505
  ## App Builder AI
1323
1506
 
1324
1507
  The App Builder uses one AI service (APEX: AI Assistant in App Builder and SQL Workshop). An
@@ -1429,6 +1612,10 @@ the SQL Workshop's connection may use. Without JavaScript the header's field ope
1429
1612
  transaction (nothing changes when one fails, and the error shows under *Output*); **Open in the worksheet** puts
1430
1613
  them there instead. *Use as template* starts a new table with the same columns and keys (names with `_copy`).
1431
1614
  Defaults, checks and index expressions are single expressions (no `;` or comments).
1615
+ - (sprint 119) **Security** (the Object Browser's *Security* button on a table, *Security…* in its ⋯ menu): the
1616
+ table's row level security policies, a matrix of database roles × columns with *read* and *update*, and masked
1617
+ columns per application role in a masking view; Preview SQL, Apply (one transaction, recorded in the Change Log)
1618
+ or Open in the worksheet ([chapter 8](08-security.md#data-security-per-table-rows-columns-and-values)).
1432
1619
  - **Run Statement** (**Ctrl/Cmd + Enter**) runs the statement at the cursor: the one the cursor is in, else the
1433
1620
  nearest one before it. A selection runs as it is, even when it holds several statements. The rows of the last
1434
1621
  query show in **Query Result**, up to 500, with row numbers, numbers right-aligned and NULL as *(null)*. The
@@ -256,7 +256,7 @@ Features for end users:
256
256
  computed columns and views under a name, and switch between saved reports (below);
257
257
  - **rows per page**, **download CSV / Excel / PDF**, **print**, **reset**.
258
258
  - Active search, filters, break, aggregates, highlights, computed columns and views appear as
259
- removable chips. Once a group by, pivot or chart is set, **Report / Group by / Pivot / Chart**
259
+ removable chips (with *Ask in your own words*, as chips with Edit and Remove under the question box). Once a group by, pivot or chart is set, **Report / Group by / Pivot / Chart**
260
260
  links switch between the views; all views use the same search, filters and facets.
261
261
  - **Paging** with Previous/Next.
262
262
  - On phones every row **reflows** into a card with labelled values.
@@ -267,26 +267,71 @@ and highlight values become literals, columns must exist in the result, operator
267
267
  functions, chart types and colors come from fixed lists, sort positions are integers, and computed
268
268
  column expressions are parsed (below), never pasted into SQL.
269
269
 
270
- ### Natural-language filters (*Ask in your own words*)
271
-
272
- With an [AI service](06-processing.md#generate-text-with-ai), an interactive report can take a
273
- question in the user's own words (APEX: natural language to interactive report): *"analysts and
274
- managers hired before 1982, highest salary first"*. Set `"ai_filter": {"service": "NAME"}` in the
275
- report's attributes (or *Ask in your own words (AI)* under the report's settings in the page
276
- designer); a question box appears above the report when the application may use the service.
277
-
278
- The question goes to the service with the report's shown columns (names, headings and kinds:
279
- text, number, date) and its filter operators; the answer is a structured output (filters, a search
280
- text, a sort column) that pgkiln checks again (only those columns and operators, at most five
281
- filters, short values) and turns into the report's ordinary URL parameters. So the model can only
282
- set what the user could set by hand in the Actions menu; the report builds the SQL (values as
283
- escaped literals) and runs it as the application's role, and the user sees which filters were
284
- applied and can change or remove them. Hidden columns (`hidden`) are not offered. Signed-in users
285
- only, unless `"public": true`; logged in the AI usage log with the source `nl2ir`.
270
+ ### Ask in your own words (AI interactive reports)
271
+
272
+ An interactive report can take a request in the user's own words (APEX 26.1/26.2: AI interactive
273
+ reports): *"sum of salary by department where job is clerk"*, *"managers hired before 1982, highest
274
+ salary first"*, *"bonus is 10% of salary"*. Set `"ai_filter": {}` in the report's attributes (or
275
+ *Ask in your own words* under the report's settings in the page designer); a question box appears
276
+ above the report.
277
+
278
+ The answer is never SQL: it becomes the report's **own settings**, the same ones the Actions menu
279
+ sets: filters, highlights, a search, a sort, a control break, totals (aggregates), a group by, a
280
+ pivot, a chart and computed columns. They are listed under the box as **chips**; each chip has
281
+ **Edit** (a small form with its column, operator, value, function, color or expression) and
282
+ **Remove**, and **Undo** returns to the settings before the last change (the last five steps, kept
283
+ in the session). A new request refines the current settings: a filter on a column replaces that
284
+ column's filters, a sort, break, group by, pivot or chart replaces the old one, highlights, totals
285
+ and computed columns are added; *"start over"* clears them. The chips are buttons of one form, so
286
+ they work without JavaScript.
287
+
288
+ **Without an AI service** pgkiln reads the request itself with a rule-based parser for common
289
+ phrases (APEX 26.2: works with zero AI). Columns are recognised by name or heading (also without
290
+ spaces, and verbs such as *hired* for `hiredate`); values such as *"only clerks"* or *"in sales"*
291
+ are looked up among the report's own rows (as a query parameter); what isn't understood is said
292
+ in the message, not guessed. Phrases it understands, in English (always), Dutch and German (when
293
+ that is the user's language):
294
+
295
+ | Request | Becomes |
296
+ |---|---|
297
+ | *salary over 3000*, *salary between 1,000 and 3k*, *hired before june 1981*, *hired in 1981*, *name contains "mi"*, *no commission* | filters (`gt`, `ge` + `le`, `lt`, a date range, `contains`, `null`) |
298
+ | *only clerks*, *not clerks*, *in sales*, *active* | a filter on the column that holds the value |
299
+ | *sort by hire date, newest first*, *highest salary first* | the sort |
300
+ | *sum of salary by department*, *count by department and job* | a group by with its function |
301
+ | *total salary*, *average salary* | a totals row |
302
+ | *break by department* | a control break |
303
+ | *pivot sum of salary by department and job* | a pivot |
304
+ | *pie chart of count by department*, *chart of salary by job* | a chart |
305
+ | *highlight salary over 4000 in red* | a highlight |
306
+ | *bonus is 10% of salary*, *yearly = sal * 12* | a computed column |
307
+ | *search for smith* | the search |
308
+ | *start over*, *clear all* | no settings |
309
+
310
+ Dutch (*salaris boven 3000*, *sorteer op datum aflopend*, *som van salaris per afdeling*, *alleen
311
+ klerken*, *bonus is 10% van salaris*) and German (*Gehalt über 3000*, *sortiere nach Datum
312
+ absteigend*, *Summe von Gehalt pro Abteilung*, *nur Angestellte*) work the same way; another
313
+ language adds a phrase table to `src/runtime/ir-phrases.ts`.
314
+
315
+ **With an AI service** (`"ai_filter": {"service": "NAME"}`, an [AI
316
+ service](06-processing.md#generate-text-with-ai) the application may use), the request goes to the
317
+ service with the report's shown columns (names, headings and kinds: text, number, date, boolean),
318
+ its operators and functions and the current settings; the answer is a structured output. When the
319
+ service is disabled or not allowed for the application, the built-in phrases answer instead. The
320
+ request and the column names go to the service's provider; logged in the AI usage log with the
321
+ source `nl2ir`.
322
+
323
+ Either way the answer is checked again before it is applied: only the report's shown columns
324
+ (`hidden` columns are neither offered nor accepted), operators, functions, chart types and colors
325
+ from the report's fixed lists, at most five filters, highlights and totals, short values (which
326
+ become escaped literals or query parameters), and computed expressions that pass the same
327
+ validator as Actions → Compute (below). A chip edit is checked the same way; a form that names a
328
+ hidden column, an unknown operator or a chip that isn't there is refused. The report builds the
329
+ SQL and runs it as the application's role. Signed-in users only, unless `"public": true`.
286
330
 
287
331
  | Key | Meaning |
288
332
  |---|---|
289
- | `ai_filter.service` | The AI service's name |
333
+ | `ai_filter` | `{}` for the built-in phrases |
334
+ | `ai_filter.service` | An AI service's name (the built-in phrases when it isn't available) |
290
335
  | `ai_filter.placeholder` | The question box's placeholder |
291
336
  | `ai_filter.public` | Also for users who are not signed in |
292
337
 
@@ -1297,8 +1342,12 @@ forms *Departments* and *Book a meeting*.
1297
1342
  ### `ai_assistant` (AI assistant)
1298
1343
 
1299
1344
  An **AI assistant** region (APEX: AI Assistant, AI agents and tools) is a chat with an
1300
- [AI service](06-processing.md#generate-text-with-ai) (Claude or OpenAI) that an administrator
1301
- configured and allowed for the application. The developer gives it a system prompt and, optionally,
1345
+ [AI service](06-processing.md#generate-text-with-ai) (Claude, OpenAI, Gemini or an OpenAI-compatible
1346
+ endpoint) that an administrator configured and allowed for the application. Either the region names an
1347
+ **[AI agent](03-builder.md#ai-agents)** of the application (`"agent": "SALES_AGENT"`: the agent sets the
1348
+ service, system prompt, reasoning effort, tools, steps and output limit, and its authorization scheme
1349
+ decides who may use the region; tools may also be PL/pgSQL functions, JavaScript actions and links to
1350
+ pages), or the developer gives the region a system prompt and, optionally,
1302
1351
  **context queries** and **tools**; the model then answers questions about the application's data,
1303
1352
  calling the tools when it needs to, like an agent. It works without JavaScript (the message is
1304
1353
  posted and the page comes back with the answer); with JavaScript the Send button shows that the
@@ -1344,6 +1393,7 @@ Attributes (written by the region's **Settings**; they travel with the applicati
1344
1393
 
1345
1394
  | Key | Default | Meaning |
1346
1395
  |---|---|---|
1396
+ | `agent` | | (121) An [AI agent](03-builder.md#ai-agents) of the application; then leave out `service`, `system`, `tools` and `max_rounds` |
1347
1397
  | `service` | | The AI service's name |
1348
1398
  | `system` | | The system prompt (`&ITEM.` substitutions as delimited data) |
1349
1399
  | `welcome` | | The first message shown (translatable) |
@@ -1357,7 +1407,7 @@ Attributes (written by the region's **Settings**; they travel with the applicati
1357
1407
 
1358
1408
  The HR example's page 38 (*HR assistant*) has an assistant with a context query over `hr.policy`
1359
1409
  and four tools (the user's own leave, colleagues, departments, and *request_leave*, which files a
1360
- leave request), next to a staff list with [*Ask in your own words*](#natural-language-filters-ask-in-your-own-words).
1410
+ leave request), next to a staff list with [*Ask in your own words*](#ask-in-your-own-words-ai-interactive-reports).
1361
1411
 
1362
1412
  ### Template components
1363
1413
 
@@ -217,6 +217,11 @@ item's config. A combobox is free text and isn't checked.
217
217
  A `popup_lov` item shows the chosen value's display text and a **Search** button. The button
218
218
  opens a dialog with a search field: the server searches the item's own list of values (case
219
219
  insensitive, on the display column and any extra columns) and answers one page at a time.
220
+ **The next page loads as the list scrolls** (APEX: infinite scrolling): when the end of the list
221
+ comes into view the dialog fetches the next `page_size` rows and adds them, until the list of
222
+ values has no more, so a list of thousands of rows scrolls to its end without a *More* button. A
223
+ new search starts over at the first page. A browser without `IntersectionObserver` pages with
224
+ ‹ and *More* instead.
220
225
 
221
226
  - **Several columns**: the first column is the display value, the second the return value, and
222
227
  any further columns are shown in the dialog (and searched), for example:
@@ -233,7 +238,8 @@ insensitive, on the display column and any extra columns) and answers one page a
233
238
  parameter (never SQL text), only for an item the user may see and change on a page they may
234
239
  open. A submitted value must be one the list of values returns, or the page shows an error.
235
240
 
236
- The HR example's page 26 (*Pick an employee*) has one with name, job and department columns.
241
+ The HR example's page 26 (*Pick an employee*) has one with name, job and department columns;
242
+ page 47 (*Component details*) one over 6,000 rows, 100 a page.
237
243
 
238
244
  ## Cascading lists of values
239
245