@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.
Files changed (67) hide show
  1. package/apps/runtime/dist/authoring/guide.js +5 -1
  2. package/apps/runtime/dist/docs/dev/index.md +6 -1
  3. package/apps/runtime/dist/docs/dev/integration/channels.md +11 -0
  4. package/apps/runtime/dist/docs/dev/integration/claude-code-plugin.md +10 -0
  5. package/apps/runtime/dist/docs/dev/integration/embed.md +10 -0
  6. package/apps/runtime/dist/docs/dev/integration/index.md +36 -0
  7. package/apps/runtime/dist/docs/dev/integration/link-and-share.md +10 -0
  8. package/apps/runtime/dist/docs/dev/integration/mcp-apps.md +10 -0
  9. package/apps/runtime/dist/docs/dev/integration/mcp.md +10 -0
  10. package/apps/runtime/dist/docs/dev/integration/meta.json +20 -0
  11. package/apps/runtime/dist/docs/dev/integration/openui.md +181 -0
  12. package/apps/runtime/dist/docs/dev/integration/own-ui.md +10 -0
  13. package/apps/runtime/dist/docs/dev/integration/results.md +11 -0
  14. package/apps/runtime/dist/docs/dev/integration/wordpress.md +10 -0
  15. package/apps/runtime/dist/docs/dev/meta.json +1 -1
  16. package/apps/runtime/dist/engine/steps.js +2 -1
  17. package/apps/runtime/dist/routes/runs.js +14 -1
  18. package/apps/runtime/dist/tools/code.js +133 -0
  19. package/apps/runtime/dist/tools/connector.js +6 -0
  20. package/apps/runtime/dist/tools/index.js +14 -3
  21. package/apps/web/dist/assets/{AboutDialog-DYgXPagX.js → AboutDialog-BP8-Ugxf.js} +1 -1
  22. package/apps/web/dist/assets/{ChangelogDialog-BXqBW8j3.js → ChangelogDialog-CD6CQNW9.js} +1 -1
  23. package/apps/web/dist/assets/{ChatView-CK2--tsE.js → ChatView-4T0QarEr.js} +1 -1
  24. package/apps/web/dist/assets/{EditorPage-jvcwyXbU.js → EditorPage-CWdV7Xdn.js} +3 -3
  25. package/apps/web/dist/assets/{FlowPreview-CusP7ci9.js → FlowPreview-Dj2gAfuf.js} +1 -1
  26. package/apps/web/dist/assets/{Harness-CV9uYzbv.js → Harness-SLbsntQF.js} +1 -1
  27. package/apps/web/dist/assets/{NewWizardChannels-BakW5q6s.js → NewWizardChannels-CsDMlji7.js} +1 -1
  28. package/apps/web/dist/assets/{NewWizardPage-BoA-LJuW.js → NewWizardPage-_bFYjlgh.js} +1 -1
  29. package/apps/web/dist/assets/{OnboardingPage-DaD1XnOe.js → OnboardingPage-DObQeCDg.js} +1 -1
  30. package/apps/web/dist/assets/{ProjectPage-BFgk1DEv.js → ProjectPage-DX-oNLWy.js} +1 -1
  31. package/apps/web/dist/assets/{PublicRunner-C9MwEvxy.js → PublicRunner-Bn9rVNEb.js} +1 -1
  32. package/apps/web/dist/assets/{ReadOnly-DC2Kp7l8.js → ReadOnly-CK0piLUV.js} +1 -1
  33. package/apps/web/dist/assets/{RunDrawer-CY6NZSrI.js → RunDrawer-CVMTLse_.js} +1 -1
  34. package/apps/web/dist/assets/{RunnerView-BT26XgcY.js → RunnerView-Cjhuu9ua.js} +1 -1
  35. package/apps/web/dist/assets/{SettingsPage-AFt9IDLt.js → SettingsPage-DTzuiSAj.js} +1 -1
  36. package/apps/web/dist/assets/{SetupPage-DtFYAbwJ.js → SetupPage-D9jlhgiP.js} +1 -1
  37. package/apps/web/dist/assets/{SharePage-t2xwNS61.js → SharePage-Cc3Opj2F.js} +1 -1
  38. package/apps/web/dist/assets/changelog-CJNkd1_L.js +1 -0
  39. package/apps/web/dist/assets/{esm-DXeMu8Lb.js → esm-CfnFE_jE.js} +1 -1
  40. package/apps/web/dist/assets/{index-Drw7FMTQ.js → index-B5F1_qA_.js} +4 -4
  41. package/apps/web/dist/assets/{lock-R_CfU0Xo.js → lock-DAvRJY4B.js} +1 -1
  42. package/apps/web/dist/assets/oss-credits-X2JPiMOR.js +1 -0
  43. package/apps/web/dist/assets/{phone-CgrIS4_-.js → phone-BWOB67tX.js} +1 -1
  44. package/apps/web/dist/assets/{settings-menu-2IMOOlLl.js → settings-menu-CEQlVs1O.js} +1 -1
  45. package/apps/web/dist/assets/{ui-dzGvc3xk.js → ui-BmR_4eL2.js} +1 -1
  46. package/apps/web/dist/assets/{upload-BSuTilFF.js → upload-pBKn2EFp.js} +1 -1
  47. package/apps/web/dist/index.html +2 -2
  48. package/apps/web/dist/mcp/flow.html +2 -2
  49. package/node_modules/@engenty/wizards/dist/openui/Wizard.d.ts +20 -0
  50. package/node_modules/@engenty/wizards/dist/openui/Wizard.js +62 -0
  51. package/node_modules/@engenty/wizards/dist/openui/index.d.ts +9 -0
  52. package/node_modules/@engenty/wizards/dist/openui/index.js +9 -0
  53. package/node_modules/@engenty/wizards/dist/openui/message.d.ts +11 -0
  54. package/node_modules/@engenty/wizards/dist/openui/message.js +69 -0
  55. package/node_modules/@engenty/wizards/dist/openui/run.d.ts +8 -0
  56. package/node_modules/@engenty/wizards/dist/openui/run.js +137 -0
  57. package/node_modules/@engenty/wizards/dist/openui/server.d.ts +37 -0
  58. package/node_modules/@engenty/wizards/dist/openui/server.js +77 -0
  59. package/node_modules/@engenty/wizards/package.json +4 -1
  60. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +4 -1
  61. package/node_modules/@engenty-wizards/shared/dist/definition.js +1 -0
  62. package/node_modules/@engenty-wizards/shared/dist/fit.js +7 -2
  63. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +14 -0
  64. package/node_modules/@engenty-wizards/shared/dist/run.js +2 -0
  65. package/package.json +4 -3
  66. package/apps/web/dist/assets/changelog-CATQFxCQ.js +0 -1
  67. 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.5.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
- if (allowed.has("browser")) {
147
- Object.assign(tools, await browserTools(ctx, assets, files));
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
  },