wizards 0.5.0 → 0.5.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/README.md +45 -42
- package/apps/runtime/dist/agents/studio-assistant.js +3 -3
- package/apps/runtime/dist/app.js +7 -4
- package/apps/runtime/dist/auth/index.js +2 -2
- package/apps/runtime/dist/authoring/guide.js +1 -1
- package/apps/runtime/dist/cli/account.js +31 -28
- package/apps/runtime/dist/cli/autostart.js +5 -3
- package/apps/runtime/dist/cli/clients.js +1 -1
- package/apps/runtime/dist/cli/connect.js +24 -24
- package/apps/runtime/dist/cli/features.js +6 -6
- package/apps/runtime/dist/cli/main.js +51 -39
- package/apps/runtime/dist/cli/mcp.js +5 -4
- package/apps/runtime/dist/cli/menu.js +32 -30
- package/apps/runtime/dist/cli/report.js +7 -9
- package/apps/runtime/dist/cli/setup.js +42 -38
- package/apps/runtime/dist/cli/start.js +16 -15
- package/apps/runtime/dist/cli/status.js +20 -20
- package/apps/runtime/dist/cli/ui.js +9 -2
- package/apps/runtime/dist/cli/update.js +6 -6
- package/apps/runtime/dist/cli/validate.js +1 -1
- package/apps/runtime/dist/connectors/external.js +3 -2
- package/apps/runtime/dist/connectors/index.js +5 -5
- package/apps/runtime/dist/connectors/plugin-connections.js +2 -2
- package/apps/runtime/dist/connectors/via-account.js +5 -5
- package/apps/runtime/dist/db/control-schema.js +14 -0
- package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
- package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
- package/apps/runtime/dist/docs/dev/index.md +16 -14
- package/apps/runtime/dist/docs/dev/plugins/connections.md +27 -24
- package/apps/runtime/dist/docs/dev/plugins/events.md +19 -17
- package/apps/runtime/dist/docs/dev/plugins/examples.md +13 -12
- package/apps/runtime/dist/docs/dev/plugins/index.md +29 -24
- package/apps/runtime/dist/docs/dev/plugins/layout.md +20 -19
- package/apps/runtime/dist/docs/dev/plugins/quick-start.md +16 -15
- package/apps/runtime/dist/docs/dev/plugins/reference.md +52 -40
- package/apps/runtime/dist/docs/dev/plugins/reloading.md +24 -23
- package/apps/runtime/dist/docs/dev/plugins/routes.md +26 -22
- package/apps/runtime/dist/docs/dev/plugins/runners.md +49 -44
- package/apps/runtime/dist/docs/dev/plugins/server.md +95 -43
- package/apps/runtime/dist/docs/dev/plugins/shipping.md +19 -16
- package/apps/runtime/dist/docs/dev/plugins/space.md +75 -69
- package/apps/runtime/dist/docs/dev/plugins/starters.md +22 -17
- package/apps/runtime/dist/docs/dev/plugins/studio.md +80 -37
- package/apps/runtime/dist/docs/dev/plugins/styling.md +44 -41
- package/apps/runtime/dist/docs/dev/plugins/tables.md +11 -11
- package/apps/runtime/dist/docs/dev/plugins/tools.md +26 -25
- package/apps/runtime/dist/docs/dev/wizard-format.md +35 -23
- package/apps/runtime/dist/docs/user/ai-apps.md +46 -43
- package/apps/runtime/dist/docs/user/assistant.md +41 -36
- package/apps/runtime/dist/docs/user/build-a-wizard.md +64 -39
- package/apps/runtime/dist/docs/user/command-line.md +47 -44
- package/apps/runtime/dist/docs/user/connectors.md +40 -35
- package/apps/runtime/dist/docs/user/create-account.md +27 -22
- package/apps/runtime/dist/docs/user/data.md +29 -29
- package/apps/runtime/dist/docs/user/first-start.md +26 -28
- package/apps/runtime/dist/docs/user/index.md +16 -17
- package/apps/runtime/dist/docs/user/install.md +28 -27
- package/apps/runtime/dist/docs/user/local-only.md +21 -17
- package/apps/runtime/dist/docs/user/models.md +92 -84
- package/apps/runtime/dist/docs/user/plugins.md +28 -23
- package/apps/runtime/dist/docs/user/run.md +51 -50
- package/apps/runtime/dist/docs/user/server.md +9 -9
- package/apps/runtime/dist/docs/user/space.md +87 -65
- package/apps/runtime/dist/docs/user/steps.md +73 -69
- package/apps/runtime/dist/docs/user/test-and-share.md +127 -105
- package/apps/runtime/dist/docs/user/troubleshooting.md +40 -38
- package/apps/runtime/dist/docs/user/wordpress.md +62 -54
- package/apps/runtime/dist/documents/parse.js +2 -2
- package/apps/runtime/dist/engenty/shims/web-ingest.js +17 -1
- package/apps/runtime/dist/engine/input.js +4 -4
- package/apps/runtime/dist/engine/push.js +2 -2
- package/apps/runtime/dist/engine/runner.js +8 -8
- package/apps/runtime/dist/engine/steps.js +2 -2
- package/apps/runtime/dist/engine/surface.js +1 -1
- package/apps/runtime/dist/film/step.js +3 -3
- package/apps/runtime/dist/harness/claude.js +1 -1
- package/apps/runtime/dist/harness/codex.js +1 -1
- package/apps/runtime/dist/harness/model.js +2 -2
- package/apps/runtime/dist/harness/terminal.js +3 -3
- package/apps/runtime/dist/mcp/handler.js +2 -2
- package/apps/runtime/dist/mcp/tools.js +9 -9
- package/apps/runtime/dist/models.js +15 -15
- package/apps/runtime/dist/plugins/claims.js +56 -0
- package/apps/runtime/dist/plugins/loader.js +2 -0
- package/apps/runtime/dist/plugins/public.js +2 -3
- package/apps/runtime/dist/plugins/runs.js +13 -8
- package/apps/runtime/dist/render/chromium.js +24 -11
- package/apps/runtime/dist/routes/api.js +15 -9
- package/apps/runtime/dist/routes/debug.js +1 -1
- package/apps/runtime/dist/routes/delivery.js +46 -0
- package/apps/runtime/dist/routes/lang.js +7 -0
- package/apps/runtime/dist/routes/marketplace.js +2 -1
- package/apps/runtime/dist/routes/plugin-public.js +2 -1
- package/apps/runtime/dist/routes/plugins.js +13 -14
- package/apps/runtime/dist/routes/runs.js +59 -49
- package/apps/runtime/dist/routes/studio.js +53 -23
- package/apps/runtime/dist/services/access.js +3 -3
- package/apps/runtime/dist/services/assistant-chats.js +1 -1
- package/apps/runtime/dist/services/brand.js +2 -2
- package/apps/runtime/dist/services/cloud.js +5 -4
- package/apps/runtime/dist/services/errors.js +2 -1
- package/apps/runtime/dist/services/knowledge-model.js +2 -2
- package/apps/runtime/dist/services/marketplace.js +2 -2
- package/apps/runtime/dist/services/package-file.js +12 -9
- package/apps/runtime/dist/services/package.js +2 -2
- package/apps/runtime/dist/services/project-files.js +10 -12
- package/apps/runtime/dist/services/projects.js +13 -8
- package/apps/runtime/dist/services/runners.js +4 -3
- package/apps/runtime/dist/services/runs.js +3 -3
- package/apps/runtime/dist/services/site.js +2 -2
- package/apps/runtime/dist/services/space-categories.js +4 -4
- package/apps/runtime/dist/services/space-data.js +13 -13
- package/apps/runtime/dist/services/space-origin.js +1 -1
- package/apps/runtime/dist/services/spaces.js +7 -7
- package/apps/runtime/dist/services/wizards.js +5 -5
- package/apps/runtime/dist/tools/net-guard.js +135 -24
- package/apps/runtime/dist/tools/plugin.js +1 -1
- package/apps/runtime/dist/tools/store.js +1 -1
- package/apps/runtime/dist/widgets/step.js +1 -1
- package/apps/web/dist/assets/AboutDialog-DaYwk84X.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-BXpkgatF.js → ChangelogDialog-CsYbEayb.js} +1 -1
- package/apps/web/dist/assets/{ChatView-t7z44uFN.js → ChatView-BRD3fKZU.js} +1 -1
- package/apps/web/dist/assets/{EditorPage-4qxNJgKr.js → EditorPage-ocPlZD3E.js} +3 -3
- package/apps/web/dist/assets/{FlowPreview-WpS8DCPl.js → FlowPreview-Bm7sv746.js} +1 -1
- package/apps/web/dist/assets/{Harness-DL7Bse40.js → Harness-DKE-YNA5.js} +1 -1
- package/apps/web/dist/assets/{NewWizardChannels-Crt6PzH2.js → NewWizardChannels-CnWckqjO.js} +1 -1
- package/apps/web/dist/assets/{NewWizardPage-COiVmNsl.js → NewWizardPage-BXTpbHVM.js} +1 -1
- package/apps/web/dist/assets/{OnboardingPage-BwluEqXq.js → OnboardingPage-3Rz5uOsa.js} +1 -1
- package/apps/web/dist/assets/{ProjectPage-BIRU_AlN.js → ProjectPage-QdSJwm3k.js} +1 -1
- package/apps/web/dist/assets/{PublicRunner-BxMb4gEt.js → PublicRunner-DEpBer5g.js} +1 -1
- package/apps/web/dist/assets/{ReadOnly-D9r5ze5S.js → ReadOnly-rr_RJD-a.js} +1 -1
- package/apps/web/dist/assets/{RunDrawer-DmJl2QOJ.js → RunDrawer-BsZt0tL9.js} +1 -1
- package/apps/web/dist/assets/{RunnerView-CF_1rC-7.js → RunnerView-BbRukEdE.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-qkWsQIMN.js +1 -0
- package/apps/web/dist/assets/{SetupPage-BGKtbS7j.js → SetupPage-PzvPPLfM.js} +1 -1
- package/apps/web/dist/assets/{SharePage-C4MqXKDA.js → SharePage-7vdDWG1n.js} +1 -1
- package/apps/web/dist/assets/changelog-BFdAYD28.js +1 -0
- package/apps/web/dist/assets/{esm-CsY8TkyE.js → esm-g0kaGp8t.js} +1 -1
- package/apps/web/dist/assets/{index-BjQvGQp_.js → index-BzPid1oQ.js} +4 -4
- package/apps/web/dist/assets/{lock-CI6TpWL-.js → lock-BK9RYIft.js} +1 -1
- package/apps/web/dist/assets/oss-credits-BJrR7BR1.js +1 -0
- package/apps/web/dist/assets/{phone-BrtQz-fG.js → phone-DtkY0NfU.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-BtEKL72n.js → settings-menu-7EJQRFOi.js} +1 -1
- package/apps/web/dist/assets/{ui-4IaTD7LB.js → ui-B6S5hYPd.js} +3 -3
- package/apps/web/dist/assets/{upload-CWJAtPRo.js → upload-rYCAy7ox.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/mcp/flow.html +3 -3
- package/bin/wizards.mjs +1 -1
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +21 -2
- package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +48 -48
- package/node_modules/@engenty-wizards/shared/dist/run.js +48 -48
- package/package.json +5 -4
- package/apps/web/dist/assets/AboutDialog-A_ILyeue.js +0 -1
- package/apps/web/dist/assets/SettingsPage-3xSInE-A.js +0 -1
- package/apps/web/dist/assets/changelog-CM_GdIVI.js +0 -1
- package/apps/web/dist/assets/oss-credits-BQQX0kvk.js +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Runners
|
|
3
|
-
description: A door a wizard runs through, beside the page and the chat
|
|
4
|
-
version: 0.
|
|
3
|
+
description: A door a wizard runs through, beside the page and the chat (a call, a messenger, a kiosk), and the run API that drives runs from it.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
A runner is a way to run a wizard. The runtime has four: the stepped page at `/w/<token>`, the
|
|
8
8
|
chat at `/w/<token>/chat`, live voice at `/w/<token>/talk` and the video call at
|
|
9
|
-
`/w/<token>/video
|
|
10
|
-
owner picks them per wizard when it is made, and later under
|
|
11
|
-
link opens the first page runner that is on
|
|
12
|
-
their own channel.
|
|
9
|
+
`/w/<token>/video`. The last two are there only where a speech model is set up. A plugin adds
|
|
10
|
+
more. The owner picks them per wizard when it is made, and later under “Change” in the share
|
|
11
|
+
dialog. The link opens the first page runner that is on; the others answer under their own
|
|
12
|
+
address or from their own channel.
|
|
13
13
|
|
|
14
14
|
## Register a runner
|
|
15
15
|
|
|
@@ -32,24 +32,25 @@ server.registerRunner({
|
|
|
32
32
|
|
|
33
33
|
| | |
|
|
34
34
|
|---|---|
|
|
35
|
-
| `id` | Lower case, digits and `-`, starting with a letter, at most 24 characters. The sub-path of the wizard
|
|
35
|
+
| `id` | Lower case, digits and `-`, starting with a letter, at most 24 characters. The sub-path of the wizard’s address. Not one of the built-in runners (`steps`, `chat`, `talk`, `video`), `runs` or `icons`: such an id throws “is taken” |
|
|
36
36
|
| `label` | `{ de, en }`: its name in German and English |
|
|
37
37
|
| `kind` | `page`: the person opens an address. `channel`: the runner has an address of its own (a number, a bot) and drives runs from its webhooks |
|
|
38
38
|
| `capabilities` | What the runner takes in and shows; see below |
|
|
39
|
-
| `page` | A page runner
|
|
40
|
-
| `problem` | `() => string \| { text, kind?, note?, plan? } \| null`, may be async: why the runner cannot be used in the current tenant right now (
|
|
41
|
-
| `address` | A channel runner: `(wizard: { id, title, token }) => { number?, keyword?, link? } \| null`, may be async. Where a person reaches that wizard: the number, the keyword that picks it there, and a link that opens the channel with the keyword written (`sms:…`, `https://wa.me/…`). The share dialog shows the keyword and number under the runner
|
|
39
|
+
| `page` | A page runner’s handler: what `/w/<token>/<id>` answers with. Nobody is signed in. A page runner without one throws when it is registered |
|
|
40
|
+
| `problem` | `() => string \| { text, kind?, note?, plan? } \| null`, may be async: why the runner cannot be used in the current tenant right now (“Not connected: Settings → WhatsApp”). The runtime asks it inside the tenant whenever the studio lists the runners, and a runner with a problem can’t be switched on. A text is something to set up: the share dialog’s row says “Not set up yet” and offers “Set up”. An object says its `kind`: `setup` (the same), `waiting` (asked for and waiting for someone else; the row says “Waiting” and has no button) or `plan` (the tenant’s plan does not have it; the row says “Not in your plan” and offers “See {plan}” with the `plan` it names, else “See the plans”). `note` (`{ de, en }`) is what the row says instead (“Number requested”, “From Pro”) |
|
|
41
|
+
| `address` | A channel runner: `(wizard: { id, title, token }) => { number?, keyword?, link? } \| null`, may be async. Where a person reaches that wizard: the number, the keyword that picks it there, and a link that opens the channel with the keyword written (`sms:…`, `https://wa.me/…`). The share dialog shows the keyword and number under the runner’s name, copies the link (else the number) and offers the link as a QR code. Asked for each wizard that has the runner on; null while it is not set up |
|
|
42
42
|
|
|
43
|
-
A page runner is served at `/w/<token>/<id>` for every wizard that switched it on.
|
|
44
|
-
source, where Vite serves the pages, the same handler answers at
|
|
45
|
-
`/api/public/wizards/<token>/runners/<id>`; the public wizard
|
|
43
|
+
A page runner is served at `/w/<token>/<id>` for every wizard that switched it on. When you run
|
|
44
|
+
from source, where Vite serves the pages, the same handler answers at
|
|
45
|
+
`/api/public/wizards/<token>/runners/<id>`; the public wizard’s `runners` list carries the
|
|
46
46
|
right address either way.
|
|
47
47
|
|
|
48
48
|
### Capabilities
|
|
49
49
|
|
|
50
|
-
The runner declares
|
|
51
|
-
everything in the channel,
|
|
52
|
-
|
|
50
|
+
The runner declares what it can do. The runtime works out per wizard how far it gets:
|
|
51
|
+
everything in the channel, a hand-off to the run page at some steps, or not possible. Where it is
|
|
52
|
+
not possible, the share dialog’s row says “Not possible:” with the steps. A kind the runtime does
|
|
53
|
+
not know throws when the runner is registered.
|
|
53
54
|
|
|
54
55
|
| Field | Says |
|
|
55
56
|
|---|---|
|
|
@@ -58,12 +59,12 @@ runtime does not know throws when the runner is registered.
|
|
|
58
59
|
| `output.pictures` | What it shows as a picture with a link to the real thing. Anything else is a link |
|
|
59
60
|
| `asks` | `confirm` for a running step that asks before it changes something; `login` only where a browser types the sign-in |
|
|
60
61
|
| `review` | `accept`, `regenerate`, `edit` |
|
|
61
|
-
| `waits` | Whether it stays through a step that takes minutes. A runner that
|
|
62
|
+
| `waits` | Whether it stays through a step that takes minutes. A runner that can’t is not possible for such wizards |
|
|
62
63
|
| `handoff` | How a link to the run page reaches the person: `screen`, `thread`, `sms`, `push`. A runner with none cannot hand off |
|
|
63
64
|
|
|
64
65
|
## Drive a run
|
|
65
66
|
|
|
66
|
-
`server.runs` holds the functions behind the MCP tools, for the plugin
|
|
67
|
+
`server.runs` holds the functions behind the MCP tools, for the plugin’s own door. A run
|
|
67
68
|
started here is a live run of the published wizard for a person of that door.
|
|
68
69
|
|
|
69
70
|
```ts
|
|
@@ -89,17 +90,17 @@ server.registerPublicRoute({
|
|
|
89
90
|
|
|
90
91
|
| Call | |
|
|
91
92
|
|---|---|
|
|
92
|
-
| `runs.start({ token \| wizardId, person, answers?, runner? })` | Starts a live run. `person` names who it is for on the door; the runtime keeps a hash of it as the run
|
|
93
|
-
| `runs.report(runId, { waitSeconds?, draft? })` | The run as it stands: `lang`, `waitingFor` (a page with the fields shown and what they know, a review, a question), outputs with signed links and a `picture` (a PNG of a document, a dashboard or a widget), cost, `browserUrl`. Waits up to 45 seconds while the run works. A door that asks a page one field at a time passes what it has as `draft`: a field
|
|
93
|
+
| `runs.start({ token \| wizardId, person, answers?, runner? })` | Starts a live run. `person` names who it is for on the door; the runtime keeps a hash of it as the run’s visitor, so what the wizard remembers is theirs on the run page too. `answers` fills the first page; what did not fit comes back as `refused`. Gives the run’s ticket |
|
|
94
|
+
| `runs.report(runId, { waitSeconds?, draft? })` | The run as it stands: `lang`, `waitingFor` (a page with the fields shown and what they know, a review, a question), outputs with signed links and a `picture` (a PNG of a document, a dashboard or a widget), cost, `browserUrl`. Waits up to 45 seconds while the run works. A door that asks a page one field at a time passes what it has as `draft`: a field’s `shown` then follows those answers, as the form on the screen does |
|
|
94
95
|
| `runs.wizards(runner)` | The published wizards of the tenant that switched the runner on: what the door offers, with `token`, `title` and `lang` |
|
|
95
96
|
| `runs.answerPage(runId, stepId, values)` | Fills the page the run waits for |
|
|
96
97
|
| `runs.review(runId, stepId, action)` | Accepts a review, or asks for one of the shown steps again with a note |
|
|
97
|
-
| `runs.answerAsk(runId, askId, "allow" \| "skip")` | Answers a running step
|
|
98
|
+
| `runs.answerAsk(runId, askId, "allow" \| "skip")` | Answers a running step’s question. A sign-in cannot be answered here: hand off |
|
|
98
99
|
| `runs.control(runId, "back" \| "retry" \| "cancel")` | |
|
|
99
100
|
| `runs.subscribe(runId, listener)` | Hears every change of the run: its events with their note in both languages, or a status change. Returns the function that stops it |
|
|
100
|
-
| `runs.handoffUrl(runId)` | The run page with the run
|
|
101
|
+
| `runs.handoffUrl(runId)` | The run page with the run’s ticket: the link to send for what the door cannot do. The person who opens it and the door’s person are one to the wizard |
|
|
101
102
|
| `runs.upload(runId, { data, mime, name })` | A file of the person; the asset id goes into an image or file field with `answerPage` |
|
|
102
|
-
| `runs.transcribe(runId, { data, mime })` | What is said in a recording of the person (a voice message), written down with the tenant
|
|
103
|
+
| `runs.transcribe(runId, { data, mime })` | What is said in a recording of the person (a voice message), written down with the tenant’s audio model and charged to the run. Up to 10 MB |
|
|
103
104
|
|
|
104
105
|
A refused command throws an error with a `status` and a `code` (`not_found`, `invalid`,
|
|
105
106
|
`refused`, `no_credits`); an `invalid` one also carries the fields that did not fit as
|
|
@@ -107,12 +108,12 @@ A refused command throws an error with a `status` and a `code` (`not_found`, `in
|
|
|
107
108
|
|
|
108
109
|
## What the person sees
|
|
109
110
|
|
|
110
|
-
- The wizard
|
|
111
|
+
- The wizard’s first page offers the runners that are on beside “Let’s go”.
|
|
111
112
|
- A hand-off is a link to the same run; nothing is asked twice. The run page knows the
|
|
112
|
-
door
|
|
113
|
-
- The space
|
|
113
|
+
door’s person through the run, and “What this wizard remembers” is theirs there too.
|
|
114
|
+
- The space’s results carry the door a run came through.
|
|
114
115
|
|
|
115
|
-
## The runner
|
|
116
|
+
## The runner’s part of the share dialog
|
|
116
117
|
|
|
117
118
|
The row of a channel runner, with its number, keyword, link and QR code, comes from its
|
|
118
119
|
`address`. What the owner changes per wizard, such as the keyword, the studio half registers as
|
|
@@ -132,8 +133,8 @@ go into a [settings section](./studio.md).
|
|
|
132
133
|
|
|
133
134
|
### Setting up inside the dialog
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
“Set up” in a row opens the plugin’s settings section, unless the studio half registers a
|
|
137
|
+
setup. That setup opens inside the dialog instead, as a view with a back arrow, at the dialog’s
|
|
137
138
|
height:
|
|
138
139
|
|
|
139
140
|
```tsx
|
|
@@ -149,21 +150,21 @@ function Setup({ wizard, Actions, done }: StudioShareSetupProps) {
|
|
|
149
150
|
<>
|
|
150
151
|
<p>…</p>
|
|
151
152
|
<Actions>
|
|
152
|
-
<Button onClick={() => ask.mutate()}>
|
|
153
|
+
<Button onClick={() => ask.mutate()}>{t("request")}</Button>
|
|
153
154
|
</Actions>
|
|
154
155
|
</>
|
|
155
156
|
);
|
|
156
|
-
// done({ title: "
|
|
157
|
+
// done({ title: t("requested"), text: "…", tone: "waiting" }) goes back.
|
|
157
158
|
}
|
|
158
159
|
```
|
|
159
160
|
|
|
160
161
|
| | |
|
|
161
162
|
|---|---|
|
|
162
|
-
| `title` | The view
|
|
163
|
-
| `component` | Gets `{ wizard: { id, title }, Actions, done }`. What it renders inside `Actions` goes to the foot of the dialog, where the dialog
|
|
164
|
-
| `notice` | Optional: a banner above the rows while the runner has news (
|
|
163
|
+
| `title` | The view’s heading, a function: the language can change |
|
|
164
|
+
| `component` | Gets `{ wizard: { id, title }, Actions, done }`. What it renders inside `Actions` goes to the foot of the dialog, where the dialog’s own buttons are. `done(banner?)` goes back to the main view and asks the rows again; `banner` (`{ title, text?, tone? }`, `tone` `done` green or `waiting` plain) shows on top |
|
|
165
|
+
| `notice` | Optional: a banner above the rows while the runner has news (“Your number is here”), drawn by the plugin. It draws nothing while there is no news. It gets `{ wizard, open }`; `open()` opens the setup. The dialog shows the banner of the first runner that is on |
|
|
165
166
|
|
|
166
|
-
One setup per runner. A setup that asks the server for something calls the plugin
|
|
167
|
+
One setup per runner. A setup that asks the server for something calls the plugin’s own routes;
|
|
167
168
|
the rows show what `problem` answers after `done`.
|
|
168
169
|
|
|
169
170
|
## A thread door from the SDK
|
|
@@ -180,7 +181,7 @@ const surface: ThreadSurface = {
|
|
|
180
181
|
runner: "sms",
|
|
181
182
|
asks: ["text", "number", "select", "multiselect", "toggle", "date", "email", "url", "location"],
|
|
182
183
|
async send(thread, prompt) {
|
|
183
|
-
// prompt.kind: "text" | "choice" | "location" | "link" | "media"
|
|
184
|
+
// prompt.kind: "text" | "choice" | "location" | "link" | "media": draw it as the channel allows.
|
|
184
185
|
await twilio.send(thread.id, toText(prompt, thread.state.lang));
|
|
185
186
|
},
|
|
186
187
|
};
|
|
@@ -194,20 +195,24 @@ const door = new ThreadDoor({ runs: server.runs, surface, offered, watch, unwatc
|
|
|
194
195
|
| `ThreadSurface.asks` | The field kinds the thread asks itself; a page with any other field goes to the screen as a link |
|
|
195
196
|
| `send(thread, prompt)` | A `choice` is drawn as buttons, a list or numbered lines; a typed number or title counts as a tap, the door works that out. `link` and `media` carry a URL |
|
|
196
197
|
| `media(ref)` | A file the person sent, by what the webhook named; without it files hand off |
|
|
197
|
-
| `mayDeliver`, `reach` | A messenger with a window (WhatsApp
|
|
198
|
+
| `mayDeliver`, `reach` | A messenger with a window (WhatsApp’s 24 hours): whether a free message may go out now, and the link by other means when not |
|
|
198
199
|
| `offered()` | The wizards switched on for the runner (`runs.wizards`) with their keywords; a keyword in the thread starts its wizard, else the door offers the menu |
|
|
199
200
|
| `watch(thread)`, `unwatch(runId)` | Hear the run (`runs.subscribe`) and call `door.resume` when it moves; the plugin keeps the subscriptions |
|
|
200
201
|
| `Thread` | `{ id, name, runId, state, lastInboundAt, lastMessageId }`: the plugin keeps it in a table per person and hands it to the door before and after |
|
|
201
202
|
|
|
203
|
+
Once a wizard runs, the door speaks the wizard’s language. Before that, it speaks the language
|
|
204
|
+
all offered wizards share; when they differ, it keeps the language it spoke last, which is
|
|
205
|
+
English in a new thread.
|
|
206
|
+
|
|
202
207
|
A messenger plugin is a surface of this door: the webhook on a public route, the keyword table,
|
|
203
|
-
the number and the keyword in the share dialog are the plugin
|
|
208
|
+
the number and the keyword in the share dialog are the plugin’s; buttons and lists, or numbered
|
|
204
209
|
lines and links, are its surface.
|
|
205
210
|
|
|
206
211
|
## A call
|
|
207
212
|
|
|
208
|
-
A call has no thread and no screen: the person
|
|
209
|
-
model fills the run through tools, as the chat
|
|
210
|
-
hands a plugin the tenant
|
|
213
|
+
A call has no thread and no screen: the person talks with a realtime speech model, and the
|
|
214
|
+
model fills the run through tools, as the chat’s live voice does in the browser. The runtime
|
|
215
|
+
hands a plugin the tenant’s speech model for it:
|
|
211
216
|
|
|
212
217
|
```ts
|
|
213
218
|
const { apiKey, model } = await server.speech.conversation();
|
|
@@ -215,9 +220,9 @@ const { apiKey, model } = await server.speech.conversation();
|
|
|
215
220
|
```
|
|
216
221
|
|
|
217
222
|
A call plugin keeps the audio out of the runtime: the telephony provider hands the call to the
|
|
218
|
-
speech model
|
|
219
|
-
call to the plugin
|
|
220
|
-
and answers the model
|
|
223
|
+
speech model’s SIP endpoint with a token in a header, the model’s provider posts the incoming
|
|
224
|
+
call to the plugin’s public route, and the plugin accepts it with the wizard’s rules and tools
|
|
225
|
+
and answers the model’s tool calls over a WebSocket, driving the run with `server.runs`. What a
|
|
221
226
|
voice cannot take goes out as a link.
|
|
222
227
|
|
|
223
228
|
## Not there yet
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The server half
|
|
3
3
|
description: The function a plugin exports, what it is handed, and the rules it runs under.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
The server half is a file whose default export is a function. The runtime calls it once when the
|
|
@@ -29,7 +29,8 @@ export default definePlugin((wizards) => {
|
|
|
29
29
|
| `wizards.server` | Registers tools, routes, tables and listeners. See the table below |
|
|
30
30
|
| `wizards.plugin` | The manifest: `id`, `name`, `version` and whatever else it holds |
|
|
31
31
|
| `wizards.config.get(key)` | A setting of the install: a line of its `.env`. Empty and missing are both `undefined` |
|
|
32
|
-
| `wizards.
|
|
32
|
+
| `wizards.features.has(name)`, `.all()` | The feature switches of the tenant at hand. See [Feature switches](#feature-switches) |
|
|
33
|
+
| `wizards.log.info`, `.warn`, `.error` | A log line with the plugin’s name in front |
|
|
33
34
|
| `wizards.resolvePath(relative)` | A file of the plugin as an absolute path |
|
|
34
35
|
|
|
35
36
|
| `wizards.server.` | Does | Page |
|
|
@@ -37,32 +38,33 @@ export default definePlugin((wizards) => {
|
|
|
37
38
|
| `registerTool(tool)` | A tool for AI steps | [Tools](./tools.md) |
|
|
38
39
|
| `registerSpaceContext(context)` | A block and tools for every AI step of a space | [The space](./space.md) |
|
|
39
40
|
| `registerAssistantTool(tool)` | A tool of the space assistant | [The space](./space.md#tools-of-the-space-assistant) |
|
|
40
|
-
| `registerAssistantContext(context)` | What the space assistant is told on the plugin
|
|
41
|
-
| `spaceData.putPage(input)`, `putTable`, `putFile`, `putDocument`, `putCategories`, … | Knowledge of a space: pages, tables, files and
|
|
42
|
-
| `spaces.brand(space)` | The space
|
|
43
|
-
| `index.put(entry)`, `index.remove(space, key?)` | Texts in the space
|
|
41
|
+
| `registerAssistantContext(context)` | What the space assistant is told on the plugin’s own page | [The space](./space.md#the-assistant-on-the-plugins-own-page) |
|
|
42
|
+
| `spaceData.putPage(input)`, `putTable`, `putFile`, `putDocument`, `putCategories`, … | Knowledge of a space: pages, tables, files and categories | [The space](./space.md#knowledge-a-plugin-writes) |
|
|
43
|
+
| `spaces.brand(space)` | The space’s look: its title, colours and fonts | [The space](./space.md#the-spaces-brand) |
|
|
44
|
+
| `index.put(entry)`, `index.remove(space, key?)` | Texts in the space’s search index | [The space](./space.md#the-search-index) |
|
|
44
45
|
| `registerHttpRoute(route)` | A route below `/api/studio/plugins/<id>` | [Routes](./routes.md) |
|
|
45
46
|
| `registerPublicRoute(route)`, `publicUrl(path)` | A route anyone may call, and its address | [Routes](./routes.md#public-routes) |
|
|
46
47
|
| `registerSharedRoute(route)`, `sharedUrl(path)`, `publicRef()` | One route for every tenant, and the ref that names one | [Routes](./routes.md#shared-routes) |
|
|
47
48
|
| `channels.get()`, `request`, `withdraw`, `book` | The numbers the Manage-App gives the tenant | below |
|
|
49
|
+
| `claims.take(key)`, `put`, `drop`, `holder`, `mine` | Keys held across tenants, one tenant each | below |
|
|
48
50
|
| `registerRunner(runner)` | A door a wizard runs through, beside the page and the chat | [Runners](./runners.md) |
|
|
49
|
-
| `runs.start(…)`, `report`, `answerPage`, … | Drives runs from the plugin
|
|
50
|
-
| `speech.conversation()` | The tenant
|
|
51
|
-
| `registerMigrations(folder)` | `.sql` files with the plugin
|
|
51
|
+
| `runs.start(…)`, `report`, `answerPage`, … | Drives runs from the plugin’s own door | [Runners](./runners.md#drive-a-run) |
|
|
52
|
+
| `speech.conversation()` | The tenant’s realtime speech model, for a call | [Runners](./runners.md#a-call) |
|
|
53
|
+
| `registerMigrations(folder)` | `.sql` files with the plugin’s tables | [Tables](./tables.md) |
|
|
52
54
|
| `on(event, listener)` | Called when a run ends or a space changes | [Events](./events.md) |
|
|
53
55
|
| `connections.available(…)`, `start`, `connect`, `list`, `call`, … | Accounts of a space, through the built-in connectors | [Accounts](./connections.md) |
|
|
54
56
|
| `registerStarter(starter)` | A wizard the plugin brings as a template | [Starters](./starters.md) |
|
|
55
57
|
| `every(name, everyMs, handler)` | A job, again and again | below |
|
|
56
58
|
| `web.fetch(url, init?)`, `web.read(url)` | Requests to the web, guarded | below |
|
|
57
59
|
| `documents.parse(file)` | A file as Markdown | below |
|
|
58
|
-
| `secrets.seal(value)`, `secrets.unseal(sealed)` | Keys of the plugin
|
|
60
|
+
| `secrets.seal(value)`, `secrets.unseal(sealed)` | Keys of the plugin’s own, sealed for a table | below |
|
|
59
61
|
| `getTenantDb()` | The database of the tenant the current request, step or event belongs to | [Tables](./tables.md) |
|
|
60
62
|
| `generate(request)` | Asks a model of the current tenant | below |
|
|
61
63
|
| `onUnload(fn)` | Something to undo when the plugin unloads | below |
|
|
62
64
|
|
|
63
65
|
## Settings
|
|
64
66
|
|
|
65
|
-
Name a plugin
|
|
67
|
+
Name a plugin’s settings after the plugin and read them when the plugin loads:
|
|
66
68
|
|
|
67
69
|
```ts
|
|
68
70
|
const url = wizards.config.get("NOTIFY_URL");
|
|
@@ -72,17 +74,35 @@ if (!url) {
|
|
|
72
74
|
}
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
The person sets them in the install
|
|
76
|
-
|
|
77
|
-
values in a [table](./tables.md).
|
|
77
|
+
The person sets them in the install’s `.env` and restarts engenty wizards. A plugin doesn’t get
|
|
78
|
+
a settings page for free: a plugin that wants one adds a [settings section](./studio.md) and
|
|
79
|
+
keeps the values in a [table](./tables.md).
|
|
80
|
+
|
|
81
|
+
## Feature switches
|
|
82
|
+
|
|
83
|
+
A plugin declares its own switches in its manifest under `features`
|
|
84
|
+
([Files and manifest](./layout.md#the-manifest)). Everywhere else each one is known as
|
|
85
|
+
`<plugin id>.<name>`: in `wizards features`, in the install’s `.features` file and in the
|
|
86
|
+
`FEATURES` setting.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
if (await wizards.features.has("leadScoring")) {
|
|
90
|
+
server.registerTool(scoreLead);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `has(name)` with a short name asks for the plugin’s own switch (`contacts.leadScoring`); a
|
|
95
|
+
full name asks for any switch, the runtime’s own included.
|
|
96
|
+
- `all()` gives every switch of the tenant at hand, by its full name.
|
|
97
|
+
- A switch is off unless something turned it on.
|
|
78
98
|
|
|
79
99
|
## Tenants
|
|
80
100
|
|
|
81
|
-
A runtime serves one tenant on a person
|
|
82
|
-
|
|
101
|
+
A runtime serves one tenant on a person’s computer and many on engenty.ai. Either way, a plugin
|
|
102
|
+
never names a tenant:
|
|
83
103
|
|
|
84
104
|
- A route, a tool and a listener each run inside one tenant.
|
|
85
|
-
- `server.getTenantDb()` is that tenant
|
|
105
|
+
- `server.getTenantDb()` is that tenant’s database. Call it where you use it, not once at load
|
|
86
106
|
time: at load time there is no tenant.
|
|
87
107
|
- State kept in a variable of the module is shared by all tenants. Keep per-tenant state in a
|
|
88
108
|
table.
|
|
@@ -91,7 +111,7 @@ Either way a plugin never names a tenant:
|
|
|
91
111
|
|
|
92
112
|
On engenty.ai a team gets its number for texts and calls from engenty: it asks for one, an
|
|
93
113
|
admin assigns it. `server.channels` reads it for the current tenant and books what it carries;
|
|
94
|
-
the prices are the Manage-App
|
|
114
|
+
the prices are the Manage-App’s ([manage contract](https://github.com/engenty/wizards/blob/main/docs/manage-contract.md#numbers-of-a-tenant)).
|
|
95
115
|
|
|
96
116
|
```ts
|
|
97
117
|
const channels = await server.channels.get();
|
|
@@ -106,15 +126,46 @@ await server.channels.book({ kind: "sms", quantity: segments, key: messageSid, r
|
|
|
106
126
|
|
|
107
127
|
- `get` is kept for a minute; `request` and `withdraw` forget it.
|
|
108
128
|
- `request` throws `403` / `plan` on a plan without a number and `409` / `has_number` when
|
|
109
|
-
the team has one. Asking again
|
|
110
|
-
- `book`: `sms` by segment, sent or received, `call` by started minute
|
|
111
|
-
once.
|
|
129
|
+
the team has one. Asking again gives the open request.
|
|
130
|
+
- `book`: `sms` by segment, sent or received, `call` by started minute, `whatsapp` by
|
|
131
|
+
message sent. The same `key` books once.
|
|
112
132
|
- A runtime without a Manage-App has no numbers: `get` gives `null`, `request` and `withdraw`
|
|
113
133
|
throw `404` / `not_managed`, `book` books nothing.
|
|
114
134
|
|
|
135
|
+
## Keys across tenants
|
|
136
|
+
|
|
137
|
+
What a [shared route](./routes.md#shared-routes) needs to find its tenant: a keyword on a number
|
|
138
|
+
every tenant shares, who last wrote to it, a number a tenant connected. `server.claims` holds
|
|
139
|
+
such keys, each for one tenant at a time, in the runtime’s own database:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// In the tenant: the keyword is this tenant's, unless another one has it.
|
|
143
|
+
if (!(await server.claims.take(`kw:${keyword.toLowerCase()}`))) {
|
|
144
|
+
throw Object.assign(new Error("Taken."), { status: 409, code: "keyword_taken" });
|
|
145
|
+
}
|
|
146
|
+
await server.claims.put(`sender:${waId}`); // the last tenant they wrote to has them
|
|
147
|
+
|
|
148
|
+
// In the shared route: whose is it?
|
|
149
|
+
const ref = await server.claims.holder(`kw:${text.toLowerCase()}`);
|
|
150
|
+
if (ref) {
|
|
151
|
+
await inTenant(ref, () => handle(message));
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| | |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `take(key)` | The current tenant takes the key; `false` when another tenant holds it |
|
|
158
|
+
| `put(key)` | The current tenant gets the key, whoever held it |
|
|
159
|
+
| `drop(key)` | The current tenant gives the key up; a key another tenant holds stays |
|
|
160
|
+
| `holder(key)` | The ref of the tenant that holds the key, for `inTenant`; `null` for none. Also outside a tenant |
|
|
161
|
+
| `mine(key)` | Whether the current tenant holds the key |
|
|
162
|
+
|
|
163
|
+
Keys are 1 to 200 characters and the plugin’s own: another plugin’s key of the same name is
|
|
164
|
+
another key. They stay when a tenant loses the plugin; `inTenant` then answers `404`.
|
|
165
|
+
|
|
115
166
|
## Asking a model
|
|
116
167
|
|
|
117
|
-
`server.generate` asks one of the tenant
|
|
168
|
+
`server.generate` asks one of the tenant’s models, the same ones its wizards run on, and is paid
|
|
118
169
|
like any other of its calls. With a `schema` the answer is an object of that shape:
|
|
119
170
|
|
|
120
171
|
```ts
|
|
@@ -132,13 +183,13 @@ const { object } = await server.generate({
|
|
|
132
183
|
| `system` | How it should answer | |
|
|
133
184
|
| `schema` | A zod schema: `object` is the answer in that shape | text only |
|
|
134
185
|
| `model` | `classifier` (fast, cheap), `standard`, `high`, `highest` | `standard` |
|
|
135
|
-
| `maxOutputTokens` | A limit on the answer | the model
|
|
186
|
+
| `maxOutputTokens` | A limit on the answer | the model’s |
|
|
136
187
|
| `signal` | Aborts the call | two minutes |
|
|
137
188
|
| `files` | Pictures (PNG, JPEG, WebP, GIF) and PDFs the model looks at with the prompt: `{ data, mediaType, name? }[]`, `data` a `Uint8Array` | none |
|
|
138
189
|
|
|
139
190
|
It answers `{ text, object }`. Without a model set up it throws an error with the status `503`
|
|
140
191
|
and the code `no_model`: a route that lets it through answers just that, and the studio half
|
|
141
|
-
says it in the person
|
|
192
|
+
says it in the person’s language.
|
|
142
193
|
|
|
143
194
|
## Jobs
|
|
144
195
|
|
|
@@ -151,11 +202,11 @@ server.every("refresh", 60 * 60_000, async ({ tenantId, lastRun, signal }) => {
|
|
|
151
202
|
```
|
|
152
203
|
|
|
153
204
|
- The handler runs for every tenant that has the plugin, inside that tenant, at most once per
|
|
154
|
-
`everyMs`.
|
|
155
|
-
- A job
|
|
156
|
-
-
|
|
157
|
-
due job once when it starts, not once per turn it missed.
|
|
158
|
-
runs.
|
|
205
|
+
`everyMs`. The shortest interval is a minute.
|
|
206
|
+
- A job’s name is lower case, digits and `_`, once per plugin.
|
|
207
|
+
- The runtime keeps when a job ran, so a restart does not start the count again. A runtime that
|
|
208
|
+
was off runs a due job once when it starts, not once per turn it missed. On a person’s computer,
|
|
209
|
+
jobs run while engenty wizards runs.
|
|
159
210
|
- One turn per tenant at a time. A turn that throws is logged and counts as run.
|
|
160
211
|
- `lastRun` is when the turn before started; `null` the first time. `signal` is aborted
|
|
161
212
|
when the plugin unloads.
|
|
@@ -172,20 +223,20 @@ const res = await server.web.fetch(`${origin}/sitemap.xml`, {
|
|
|
172
223
|
});
|
|
173
224
|
```
|
|
174
225
|
|
|
175
|
-
- Both refuse addresses of
|
|
176
|
-
runtime
|
|
177
|
-
- `read` gives the page
|
|
226
|
+
- Both refuse addresses of the computer the runtime runs on and of its local network, after a
|
|
227
|
+
redirect too, as the runtime’s own requests do.
|
|
228
|
+
- `read` gives the page’s text as Markdown and its links, absolute and each once (up to
|
|
178
229
|
2,000). A page with hardly any text without its scripts is drawn in a browser first. `etag`
|
|
179
230
|
and `lastModified` are what the server sent, for asking later whether the page changed. `jsonld`
|
|
180
|
-
is the page
|
|
231
|
+
is the page’s structured data (its `application/ld+json` blocks, parsed): a shop’s product
|
|
181
232
|
page says title, price, availability and image there exactly, without a model.
|
|
182
233
|
- `fetch` is `fetch` behind the guard: for a sitemap, `robots.txt`, a question with
|
|
183
234
|
`if-none-match`.
|
|
184
235
|
|
|
185
|
-
## Keys of the plugin
|
|
236
|
+
## Keys of the plugin’s own
|
|
186
237
|
|
|
187
238
|
A plugin that talks to a shop or a service with keys the person typed keeps them sealed, as the
|
|
188
|
-
runtime seals a connection
|
|
239
|
+
runtime seals a connection’s tokens:
|
|
189
240
|
|
|
190
241
|
```ts
|
|
191
242
|
await db().update(store).set({ credentials: server.secrets.seal({ token }) });
|
|
@@ -200,8 +251,9 @@ const keys = server.secrets.unseal<{ token: string }>(row.credentials);
|
|
|
200
251
|
const { markdown, pages } = await server.documents.parse({ data, name: "preise.pdf", mime });
|
|
201
252
|
```
|
|
202
253
|
|
|
203
|
-
PDF and Word files by their own text, scans and photos by a vision model,
|
|
204
|
-
tables. What was read is kept by content
|
|
254
|
+
PDF and Word files are read by their own text, scans and photos by a vision model, and
|
|
255
|
+
spreadsheets and CSV files as tables. What was read is kept by content, so the same bytes are
|
|
256
|
+
read, and paid for, only once.
|
|
205
257
|
|
|
206
258
|
## Undo on unload
|
|
207
259
|
|
|
@@ -217,13 +269,13 @@ The functions run last-first when the plugin unloads or loads again.
|
|
|
217
269
|
|
|
218
270
|
## When loading fails
|
|
219
271
|
|
|
220
|
-
A plugin whose function throws keeps nothing of what it registered before the error
|
|
221
|
-
plugins. Settings → Plugins shows the error, and the plugin
|
|
222
|
-
|
|
272
|
+
A plugin whose function throws keeps nothing of what it registered before the error, so there
|
|
273
|
+
are no half-loaded plugins. Settings → Plugins shows the error, and the plugin’s routes answer
|
|
274
|
+
`404` until you fix its file.
|
|
223
275
|
|
|
224
276
|
## Imports
|
|
225
277
|
|
|
226
278
|
`@engenty-wizards/plugin-sdk` (and `@engenty-wizards/plugin-sdk/thread`), `zod`, `drizzle-orm` and
|
|
227
|
-
`drizzle-orm/sqlite-core` come from the
|
|
228
|
-
|
|
229
|
-
|
|
279
|
+
`drizzle-orm/sqlite-core` come from the runtime’s own copies, so a schema or a table a plugin
|
|
280
|
+
makes is one the runtime understands. See [Files and manifest](./layout.md#dependencies) for
|
|
281
|
+
everything else.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Shipping
|
|
3
3
|
description: Hand a plugin to someone else, put it into a server's image, and decide which tenant has it.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## To someone
|
|
7
|
+
## To someone’s install
|
|
8
8
|
|
|
9
|
-
A plugin is a file or a folder. Whoever installs it puts it into their plugins folder
|
|
9
|
+
A plugin is a file or a folder. Whoever installs it puts it into their plugins folder, which is
|
|
10
10
|
`~/.engenty/wizards/plugins` for an installed app.
|
|
11
11
|
|
|
12
12
|
Give them:
|
|
@@ -18,7 +18,7 @@ Give them:
|
|
|
18
18
|
| Dependencies | A `node_modules` beside the plugin, if it imports anything but the SDK, `zod` and `drizzle-orm` |
|
|
19
19
|
| Settings | The names of the `.env` lines it reads, in its description |
|
|
20
20
|
|
|
21
|
-
Say what
|
|
21
|
+
Say what the plugin does with data. A plugin runs with the app’s full rights, and the person who
|
|
22
22
|
installs it trusts you with them.
|
|
23
23
|
|
|
24
24
|
## As a package
|
|
@@ -31,8 +31,8 @@ npm install @acme/wizards-contacts
|
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
The runtime finds `node_modules/@acme/wizards-contacts/engenty.plugin.json` at its next start,
|
|
34
|
-
or with
|
|
35
|
-
`dist/` into the package
|
|
34
|
+
or with “Look again” in Settings → Plugins. Put the manifest, the server half and the built
|
|
35
|
+
`dist/` into the package’s `files`.
|
|
36
36
|
|
|
37
37
|
## Into a server
|
|
38
38
|
|
|
@@ -47,17 +47,20 @@ services:
|
|
|
47
47
|
- ./plugins:/plugins
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Or copy the plugins into an image of your own, built on the runtime
|
|
50
|
+
Or copy the plugins into an image of your own, built on the runtime’s.
|
|
51
51
|
|
|
52
52
|
## With engenty wizards itself
|
|
53
53
|
|
|
54
|
-
Plugins in the repository
|
|
55
|
-
installed app and in the Docker image. A module there is part of the open
|
|
54
|
+
Plugins in the repository’s `modules/` folder ship with every runtime: in a checkout, in the
|
|
55
|
+
npm package and installed app, and in the Docker image. A module there is part of the open
|
|
56
|
+
product.
|
|
56
57
|
|
|
57
|
-
- Its server half imports only what the runtime hands every plugin and its own files. It has no
|
|
58
|
+
- Its server half imports only what the runtime hands every plugin, and its own files. It has no
|
|
58
59
|
`node_modules` of its own where it is installed.
|
|
59
60
|
- A module with a studio half is a workspace package whose `build` script is
|
|
60
|
-
`node ../../scripts/build-plugin.mjs .`, so the product
|
|
61
|
+
`node ../../scripts/build-plugin.mjs .`, so the product’s build builds its `dist/`.
|
|
62
|
+
`apps/runtime/Dockerfile` copies each workspace package’s `package.json` before it installs:
|
|
63
|
+
add the module’s there.
|
|
61
64
|
|
|
62
65
|
A plugin that is not part of the open product stays out of `modules/` and goes into a folder of
|
|
63
66
|
`PLUGINS_DIR`.
|
|
@@ -66,11 +69,11 @@ A plugin that is not part of the open product stays out of `modules/` and goes i
|
|
|
66
69
|
|
|
67
70
|
On an install of your own, on your computer or your own server, every tenant has every plugin
|
|
68
71
|
the runtime loaded. On engenty.ai the service installs the plugins and switches them on per
|
|
69
|
-
account
|
|
72
|
+
account. A person cannot install a plugin there.
|
|
70
73
|
|
|
71
74
|
## Versions
|
|
72
75
|
|
|
73
|
-
- `version`
|
|
74
|
-
- A plugin
|
|
75
|
-
- A wizard that uses a plugin
|
|
76
|
-
wizards
|
|
76
|
+
- The settings show `version` from the manifest. The runtime doesn’t compare versions.
|
|
77
|
+
- A plugin’s `.sql` files only ever grow: an update adds files and never changes one that ran.
|
|
78
|
+
- A wizard that uses a plugin’s tool names it by its id. Renaming a tool or the plugin breaks
|
|
79
|
+
those wizards, so keep ids for good.
|