@engenty/wizards-runtime 0.7.6 → 0.7.7
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/apps/runtime/dist/authoring/guide.js +5 -1
- package/apps/runtime/dist/docs/dev/index.md +6 -1
- package/apps/runtime/dist/docs/dev/integration/channels.md +11 -0
- package/apps/runtime/dist/docs/dev/integration/claude-code-plugin.md +10 -0
- package/apps/runtime/dist/docs/dev/integration/embed.md +10 -0
- package/apps/runtime/dist/docs/dev/integration/index.md +36 -0
- package/apps/runtime/dist/docs/dev/integration/link-and-share.md +10 -0
- package/apps/runtime/dist/docs/dev/integration/mcp-apps.md +10 -0
- package/apps/runtime/dist/docs/dev/integration/mcp.md +10 -0
- package/apps/runtime/dist/docs/dev/integration/meta.json +20 -0
- package/apps/runtime/dist/docs/dev/integration/openui.md +181 -0
- package/apps/runtime/dist/docs/dev/integration/own-ui.md +10 -0
- package/apps/runtime/dist/docs/dev/integration/results.md +11 -0
- package/apps/runtime/dist/docs/dev/integration/wordpress.md +10 -0
- package/apps/runtime/dist/docs/dev/meta.json +1 -1
- package/apps/runtime/dist/engine/steps.js +2 -1
- package/apps/runtime/dist/routes/runs.js +14 -1
- package/apps/runtime/dist/tools/code.js +133 -0
- package/apps/runtime/dist/tools/connector.js +6 -0
- package/apps/runtime/dist/tools/index.js +14 -3
- package/apps/web/dist/assets/{AboutDialog-DYgXPagX.js → AboutDialog-BP8-Ugxf.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-BXqBW8j3.js → ChangelogDialog-CD6CQNW9.js} +1 -1
- package/apps/web/dist/assets/{ChatView-CK2--tsE.js → ChatView-4T0QarEr.js} +1 -1
- package/apps/web/dist/assets/{EditorPage-jvcwyXbU.js → EditorPage-CWdV7Xdn.js} +3 -3
- package/apps/web/dist/assets/{FlowPreview-CusP7ci9.js → FlowPreview-Dj2gAfuf.js} +1 -1
- package/apps/web/dist/assets/{Harness-CV9uYzbv.js → Harness-SLbsntQF.js} +1 -1
- package/apps/web/dist/assets/{NewWizardChannels-BakW5q6s.js → NewWizardChannels-CsDMlji7.js} +1 -1
- package/apps/web/dist/assets/{NewWizardPage-BoA-LJuW.js → NewWizardPage-_bFYjlgh.js} +1 -1
- package/apps/web/dist/assets/{OnboardingPage-DaD1XnOe.js → OnboardingPage-DObQeCDg.js} +1 -1
- package/apps/web/dist/assets/{ProjectPage-BFgk1DEv.js → ProjectPage-DX-oNLWy.js} +1 -1
- package/apps/web/dist/assets/{PublicRunner-C9MwEvxy.js → PublicRunner-Bn9rVNEb.js} +1 -1
- package/apps/web/dist/assets/{ReadOnly-DC2Kp7l8.js → ReadOnly-CK0piLUV.js} +1 -1
- package/apps/web/dist/assets/{RunDrawer-CY6NZSrI.js → RunDrawer-CVMTLse_.js} +1 -1
- package/apps/web/dist/assets/{RunnerView-BT26XgcY.js → RunnerView-Cjhuu9ua.js} +1 -1
- package/apps/web/dist/assets/{SettingsPage-AFt9IDLt.js → SettingsPage-DTzuiSAj.js} +1 -1
- package/apps/web/dist/assets/{SetupPage-DtFYAbwJ.js → SetupPage-D9jlhgiP.js} +1 -1
- package/apps/web/dist/assets/{SharePage-t2xwNS61.js → SharePage-Cc3Opj2F.js} +1 -1
- package/apps/web/dist/assets/changelog-CJNkd1_L.js +1 -0
- package/apps/web/dist/assets/{esm-DXeMu8Lb.js → esm-CfnFE_jE.js} +1 -1
- package/apps/web/dist/assets/{index-Drw7FMTQ.js → index-B5F1_qA_.js} +4 -4
- package/apps/web/dist/assets/{lock-R_CfU0Xo.js → lock-DAvRJY4B.js} +1 -1
- package/apps/web/dist/assets/oss-credits-X2JPiMOR.js +1 -0
- package/apps/web/dist/assets/{phone-CgrIS4_-.js → phone-BWOB67tX.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-2IMOOlLl.js → settings-menu-CEQlVs1O.js} +1 -1
- package/apps/web/dist/assets/{ui-dzGvc3xk.js → ui-BmR_4eL2.js} +1 -1
- package/apps/web/dist/assets/{upload-BSuTilFF.js → upload-pBKn2EFp.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/mcp/flow.html +2 -2
- package/node_modules/@engenty/wizards/dist/openui/Wizard.d.ts +20 -0
- package/node_modules/@engenty/wizards/dist/openui/Wizard.js +62 -0
- package/node_modules/@engenty/wizards/dist/openui/index.d.ts +9 -0
- package/node_modules/@engenty/wizards/dist/openui/index.js +9 -0
- package/node_modules/@engenty/wizards/dist/openui/message.d.ts +11 -0
- package/node_modules/@engenty/wizards/dist/openui/message.js +69 -0
- package/node_modules/@engenty/wizards/dist/openui/run.d.ts +8 -0
- package/node_modules/@engenty/wizards/dist/openui/run.js +137 -0
- package/node_modules/@engenty/wizards/dist/openui/server.d.ts +37 -0
- package/node_modules/@engenty/wizards/dist/openui/server.js +77 -0
- package/node_modules/@engenty/wizards/package.json +4 -1
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +4 -1
- package/node_modules/@engenty-wizards/shared/dist/definition.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/fit.js +7 -2
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +2 -0
- package/package.json +4 -3
- package/apps/web/dist/assets/changelog-CATQFxCQ.js +0 -1
- package/apps/web/dist/assets/oss-credits-cZ9NUaLZ.js +0 -1
|
@@ -133,7 +133,7 @@ type AgentStep = {
|
|
|
133
133
|
// (fields name the keys later steps read). Use it where a model would only fetch and copy
|
|
134
134
|
// data (free appointment times) — it answers in a second instead of half a minute.
|
|
135
135
|
// String inputs are templates; a filled-in whole number or true/false is passed as one.
|
|
136
|
-
tools: ("web_search"|"web_fetch"|"browser"|"sandbox"|"image"|"http"|"pages")[]
|
|
136
|
+
tools: ("web_search"|"web_fetch"|"browser"|"sandbox"|"image"|"http"|"pages"|"code")[]
|
|
137
137
|
mcp?: string[] // ids of the project's MCP servers this step may use
|
|
138
138
|
connections?: string[] // ids of wizard connections this step may read (mail → mail_search, mail_read, mail_save)
|
|
139
139
|
output: { format: "text"|"markdown"|"json", fields?: { id, kind: "text"|"number"|"list"|"table"|"yesno"|"choice"|"score", description, columns?: string[], options?: string[] }[], schema?: Schema, surface?: SurfaceComponent[] | true }
|
|
@@ -156,6 +156,10 @@ type AgentStep = {
|
|
|
156
156
|
// project_list (by categories), page_read / table_read / project_document (an item whole, by its path).
|
|
157
157
|
// "pages" brings: page_read / page_write — Markdown pages of the space the wizard keeps for every run (notes, a log
|
|
158
158
|
// that grows with append), shown to the admin under Space → Data; page_read also reads Knowledge by path.
|
|
159
|
+
// "code" brings: execute_typescript — the model writes one program that calls the step's other tools (not the browser) as
|
|
160
|
+
// functions: loops, Promise.all, filtering, sums; only its answer reaches the model. For many calls of one kind (fetch 30
|
|
161
|
+
// pages, read every row of a list, search a mailbox month by month). It needs other tools to call; a change the person
|
|
162
|
+
// must allow first is never made from a program.
|
|
159
163
|
// "browser" brings: browser_open/read/click/type/select/scroll/press/back/wait, browser_screenshot (the model looks at the page),
|
|
160
164
|
// browser_run_fast (a decision model clicks through a page's mechanical part, where one is reached),
|
|
161
165
|
// browser_request_credentials (the PERSON types a login into the page — the model never sees it; sign-ins can be remembered),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Developer
|
|
3
3
|
description: Extend engenty wizards with plugins – tools for AI steps, routes with their own tables, pages in the studio.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.3
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
You extend engenty wizards with **plugins**. A plugin adds to a runtime without changing its
|
|
@@ -21,6 +21,11 @@ code, and the runtime loads it from its files while it runs.
|
|
|
21
21
|
A wizard is JSON, and it travels as a `.wizard` file. [The wizard format](./wizard-format.md)
|
|
22
22
|
describes the format, the JSON Schema for your editor and `wizards validate`.
|
|
23
23
|
|
|
24
|
+
## Wizards in other systems
|
|
25
|
+
|
|
26
|
+
[Integration](./integration/index.md) shows how a published wizard reaches other systems: people
|
|
27
|
+
over a link, a website or a channel, AI apps over MCP, and your own code.
|
|
28
|
+
|
|
24
29
|
## What you need
|
|
25
30
|
|
|
26
31
|
- engenty wizards on your computer, or a checkout of
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Channels
|
|
3
|
+
description: Run a wizard as a chat, a conversation or a call, and over WhatsApp, SMS or the phone.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [Channels](../../user/test-and-share.md#channels) in the user guide describes how a
|
|
10
|
+
wizard runs as a chat, a conversation or a call, and over WhatsApp, SMS or the phone. A plugin
|
|
11
|
+
adds a channel of its own with a [runner](../plugins/runners.md).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Claude Code plugin
|
|
3
|
+
description: Build and use wizards from Claude Code with the /wizard command and its skills.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [The Claude Code plugin](../../user/ai-apps.md#the-claude-code-plugin) in the user
|
|
10
|
+
guide shows how you install it.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Embed
|
|
3
|
+
description: Put a published wizard on your website with one script tag.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [Embed in a website](../../user/test-and-share.md#embed-in-a-website) in the user
|
|
10
|
+
guide shows the script tag and its options.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Integration
|
|
3
|
+
description: Bring a published wizard into other systems – for people, for AI apps and for your own code.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A published wizard runs wherever a person can reach it. These pages show how to bring it into
|
|
8
|
+
other systems, from a plain link to a chat that starts the wizard by itself.
|
|
9
|
+
|
|
10
|
+
## For people
|
|
11
|
+
|
|
12
|
+
| | |
|
|
13
|
+
|---|---|
|
|
14
|
+
| [Link and QR code](./link-and-share.md) | The wizard’s own page, as a link or a QR code |
|
|
15
|
+
| [Embed](./embed.md) | The wizard on your website, inline, as a button or a chat |
|
|
16
|
+
| [WordPress](./wordpress.md) | The plugin “engenty wizards” places wizards and reads their results |
|
|
17
|
+
| [Channels](./channels.md) | A chat, a conversation or a call, and WhatsApp, SMS or the phone |
|
|
18
|
+
|
|
19
|
+
## For AI apps
|
|
20
|
+
|
|
21
|
+
| | |
|
|
22
|
+
|---|---|
|
|
23
|
+
| [MCP](./mcp.md) | AI apps list, run and build wizards over the Model Context Protocol |
|
|
24
|
+
| [MCP Apps](./mcp-apps.md) | AI apps that support MCP Apps show the wizard itself, page by page |
|
|
25
|
+
| [Claude Code plugin](./claude-code-plugin.md) | The `/wizard` command and the skills to build and use wizards |
|
|
26
|
+
| [OpenUI](./openui.md) | A chat built with OpenUI runs the wizard inside the conversation |
|
|
27
|
+
|
|
28
|
+
## For your code
|
|
29
|
+
|
|
30
|
+
| | |
|
|
31
|
+
|---|---|
|
|
32
|
+
| [Your own UI](./own-ui.md) | Your app draws the run with `@engenty-wizards/runner` |
|
|
33
|
+
| [Results to your systems](./results.md) | What a run produces goes on to your own systems |
|
|
34
|
+
|
|
35
|
+
Each way starts from a published wizard. [Test and share](../../user/test-and-share.md) in the
|
|
36
|
+
user guide explains how you publish one.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Link and QR code
|
|
3
|
+
description: Hand out a published wizard as a link or a QR code.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [Share](../../user/test-and-share.md#share) in the user guide describes the link, the
|
|
10
|
+
QR code and who can open them.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP Apps
|
|
3
|
+
description: AI apps that support MCP Apps show the wizard itself as a widget, page by page.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [What an app can do](../../user/ai-apps.md#what-an-app-can-do) in the user guide lists
|
|
10
|
+
what an app with MCP Apps shows.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP
|
|
3
|
+
description: AI apps list, run and build wizards over the Model Context Protocol.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [AI apps](../../user/ai-apps.md) in the user guide shows how you add engenty wizards
|
|
10
|
+
to Claude Desktop, Cursor, Codex and other AI apps.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Integration",
|
|
3
|
+
"defaultOpen": true,
|
|
4
|
+
"pages": [
|
|
5
|
+
"index",
|
|
6
|
+
"---For people---",
|
|
7
|
+
"link-and-share",
|
|
8
|
+
"embed",
|
|
9
|
+
"wordpress",
|
|
10
|
+
"channels",
|
|
11
|
+
"---For AI apps---",
|
|
12
|
+
"mcp",
|
|
13
|
+
"mcp-apps",
|
|
14
|
+
"claude-code-plugin",
|
|
15
|
+
"openui",
|
|
16
|
+
"---For your code---",
|
|
17
|
+
"own-ui",
|
|
18
|
+
"results"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Wizards in OpenUI chats
|
|
3
|
+
description: A Wizard component for OpenUI’s React renderer. The chat’s model decides that a wizard runs, and the wizard runs its own pages inside the conversation.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A chat built with [OpenUI](https://github.com/thesysdev/openui) composes its answers as it
|
|
8
|
+
goes: cards, charts, forms and buttons, whatever the model finds fitting. A wizard is the one
|
|
9
|
+
thing in such a chat that runs the same way every time, with the pages, steps and result its
|
|
10
|
+
author defined. `@engenty/wizards/openui` puts the two together: the chat decides that a wizard
|
|
11
|
+
fits and what it already knows, the wizard then runs inside the conversation, and the chat
|
|
12
|
+
hears the result.
|
|
13
|
+
|
|
14
|
+
It is part of `@engenty/wizards`, the package for code outside engenty wizards
|
|
15
|
+
([`packages/wizards/src/openui`](https://github.com/engenty/wizards/tree/main/packages/wizards/src/openui)
|
|
16
|
+
in the repository, under the MIT licence). A whole chat to try it with is `examples/openui-chat`.
|
|
17
|
+
|
|
18
|
+
## What OpenUI is
|
|
19
|
+
|
|
20
|
+
OpenUI (Thesys, MIT) is a way to let a model draw. Three parts matter here:
|
|
21
|
+
|
|
22
|
+
- **A language the model writes.** An answer is a short program: `root = Card([…])` with
|
|
23
|
+
components as calls, such as `TextContent("…")` or `Form(…)`. The model streams it like text.
|
|
24
|
+
- **A React renderer** (`@openuidev/react-lang`) that parses the program as it streams and draws
|
|
25
|
+
it with a **component library**: React components with a name, a description and typed props.
|
|
26
|
+
- **A chat kit** (`@openuidev/react-ui`): the library of chat components, the thread, the
|
|
27
|
+
composer and an `AgentInterface` that puts them together.
|
|
28
|
+
|
|
29
|
+
The model learns the language from the system prompt the library makes. The chat library’s
|
|
30
|
+
prompt is about 14,000 tokens: every component with its props and description, the rules, and
|
|
31
|
+
5 worked examples of whole answers. The model imitates the examples. Nothing in that prompt is
|
|
32
|
+
a fixed flow: every answer, and every form in it, is composed anew.
|
|
33
|
+
|
|
34
|
+
A wizard is the only fixed definition in such a chat. The model writes one line, and from then
|
|
35
|
+
on the wizard’s pages, AI steps, review and result run as its author defined them.
|
|
36
|
+
|
|
37
|
+
## How the Wizard component works
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
sequenceDiagram
|
|
41
|
+
participant S as Chat server
|
|
42
|
+
participant M as Model
|
|
43
|
+
participant B as Browser (OpenUI renderer)
|
|
44
|
+
participant R as Runtime
|
|
45
|
+
S->>R: GET /api/public/wizards/:token
|
|
46
|
+
R-->>S: title, description, fields
|
|
47
|
+
S->>M: system prompt + wizard rules
|
|
48
|
+
M-->>B: root = Card([TextContent("…"), Wizard("<token>", "<note>", {plate: "W-12345"})])
|
|
49
|
+
B->>R: POST /api/public/wizards/:token/runs
|
|
50
|
+
R-->>B: run id
|
|
51
|
+
loop every step
|
|
52
|
+
R-->>B: the run’s view (stream)
|
|
53
|
+
B->>R: a page’s answers, a review, back
|
|
54
|
+
end
|
|
55
|
+
B->>S: continue_conversation: “The wizard finished.” + result
|
|
56
|
+
S->>M: the thread
|
|
57
|
+
M-->>B: an answer in plain words
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
It has two halves, one for each side of the chat.
|
|
61
|
+
|
|
62
|
+
### The server half
|
|
63
|
+
|
|
64
|
+
`@engenty/wizards/openui/server` draws nothing, so a server can import it alone.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { addWizard, loadWizards, wizardRules } from "@engenty/wizards/openui/server";
|
|
68
|
+
import { openuiChatLibrary, openuiChatPromptOptions } from "@openuidev/react-ui/genui-lib";
|
|
69
|
+
|
|
70
|
+
const wizards = await loadWizards("http://localhost:24368", ["<token>", "<token>"]);
|
|
71
|
+
const prompt = addWizard(openuiChatLibrary).prompt({
|
|
72
|
+
...openuiChatPromptOptions,
|
|
73
|
+
additionalRules: [...(openuiChatPromptOptions.additionalRules ?? []), ...wizardRules(wizards)],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `loadWizards(runtimeUrl, tokens)` reads each published wizard from the runtime
|
|
78
|
+
(`GET /api/public/wizards/:token`): its title, description and the fields its pages ask, in
|
|
79
|
+
order. A wizard the runtime does not answer for is left out, so the chat keeps working when
|
|
80
|
+
one is unpublished.
|
|
81
|
+
- `addWizard(library)` extends an OpenUI library with the Wizard component, among a Card’s
|
|
82
|
+
children. Its prompt then has `Wizard(wizard: string, note?: string, prefill?: …)`.
|
|
83
|
+
- `wizardRules(wizards)` are the rules that go with it: when to answer with a wizard, that the
|
|
84
|
+
wizard asks its own questions (never a Form in its place), what may be prefilled, and the list
|
|
85
|
+
of wizards with their tokens and fields.
|
|
86
|
+
|
|
87
|
+
With that, the model answers a fitting request with a Card that holds one short text and
|
|
88
|
+
`Wizard("<token>", "<note>", {prefill})`. The prefill holds answers the person already gave in
|
|
89
|
+
the conversation, by field id; the person still sees and sends each page.
|
|
90
|
+
|
|
91
|
+
### The component, in the browser
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import "@engenty/wizards/openui/styles.css";
|
|
95
|
+
import { addWizard, configureWizards, Wizard } from "@engenty/wizards/openui";
|
|
96
|
+
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
|
|
97
|
+
|
|
98
|
+
configureWizards({ runtime: "/wizards-runtime" });
|
|
99
|
+
const library = addWizard(openuiChatLibrary, Wizard);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- `configureWizards({ runtime })` names the runtime once, as the browser reaches it: a path the
|
|
103
|
+
host proxies to it, or the runtime’s address. A proxy on the host’s own address keeps the
|
|
104
|
+
runner’s visitor cookie first-party. `lang` sets the language of the runner’s own words
|
|
105
|
+
(`en` unless given), `Mascot` draws the wizard’s mascot above its pages.
|
|
106
|
+
- `addWizard(library, Wizard)` is the same extension as on the server, with the drawing
|
|
107
|
+
component. Both make the same prompt.
|
|
108
|
+
|
|
109
|
+
The component draws the run with the runner, the same code as the wizard’s own page
|
|
110
|
+
(`packages/runner`), in styles scoped to `.engenty-runner`, so the chat’s styles and the
|
|
111
|
+
runner’s never meet. Every field kind works as on the wizard’s own page, the camera and the
|
|
112
|
+
signature included. The page is not a frame, so nothing else is needed for them.
|
|
113
|
+
|
|
114
|
+
What happens in the message:
|
|
115
|
+
|
|
116
|
+
1. **The run starts** once the model’s line has finished streaming, so a half-written token never
|
|
117
|
+
starts a run: `POST /api/public/wizards/:token/runs`, as a visitor of the runtime.
|
|
118
|
+
2. **The run id stays with the message**, in the state OpenUI keeps for it. A message shown again
|
|
119
|
+
continues the same run.
|
|
120
|
+
3. **The run shows live** over the runtime’s stream of the run’s view (`/api/runs/:id/stream`), and
|
|
121
|
+
the person’s answers go to the run’s routes (`/api/runs/:id/pages/:step`, `…/reviews/:step`,
|
|
122
|
+
`…/back`, `…/retry`). These are the routes the wizard’s own page uses.
|
|
123
|
+
4. **The result goes back to the chat.** When the component sees the run finish, it sends OpenUI’s
|
|
124
|
+
`continue_conversation` action: a turn of the person that says the wizard finished, with the
|
|
125
|
+
result as context for the model: the wizard’s title, the run id and the texts of the result
|
|
126
|
+
steps, each cut at 4,000 characters. The model answers in plain words.
|
|
127
|
+
|
|
128
|
+
The chat hears of a run it saw finish. A message shown again with a done run shows the result
|
|
129
|
+
and tells the chat nothing new.
|
|
130
|
+
|
|
131
|
+
### Who runs, who pays
|
|
132
|
+
|
|
133
|
+
The browser talks to the runtime as a visitor, as on the wizard’s own page: the runtime sets a
|
|
134
|
+
visitor cookie, lets a visitor start 6 runs an hour
|
|
135
|
+
([`LIMIT_VISITOR_RUNS_PER_HOUR`](../../user/command-line.md#settings)) and spends the wizard’s own
|
|
136
|
+
credits or model keys. The chat’s model and the wizards’ model are two different things: the
|
|
137
|
+
chat picks the model that writes its answers, and each wizard’s steps run on what its space
|
|
138
|
+
has.
|
|
139
|
+
|
|
140
|
+
## Try it
|
|
141
|
+
|
|
142
|
+
`examples/openui-chat` is an OpenUI chat (`AgentInterface`) with any OpenAI-compatible model,
|
|
143
|
+
the wizards it may start and a proxy to the runtime. In short:
|
|
144
|
+
|
|
145
|
+
1. Start the runtime from a checkout (`pnpm dev`) and publish a wizard or two in the studio.
|
|
146
|
+
Each one’s token is the part of its link after `/w/`.
|
|
147
|
+
2. Put an `.env.local` in `examples/openui-chat`: `WIZARDS=<token>,<token>`, `CHAT_BASE_URL`,
|
|
148
|
+
`CHAT_API_KEY` and `CHAT_MODEL` for the chat’s model.
|
|
149
|
+
3. `pnpm --filter @engenty-wizards/example-openui-chat dev` and open `http://localhost:5190`.
|
|
150
|
+
4. Ask for something a wizard does, in your own words.
|
|
151
|
+
|
|
152
|
+
The example’s README has the details, the endpoints that work and what to set while you try
|
|
153
|
+
things.
|
|
154
|
+
|
|
155
|
+
## Exports
|
|
156
|
+
|
|
157
|
+
| From | Export | What it is |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `@engenty/wizards/openui/server` | `loadWizards(runtimeUrl, tokens)` | The published wizards behind the tokens, as `WizardInfo[]` |
|
|
160
|
+
| | `wizardRules(wizards)` | The rules a system prompt adds |
|
|
161
|
+
| | `addWizard(library, component?)` | The library with the Wizard component among a Card’s children |
|
|
162
|
+
| | `WizardSpec`, `wizardProps`, `WizardProps` | The component as a prompt sees it, and its props |
|
|
163
|
+
| | `WIZARD_COMPONENT`, `WIZARD_DESCRIPTION` | Its name and description |
|
|
164
|
+
| `@engenty/wizards/openui` | all of the above | |
|
|
165
|
+
| | `configureWizards(config)` | Names the runtime, the language and the mascot |
|
|
166
|
+
| | `Wizard` | The drawing component |
|
|
167
|
+
| | `useWizardRun(runId, prefill)` | One run, live, for a host that draws runs itself |
|
|
168
|
+
| `@engenty/wizards/openui/styles.css` | | The runner’s styles, scoped to the wizard |
|
|
169
|
+
|
|
170
|
+
`@openuidev/react-lang`, `react`, `react-dom` and `zod` are peer dependencies: the chat brings
|
|
171
|
+
them. The package is written against `@openuidev/react-lang` 1.0 and the chat kit
|
|
172
|
+
`@openuidev/react-ui` 0.18.
|
|
173
|
+
|
|
174
|
+
## Good to know
|
|
175
|
+
|
|
176
|
+
- **One runtime per page.** `configureWizards` names it once, before the first render.
|
|
177
|
+
- **Other hosts.** The component sits on the runtime’s public run routes, not on OpenUI’s
|
|
178
|
+
internals. A host with its own components can draw a run with `useWizardRun` and the runner’s
|
|
179
|
+
parts, and another UI format can follow the same way.
|
|
180
|
+
- **OpenUI’s Inspect button** at the bottom right of a page in development is OpenUI’s own
|
|
181
|
+
(`@openuidev/react-lang` adds it and loads it from jsDelivr). A production build leaves it out.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Your own UI
|
|
3
|
+
description: Draw a wizard’s run in your own app with @engenty-wizards/runner and the runtime’s public run routes.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [OpenUI](./openui.md) shows one app that does it: it draws the run with
|
|
10
|
+
`@engenty-wizards/runner` (`packages/runner`) over the runtime’s public run routes.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Results to your systems
|
|
3
|
+
description: Send what a run produces to your own systems.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [Results, contacts, appointments](../../user/wordpress.md#results-contacts-appointments)
|
|
10
|
+
shows how the WordPress plugin receives results. A plugin can also answer other services with
|
|
11
|
+
its own [routes](../plugins/routes.md).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WordPress
|
|
3
|
+
description: Put published wizards on a WordPress site with the plugin “engenty wizards”.
|
|
4
|
+
version: 0.7.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page is not written yet.
|
|
8
|
+
|
|
9
|
+
Until then, [WordPress](../../user/wordpress.md) in the user guide shows how you connect a site,
|
|
10
|
+
place a wizard and read its results in the admin.
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
"description": "Extend engenty wizards with plugins: tools for AI steps, routes with their own tables, pages in the studio.",
|
|
4
4
|
"root": true,
|
|
5
5
|
"icon": "SquareTerminal",
|
|
6
|
-
"pages": ["index", "wizard-format", "plugins"]
|
|
6
|
+
"pages": ["index", "wizard-format", "plugins", "integration"]
|
|
7
7
|
}
|
|
@@ -483,7 +483,7 @@ export async function runAgentStep(step, ctx) {
|
|
|
483
483
|
}
|
|
484
484
|
const resolved = await textModel(step.model ?? "high", { ...ctx.call, effort: step.effort });
|
|
485
485
|
const uploads = await personUploads(ctx);
|
|
486
|
-
const { tools, assets, close } = await buildStepTools(step, ctx, resolved, uploads);
|
|
486
|
+
const { tools, assets, instructions, close } = await buildStepTools(step, ctx, resolved, uploads);
|
|
487
487
|
// A step without tools of its own and without uploads: the run's device may answer it alone.
|
|
488
488
|
attachTools(resolved, tools, step.tools.length === 0 && uploads.length === 0);
|
|
489
489
|
try {
|
|
@@ -501,6 +501,7 @@ export async function runAgentStep(step, ctx) {
|
|
|
501
501
|
const system = [
|
|
502
502
|
groundRules(ctx),
|
|
503
503
|
step.tools.includes("browser") ? BROWSER_RULES : "",
|
|
504
|
+
instructions ?? "",
|
|
504
505
|
`Today is ${today(ctx)}.`,
|
|
505
506
|
brandBlock(ctx),
|
|
506
507
|
// Knowledge: what the space holds for its wizards, and how to find and read it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FORMATS, isLang, LANG_CODE, LANGS, LIST_FORMATS, TEXT_CLASSES, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
2
|
import { TableColumnValueError } from "@engenty-wizards/shared/engenty/data-tables";
|
|
3
|
-
import { noteText } from "@engenty-wizards/shared/run";
|
|
3
|
+
import { noteText, } from "@engenty-wizards/shared/run";
|
|
4
4
|
import { machineTranslated, translateDefinition, } from "@engenty-wizards/shared/translation";
|
|
5
5
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
6
6
|
import { and, eq } from "drizzle-orm";
|
|
@@ -220,6 +220,18 @@ async function publishedWizard(token) {
|
|
|
220
220
|
function wantsTranslation(w, def, lang) {
|
|
221
221
|
return w.translate && lang !== wizardLang(def);
|
|
222
222
|
}
|
|
223
|
+
/** What the wizard's pages ask, in order (`PublicWizard.fields`). */
|
|
224
|
+
export function pageFields(def) {
|
|
225
|
+
return def.steps.flatMap((step) => step.type === "page"
|
|
226
|
+
? step.fields.map((f) => ({
|
|
227
|
+
id: f.id,
|
|
228
|
+
label: f.label,
|
|
229
|
+
kind: f.kind,
|
|
230
|
+
...(f.options?.length ? { options: f.options } : {}),
|
|
231
|
+
...(f.required ? { required: true } : {}),
|
|
232
|
+
}))
|
|
233
|
+
: []);
|
|
234
|
+
}
|
|
223
235
|
/** What a wizard's page shows before a run: in the viewer's language where `as` is given. */
|
|
224
236
|
async function publicBody(c, w, original, as) {
|
|
225
237
|
const translated = as ? translateDefinition(original, as.memory) : null;
|
|
@@ -245,6 +257,7 @@ async function publicBody(c, w, original, as) {
|
|
|
245
257
|
machine: machineTranslated(as.memory, original),
|
|
246
258
|
}
|
|
247
259
|
: null,
|
|
260
|
+
fields: pageFields(def),
|
|
248
261
|
};
|
|
249
262
|
}
|
|
250
263
|
export const publicRoutes = new Hono()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { createCodeMode, createTool, noopObserve } from "@mastra/core/tools";
|
|
3
|
+
import { QuickJsCodeModeTransport } from "@mastra/quickjs";
|
|
4
|
+
import { clip } from "./shared.js";
|
|
5
|
+
/**
|
|
6
|
+
* Code Mode: the model writes one TypeScript program that calls the step's tools as
|
|
7
|
+
* `external_*` functions — loops, `Promise.all`, filtering and sums — and only what the program
|
|
8
|
+
* returns goes back to the model.
|
|
9
|
+
*
|
|
10
|
+
* The program runs in QuickJS compiled to WebAssembly, inside the server process. It has no
|
|
11
|
+
* files, network, timers or modules: its only way out are the step's own tools, which run on
|
|
12
|
+
* the server exactly as when the model calls them. Every run gets a fresh runtime with its own
|
|
13
|
+
* heap. The limits below keep one program from tying up the server or a site it calls.
|
|
14
|
+
*/
|
|
15
|
+
export const CODE_LIMITS = {
|
|
16
|
+
/** Wall time of one program, its tool calls included. */
|
|
17
|
+
timeoutMs: 120_000,
|
|
18
|
+
/** Tool calls one program may make. */
|
|
19
|
+
maxCalls: 100,
|
|
20
|
+
/** Tool calls of one program running at the same time. */
|
|
21
|
+
maxParallel: 4,
|
|
22
|
+
/** Characters of the program's answer the model reads. */
|
|
23
|
+
resultChars: 20_000,
|
|
24
|
+
/** Characters of `console.log` output the model reads. */
|
|
25
|
+
logChars: 4000,
|
|
26
|
+
};
|
|
27
|
+
/** The id of the tool, the way the model calls it. */
|
|
28
|
+
export const CODE_TOOL_ID = "execute_typescript";
|
|
29
|
+
const program = new AsyncLocalStorage();
|
|
30
|
+
/**
|
|
31
|
+
* Whether a tool is running for a program. Nobody can be asked from inside one: a tool that needs
|
|
32
|
+
* the person's yes refuses there and tells the model to call it directly.
|
|
33
|
+
*/
|
|
34
|
+
export function inProgram() {
|
|
35
|
+
return program.getStore() === true;
|
|
36
|
+
}
|
|
37
|
+
/** Heap of one program's QuickJS runtime. */
|
|
38
|
+
const MEMORY_LIMIT_MB = 64;
|
|
39
|
+
/** One transport for the process; it loads the WebAssembly module once, on the first program. */
|
|
40
|
+
let transport = null;
|
|
41
|
+
function quickJs() {
|
|
42
|
+
transport ??= new QuickJsCodeModeTransport({ memoryLimitMb: MEMORY_LIMIT_MB });
|
|
43
|
+
return transport;
|
|
44
|
+
}
|
|
45
|
+
/** Runs at most `max` tasks at a time; the others wait their turn. */
|
|
46
|
+
function limiter(max) {
|
|
47
|
+
let running = 0;
|
|
48
|
+
const waiting = [];
|
|
49
|
+
return async (task) => {
|
|
50
|
+
if (running >= max) {
|
|
51
|
+
await new Promise((resolve) => waiting.push(resolve));
|
|
52
|
+
}
|
|
53
|
+
running++;
|
|
54
|
+
try {
|
|
55
|
+
return await task();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
running--;
|
|
59
|
+
waiting.shift()?.();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** What a program may call: tools the server runs itself (a provider's own search has no `execute`). */
|
|
64
|
+
function callable(tools) {
|
|
65
|
+
return Object.fromEntries(Object.entries(tools).filter(([, tool]) => typeof tool?.execute === "function"));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Wraps each tool for one program run: the call count and the calls in flight are capped, and the
|
|
69
|
+
* tool knows it runs for a program (`inProgram`).
|
|
70
|
+
*/
|
|
71
|
+
function guarded(tools, ctx, limits) {
|
|
72
|
+
let calls = 0;
|
|
73
|
+
const slot = limiter(limits.maxParallel);
|
|
74
|
+
return Object.fromEntries(Object.entries(tools).map(([key, tool]) => [
|
|
75
|
+
key,
|
|
76
|
+
{
|
|
77
|
+
...tool,
|
|
78
|
+
execute: (input, options) => {
|
|
79
|
+
calls++;
|
|
80
|
+
if (calls > limits.maxCalls) {
|
|
81
|
+
throw new Error(`A program makes at most ${limits.maxCalls} tool calls. Split the work.`);
|
|
82
|
+
}
|
|
83
|
+
if (ctx.signal.aborted) {
|
|
84
|
+
throw new Error("The run was stopped.");
|
|
85
|
+
}
|
|
86
|
+
return slot(() => program.run(true, () => tool.execute(input, options)));
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
]));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The `execute_typescript` tool over `tools`, and the instructions that tell the model how to
|
|
93
|
+
* write a program. Null when there is nothing a program could call.
|
|
94
|
+
*/
|
|
95
|
+
export function codeTool(tools, ctx, limits = CODE_LIMITS) {
|
|
96
|
+
const reachable = callable(tools);
|
|
97
|
+
if (!Object.keys(reachable).length) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const config = { id: CODE_TOOL_ID, tools: reachable, timeout: limits.timeoutMs };
|
|
101
|
+
// The instructions list the tools once; every program runs over fresh guards.
|
|
102
|
+
const { tool: base, instructions } = createCodeMode(config, quickJs());
|
|
103
|
+
const tool = createTool({
|
|
104
|
+
id: CODE_TOOL_ID,
|
|
105
|
+
description: "Run a TypeScript program that calls your tools as external_* functions: loops, Promise.all, filtering, sums. Only what it returns comes back to you. Use it when a task needs many tool calls or arithmetic over their results.",
|
|
106
|
+
inputSchema: base.inputSchema,
|
|
107
|
+
execute: async (input, options) => {
|
|
108
|
+
await ctx.emit("tool", { code: "runsProgram" });
|
|
109
|
+
const run = createCodeMode({ ...config, tools: guarded(reachable, ctx, limits) }, quickJs()).tool;
|
|
110
|
+
// Spans need a run being traced; a call without one still runs.
|
|
111
|
+
const result = (await run.execute?.(input, {
|
|
112
|
+
...options,
|
|
113
|
+
observe: options?.observe ?? noopObserve,
|
|
114
|
+
}));
|
|
115
|
+
const logs = result.logs?.length ? clip(result.logs.join("\n"), limits.logChars) : "";
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
return { success: false, error: result.error, ...(logs ? { logs } : {}) };
|
|
118
|
+
}
|
|
119
|
+
const answer = typeof result.result === "string"
|
|
120
|
+
? result.result
|
|
121
|
+
: (JSON.stringify(result.result ?? null) ?? "null");
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
result: answer.length > limits.resultChars ? clip(answer, limits.resultChars) : result.result,
|
|
125
|
+
...(logs ? { logs } : {}),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
tool,
|
|
131
|
+
instructions: `${instructions}\n\nA program makes at most ${limits.maxCalls} tool calls, ${limits.maxParallel} at a time, and stops after ${limits.timeoutMs / 1000} seconds. Return only what you need: a long answer is cut at ${limits.resultChars} characters. A tool that asks the person first cannot run from a program: call it directly.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createTool } from "@mastra/core/tools";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { refreshConnector, resolveConnector } from "../connectors/external.js";
|
|
4
4
|
import { connectionContext } from "../connectors/index.js";
|
|
5
|
+
import { inProgram } from "./code.js";
|
|
5
6
|
import { attempt, clip } from "./shared.js";
|
|
6
7
|
import { resolveFile } from "./store.js";
|
|
7
8
|
const FILE_NOTE = " File content never passes through you: returned content is kept as a file and you get its path; to send a kept file, give `file:<path>` where an input asks for `*_base64`.";
|
|
@@ -116,6 +117,11 @@ async function toolsOf(connection, ctx, files) {
|
|
|
116
117
|
}
|
|
117
118
|
const key = `${connection.id}:${action.id}`;
|
|
118
119
|
if (asks(connection, action) && !ctx.resources.allowed.has(key)) {
|
|
120
|
+
if (inProgram()) {
|
|
121
|
+
return {
|
|
122
|
+
error: "This change waits for the person's yes, which a program cannot ask for. Call the tool directly, outside the program.",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
119
125
|
const answer = await ctx.ask({
|
|
120
126
|
kind: "confirm",
|
|
121
127
|
reason: action.summary || action.id,
|
|
@@ -12,6 +12,7 @@ import { sandboxCapabilities, shellToolDescription } from "../sandbox/index.js";
|
|
|
12
12
|
import { snapshotFile } from "../services/files.js";
|
|
13
13
|
import { hasStoreFiles } from "../store/index.js";
|
|
14
14
|
import { browserTools } from "./browser.js";
|
|
15
|
+
import { codeTool } from "./code.js";
|
|
15
16
|
import { connectorTools } from "./connector.js";
|
|
16
17
|
import { mailTools } from "./mail.js";
|
|
17
18
|
import { assertPublicUrl, safeFetch } from "./net-guard.js";
|
|
@@ -143,9 +144,9 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
|
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
146
|
const files = new FileKeeper(ctx, assets);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
// The browser is one page, worked step by step, and it asks the person to sign in.
|
|
148
|
+
const browser = allowed.has("browser") ? await browserTools(ctx, assets, files) : {};
|
|
149
|
+
Object.assign(tools, browser);
|
|
149
150
|
// The wizard's lists and files, the documents the person gave and the project's documents are
|
|
150
151
|
// always at hand. An installed client gets tools over an MCP bridge its CLI starts for every
|
|
151
152
|
// call, seconds of its own: a step with no use for the store goes without its tools, and
|
|
@@ -281,9 +282,19 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
|
|
|
281
282
|
});
|
|
282
283
|
}
|
|
283
284
|
}
|
|
285
|
+
// An installed client writes and runs code of its own.
|
|
286
|
+
let instructions;
|
|
287
|
+
if (allowed.has("code") && !isHarnessVendor(resolved.vendor)) {
|
|
288
|
+
const code = codeTool(Object.fromEntries(Object.entries(tools).filter(([key]) => !(key in browser))), ctx);
|
|
289
|
+
if (code) {
|
|
290
|
+
tools[code.tool.id] = code.tool;
|
|
291
|
+
instructions = code.instructions;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
284
294
|
return {
|
|
285
295
|
tools,
|
|
286
296
|
assets,
|
|
297
|
+
instructions,
|
|
287
298
|
close: async () => {
|
|
288
299
|
await mcp?.disconnect().catch(() => undefined);
|
|
289
300
|
},
|