@engenty/wizards-runtime 0.7.5 → 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 (82) hide show
  1. package/apps/runtime/dist/authoring/guide.js +5 -1
  2. package/apps/runtime/dist/cli/environment.js +2 -0
  3. package/apps/runtime/dist/cli/main.js +10 -1
  4. package/apps/runtime/dist/cli/sandbox.js +77 -0
  5. package/apps/runtime/dist/cli/status.js +9 -0
  6. package/apps/runtime/dist/debug.js +2 -1
  7. package/apps/runtime/dist/docs/dev/index.md +6 -1
  8. package/apps/runtime/dist/docs/dev/integration/channels.md +11 -0
  9. package/apps/runtime/dist/docs/dev/integration/claude-code-plugin.md +10 -0
  10. package/apps/runtime/dist/docs/dev/integration/embed.md +10 -0
  11. package/apps/runtime/dist/docs/dev/integration/index.md +36 -0
  12. package/apps/runtime/dist/docs/dev/integration/link-and-share.md +10 -0
  13. package/apps/runtime/dist/docs/dev/integration/mcp-apps.md +10 -0
  14. package/apps/runtime/dist/docs/dev/integration/mcp.md +10 -0
  15. package/apps/runtime/dist/docs/dev/integration/meta.json +20 -0
  16. package/apps/runtime/dist/docs/dev/integration/openui.md +181 -0
  17. package/apps/runtime/dist/docs/dev/integration/own-ui.md +10 -0
  18. package/apps/runtime/dist/docs/dev/integration/results.md +11 -0
  19. package/apps/runtime/dist/docs/dev/integration/wordpress.md +10 -0
  20. package/apps/runtime/dist/docs/dev/meta.json +1 -1
  21. package/apps/runtime/dist/docs/user/command-line.md +3 -0
  22. package/apps/runtime/dist/docs/user/server.md +1 -1
  23. package/apps/runtime/dist/engine/steps.js +2 -1
  24. package/apps/runtime/dist/env.js +10 -26
  25. package/apps/runtime/dist/routes/runs.js +14 -1
  26. package/apps/runtime/dist/sandbox/agentos-package.js +73 -0
  27. package/apps/runtime/dist/sandbox/agentos.js +162 -0
  28. package/apps/runtime/dist/sandbox/docker.js +32 -62
  29. package/apps/runtime/dist/sandbox/index.js +37 -8
  30. package/apps/runtime/dist/sandbox/install.js +122 -0
  31. package/apps/runtime/dist/sandbox/mastra.js +71 -0
  32. package/apps/runtime/dist/services/spaces.js +2 -1
  33. package/apps/runtime/dist/tools/code.js +133 -0
  34. package/apps/runtime/dist/tools/connector.js +6 -0
  35. package/apps/runtime/dist/tools/index.js +14 -3
  36. package/apps/web/dist/assets/{AboutDialog-D4mvc-zu.js → AboutDialog-BP8-Ugxf.js} +1 -1
  37. package/apps/web/dist/assets/{ChangelogDialog-DLqpJsF6.js → ChangelogDialog-CD6CQNW9.js} +1 -1
  38. package/apps/web/dist/assets/{ChatView-3laypRcE.js → ChatView-4T0QarEr.js} +1 -1
  39. package/apps/web/dist/assets/{EditorPage-BalA5U2Y.js → EditorPage-CWdV7Xdn.js} +3 -3
  40. package/apps/web/dist/assets/{FlowPreview-CusP7ci9.js → FlowPreview-Dj2gAfuf.js} +1 -1
  41. package/apps/web/dist/assets/{Harness-iYLtOLaK.js → Harness-SLbsntQF.js} +1 -1
  42. package/apps/web/dist/assets/{NewWizardChannels-CbwJ9A56.js → NewWizardChannels-CsDMlji7.js} +1 -1
  43. package/apps/web/dist/assets/{NewWizardPage-BOBYiB9i.js → NewWizardPage-_bFYjlgh.js} +1 -1
  44. package/apps/web/dist/assets/{OnboardingPage-CUwCiVIr.js → OnboardingPage-DObQeCDg.js} +1 -1
  45. package/apps/web/dist/assets/{ProjectPage-Bb16MdYG.js → ProjectPage-DX-oNLWy.js} +1 -1
  46. package/apps/web/dist/assets/{PublicRunner-BCt7Q6C-.js → PublicRunner-Bn9rVNEb.js} +1 -1
  47. package/apps/web/dist/assets/{ReadOnly-kIHX2TEy.js → ReadOnly-CK0piLUV.js} +1 -1
  48. package/apps/web/dist/assets/{RunDrawer-hZ9oeSXu.js → RunDrawer-CVMTLse_.js} +1 -1
  49. package/apps/web/dist/assets/{RunnerView-DUS9fPmx.js → RunnerView-Cjhuu9ua.js} +1 -1
  50. package/apps/web/dist/assets/{SettingsPage-BzwR50fi.js → SettingsPage-DTzuiSAj.js} +1 -1
  51. package/apps/web/dist/assets/{SetupPage-gAp7rPxp.js → SetupPage-D9jlhgiP.js} +1 -1
  52. package/apps/web/dist/assets/{SharePage-DqZWHva_.js → SharePage-Cc3Opj2F.js} +1 -1
  53. package/apps/web/dist/assets/changelog-CJNkd1_L.js +1 -0
  54. package/apps/web/dist/assets/{esm-DXeMu8Lb.js → esm-CfnFE_jE.js} +1 -1
  55. package/apps/web/dist/assets/{index-C5Sm7tTR.js → index-B5F1_qA_.js} +4 -4
  56. package/apps/web/dist/assets/{lock-R_CfU0Xo.js → lock-DAvRJY4B.js} +1 -1
  57. package/apps/web/dist/assets/oss-credits-X2JPiMOR.js +1 -0
  58. package/apps/web/dist/assets/{phone-CgrIS4_-.js → phone-BWOB67tX.js} +1 -1
  59. package/apps/web/dist/assets/{settings-menu-2IMOOlLl.js → settings-menu-CEQlVs1O.js} +1 -1
  60. package/apps/web/dist/assets/{ui-dzGvc3xk.js → ui-BmR_4eL2.js} +1 -1
  61. package/apps/web/dist/assets/{upload-BSuTilFF.js → upload-pBKn2EFp.js} +1 -1
  62. package/apps/web/dist/index.html +2 -2
  63. package/apps/web/dist/mcp/flow.html +2 -2
  64. package/node_modules/@engenty/wizards/dist/openui/Wizard.d.ts +20 -0
  65. package/node_modules/@engenty/wizards/dist/openui/Wizard.js +62 -0
  66. package/node_modules/@engenty/wizards/dist/openui/index.d.ts +9 -0
  67. package/node_modules/@engenty/wizards/dist/openui/index.js +9 -0
  68. package/node_modules/@engenty/wizards/dist/openui/message.d.ts +11 -0
  69. package/node_modules/@engenty/wizards/dist/openui/message.js +69 -0
  70. package/node_modules/@engenty/wizards/dist/openui/run.d.ts +8 -0
  71. package/node_modules/@engenty/wizards/dist/openui/run.js +137 -0
  72. package/node_modules/@engenty/wizards/dist/openui/server.d.ts +37 -0
  73. package/node_modules/@engenty/wizards/dist/openui/server.js +77 -0
  74. package/node_modules/@engenty/wizards/package.json +4 -1
  75. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +4 -1
  76. package/node_modules/@engenty-wizards/shared/dist/definition.js +1 -0
  77. package/node_modules/@engenty-wizards/shared/dist/fit.js +7 -2
  78. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +14 -0
  79. package/node_modules/@engenty-wizards/shared/dist/run.js +2 -0
  80. package/package.json +4 -3
  81. package/apps/web/dist/assets/changelog-DugmCNSf.js +0 -1
  82. package/apps/web/dist/assets/oss-credits-cZ9NUaLZ.js +0 -1
@@ -0,0 +1,20 @@
1
+ import { type Lang, type MascotProps } from "@engenty-wizards/runner";
2
+ export interface WizardsConfig {
3
+ /**
4
+ * Where the runtime answers, as the browser reaches it: a path the host proxies to it
5
+ * (`/wizards-runtime`, so the runner's cookie stays first-party) or the runtime's address.
6
+ */
7
+ runtime: string;
8
+ /** The language of the runner's own words: `en` unless given. */
9
+ lang?: Lang;
10
+ /** The wizard's mascot above its pages; a plain dot in the wizard's colour unless given. */
11
+ Mascot?: (props: MascotProps) => React.ReactNode;
12
+ }
13
+ /** Names the runtime the Wizard component talks to. Once, before the first render. */
14
+ export declare function configureWizards(config: WizardsConfig): void;
15
+ /** The component an OpenUI library gets: `Wizard("<token>", "<note>", {prefill})`. */
16
+ export declare const Wizard: import("@openuidev/react-lang").DefinedComponent<import("zod").ZodObject<{
17
+ wizard: import("zod").ZodString;
18
+ note: import("zod").ZodOptional<import("zod").ZodString>;
19
+ prefill: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
20
+ }, import("zod/v4/core").$strip>>;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { api, configureRunner, RunnerBody, } from "@engenty-wizards/runner";
3
+ import { defineComponent } from "@openuidev/react-lang";
4
+ import { useEffect, useRef } from "react";
5
+ import { useMessageRun, useReport } from "./message.js";
6
+ import { useWizardRun } from "./run.js";
7
+ import { WIZARD_COMPONENT, WIZARD_DESCRIPTION, wizardProps } from "./server.js";
8
+ /** A plain stand-in for the studio's engenty. */
9
+ function Dot({ size = 64 }) {
10
+ const d = Math.min(size, 72);
11
+ return (_jsx("span", { "aria-hidden": "true", className: "inline-block rounded-full bg-ember", style: { width: d * 0.6, height: d * 0.6 } }));
12
+ }
13
+ /** Names the runtime the Wizard component talks to. Once, before the first render. */
14
+ export function configureWizards(config) {
15
+ const base = config.runtime.replace(/\/+$/, "");
16
+ configureRunner({
17
+ lang: config.lang ?? "en",
18
+ runBase: (runId) => `/api/runs/${runId}`,
19
+ withBase: (path) => `${base}${path}`,
20
+ asset: (path) => `${base}/${path.replace(/^\/+/, "")}`,
21
+ Mascot: config.Mascot ?? Dot,
22
+ });
23
+ }
24
+ /** Starts a run of the published wizard behind `token`, as a visitor of the runtime. */
25
+ const startRun = (token) => api
26
+ .post(`/api/public/wizards/${encodeURIComponent(token)}/runs`, {})
27
+ .then((started) => started.runId);
28
+ /** What the chat hears when a run is done: the result's texts, each cut short. */
29
+ function resultContext(view) {
30
+ const results = Object.entries(view.outputs)
31
+ .filter(([, out]) => typeof out.text === "string" && out.text.trim())
32
+ .map(([step, out]) => ({ step, text: (out.text ?? "").slice(0, 4000) }));
33
+ return { wizard: view.wizard.title, runId: view.id, results };
34
+ }
35
+ function WizardRun({ props }) {
36
+ const { runId, failed } = useMessageRun(props.wizard, startRun);
37
+ const run = useWizardRun(runId, props.prefill);
38
+ const report = useReport();
39
+ // The chat hears of a run it saw finish. A message shown again with a done run tells nothing.
40
+ const view = run.view;
41
+ const seenRunning = useRef(false);
42
+ useEffect(() => {
43
+ if (!view) {
44
+ return;
45
+ }
46
+ if (view.status !== "done") {
47
+ seenRunning.current = true;
48
+ }
49
+ else if (seenRunning.current) {
50
+ seenRunning.current = false;
51
+ report(`The wizard “${view.wizard.title}” finished.`, resultContext(view));
52
+ }
53
+ }, [view, report]);
54
+ return (_jsxs("div", { className: "engenty-runner overflow-hidden rounded-2xl border border-paper-3 bg-paper", children: [props.note ? _jsx("p", { className: "px-5 pt-4 text-[0.9375rem] text-ink-2", children: props.note }) : null, _jsx("div", { className: "px-5 pb-6", children: failed ? (_jsx("p", { className: "py-10 text-center text-ink-3", children: failed })) : (_jsx(RunnerBody, { run: run, compact: true })) })] }));
55
+ }
56
+ /** The component an OpenUI library gets: `Wizard("<token>", "<note>", {prefill})`. */
57
+ export const Wizard = defineComponent({
58
+ name: WIZARD_COMPONENT,
59
+ description: WIZARD_DESCRIPTION,
60
+ props: wizardProps,
61
+ component: ({ props }) => _jsx(WizardRun, { props: props }),
62
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Wizards in OpenUI chats: the Wizard component runs a published wizard inside the chat, page
3
+ * by page, as its author defined it; the chat's model only decides that it runs and what it
4
+ * already knows. `server.ts` (`@engenty/wizards/openui/server`) is the server's half: the rules
5
+ * and the list of wizards.
6
+ */
7
+ export { useWizardRun } from "./run.js";
8
+ export { addWizard, loadWizards, WIZARD_COMPONENT, WIZARD_DESCRIPTION, type WizardInfo, type WizardProps, WizardSpec, wizardProps, wizardRules, } from "./server.js";
9
+ export { configureWizards, Wizard, type WizardsConfig } from "./Wizard.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Wizards in OpenUI chats: the Wizard component runs a published wizard inside the chat, page
3
+ * by page, as its author defined it; the chat's model only decides that it runs and what it
4
+ * already knows. `server.ts` (`@engenty/wizards/openui/server`) is the server's half: the rules
5
+ * and the list of wizards.
6
+ */
7
+ export { useWizardRun } from "./run.js";
8
+ export { addWizard, loadWizards, WIZARD_COMPONENT, WIZARD_DESCRIPTION, WizardSpec, wizardProps, wizardRules, } from "./server.js";
9
+ export { configureWizards, Wizard } from "./Wizard.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The run this message shows: the one it kept, else a new one from `start`. A run starts once
3
+ * the reply has finished streaming, as the token can be half written until then, and once per
4
+ * message, however often React runs the effect (twice in Strict Mode).
5
+ */
6
+ export declare function useMessageRun(token: string | undefined, start: (token: string) => Promise<string>): {
7
+ runId: string | null;
8
+ failed: string | null;
9
+ };
10
+ /** Tells the chat something as a turn of the person, with `context` for the model. */
11
+ export declare function useReport(): (message: string, context: unknown) => void;
@@ -0,0 +1,69 @@
1
+ import { useGetFieldValue, useIsStreaming, useSetFieldValue, useTriggerAction, } from "@openuidev/react-lang";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { WIZARD_COMPONENT } from "./server.js";
4
+ /**
5
+ * What the Wizard component keeps with its message, in OpenUI's state of the message.
6
+ *
7
+ * OpenUI keeps that state as the fields of forms, saves it with the message and restores it when
8
+ * the message is shown again. An action hands the chat the fields of the form it names, or every
9
+ * field where it names none; the chat shows them as “Form data” and gives them to the model. So
10
+ * the run id is a field of its own, which no action names, and the action that reports the
11
+ * result names a form that holds nothing.
12
+ */
13
+ const runField = (token) => `wizard:${token}`;
14
+ const EMPTY_FORM = "wizard:empty";
15
+ /**
16
+ * The run this message shows: the one it kept, else a new one from `start`. A run starts once
17
+ * the reply has finished streaming, as the token can be half written until then, and once per
18
+ * message, however often React runs the effect (twice in Strict Mode).
19
+ */
20
+ export function useMessageRun(token, start) {
21
+ const streaming = useIsStreaming();
22
+ const getField = useGetFieldValue();
23
+ const setField = useSetFieldValue();
24
+ const [runId, setRunId] = useState(null);
25
+ const [failed, setFailed] = useState(null);
26
+ const started = useRef(null);
27
+ useEffect(() => {
28
+ if (streaming || runId || failed || !token) {
29
+ return;
30
+ }
31
+ let cancelled = false;
32
+ const take = async () => {
33
+ // OpenUI restores the message's state in an effect of its own, which runs after this one.
34
+ await Promise.resolve();
35
+ if (cancelled) {
36
+ return;
37
+ }
38
+ const kept = getField(undefined, runField(token));
39
+ if (typeof kept === "string") {
40
+ setRunId(kept);
41
+ return;
42
+ }
43
+ started.current ??= start(token);
44
+ const id = await started.current;
45
+ if (!cancelled) {
46
+ setRunId(id);
47
+ setField(undefined, WIZARD_COMPONENT, runField(token), id, true);
48
+ }
49
+ };
50
+ take().catch((err) => {
51
+ if (!cancelled) {
52
+ setFailed(err instanceof Error ? err.message : String(err));
53
+ }
54
+ });
55
+ return () => {
56
+ cancelled = true;
57
+ };
58
+ }, [streaming, runId, failed, token, getField, setField, start]);
59
+ return { runId, failed };
60
+ }
61
+ /** Tells the chat something as a turn of the person, with `context` for the model. */
62
+ export function useReport() {
63
+ const setField = useSetFieldValue();
64
+ const trigger = useTriggerAction();
65
+ return useCallback((message, context) => {
66
+ setField(EMPTY_FORM, undefined, "empty", undefined, false);
67
+ void trigger(message, EMPTY_FORM, { type: "continue_conversation", params: { context } });
68
+ }, [setField, trigger]);
69
+ }
@@ -0,0 +1,8 @@
1
+ import { type RunSource } from "@engenty-wizards/runner";
2
+ /**
3
+ * One run, live: the runtime's stream of the whole view, plus the commands a person gives. The
4
+ * same as the studio's `useRun` (apps/web/src/runner/useRun.ts), on the runner's own client, so
5
+ * it talks to whichever runtime `configureWizards` named. `prefill` starts a page's fields with
6
+ * answers the chat already knows; the person still sees and sends the page.
7
+ */
8
+ export declare function useWizardRun(runId: string | null, prefill: Record<string, unknown> | undefined): RunSource;
@@ -0,0 +1,137 @@
1
+ import { ApiError, api, runnerEnv, setChoiceLabels } from "@engenty-wizards/runner";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ /** Two missed pings: the stream is taken for dead. */
4
+ const STALE_MS = 45_000;
5
+ /**
6
+ * One run, live: the runtime's stream of the whole view, plus the commands a person gives. The
7
+ * same as the studio's `useRun` (apps/web/src/runner/useRun.ts), on the runner's own client, so
8
+ * it talks to whichever runtime `configureWizards` named. `prefill` starts a page's fields with
9
+ * answers the chat already knows; the person still sees and sends the page.
10
+ */
11
+ export function useWizardRun(runId, prefill) {
12
+ const [view, setView] = useState(null);
13
+ const [error, setError] = useState(null);
14
+ const [notFound, setNotFound] = useState(false);
15
+ const [busy, setBusy] = useState(false);
16
+ const [fieldErrors, setFieldErrors] = useState({});
17
+ useEffect(() => {
18
+ setView(null);
19
+ setNotFound(false);
20
+ if (!runId) {
21
+ return;
22
+ }
23
+ const { runBase, withBase } = runnerEnv();
24
+ let stopped = false;
25
+ let retry = 0;
26
+ let timer;
27
+ let es = null;
28
+ let lastHeard = Date.now();
29
+ const refresh = async () => {
30
+ try {
31
+ const fresh = await api.get(runBase(runId));
32
+ if (!stopped) {
33
+ setView(fresh);
34
+ }
35
+ }
36
+ catch (err) {
37
+ if (err instanceof ApiError && err.status === 404) {
38
+ stopped = true;
39
+ es?.close();
40
+ setNotFound(true);
41
+ }
42
+ }
43
+ };
44
+ const open = () => {
45
+ clearTimeout(timer);
46
+ es?.close();
47
+ if (stopped) {
48
+ return;
49
+ }
50
+ const stream = new EventSource(withBase(`${runBase(runId)}/stream`), {
51
+ withCredentials: true,
52
+ });
53
+ es = stream;
54
+ lastHeard = Date.now();
55
+ stream.addEventListener("view", (e) => {
56
+ retry = 0;
57
+ lastHeard = Date.now();
58
+ setView(JSON.parse(e.data));
59
+ });
60
+ stream.addEventListener("ping", () => {
61
+ lastHeard = Date.now();
62
+ });
63
+ stream.onerror = () => {
64
+ stream.close();
65
+ if (stopped || es !== stream) {
66
+ return;
67
+ }
68
+ es = null;
69
+ void refresh();
70
+ retry = Math.min(retry + 1, 6);
71
+ timer = setTimeout(open, 500 * 2 ** retry);
72
+ };
73
+ };
74
+ const watchdog = setInterval(() => {
75
+ if (!stopped && es && Date.now() - lastHeard > STALE_MS) {
76
+ retry = 0;
77
+ open();
78
+ }
79
+ }, 15_000);
80
+ open();
81
+ return () => {
82
+ stopped = true;
83
+ clearTimeout(timer);
84
+ clearInterval(watchdog);
85
+ es?.close();
86
+ };
87
+ }, [runId]);
88
+ const command = useCallback(async (path, body) => {
89
+ if (!runId) {
90
+ return false;
91
+ }
92
+ setBusy(true);
93
+ setError(null);
94
+ setFieldErrors({});
95
+ try {
96
+ await api.post(`${runnerEnv().runBase(runId)}/${path}`, body);
97
+ return true;
98
+ }
99
+ catch (err) {
100
+ if (err instanceof ApiError && err.status === 422 && Array.isArray(err.body?.fields)) {
101
+ setFieldErrors(Object.fromEntries(err.body.fields.map((f) => [f.field, f.message])));
102
+ }
103
+ setError(err.message);
104
+ return false;
105
+ }
106
+ finally {
107
+ setBusy(false);
108
+ }
109
+ }, [runId]);
110
+ // The chat's answers start the page's fields, under what the run already holds.
111
+ const shown = useMemo(() => {
112
+ if (!view || !prefill || view.step?.type !== "page") {
113
+ return view;
114
+ }
115
+ const known = Object.fromEntries(view.step.fields.filter((f) => f.id in prefill).map((f) => [f.id, prefill[f.id]]));
116
+ return Object.keys(known).length ? { ...view, prefill: { ...known, ...view.prefill } } : view;
117
+ }, [view, prefill]);
118
+ setChoiceLabels(view?.translation?.labels);
119
+ return {
120
+ view: shown,
121
+ notFound,
122
+ error,
123
+ busy,
124
+ fieldErrors,
125
+ submitPage: (stepId, values) => command(`pages/${stepId}`, { values }),
126
+ accept: (stepId, edits) => command(`reviews/${stepId}`, { type: "accept", edits }),
127
+ regenerate: (stepId, target, note, items) => command(`reviews/${stepId}`, {
128
+ type: "regenerate",
129
+ target,
130
+ note,
131
+ items: items?.length ? items : undefined,
132
+ }),
133
+ back: () => command("back"),
134
+ retry: () => command("retry"),
135
+ translateResult: (stepId) => command("translate-result", { stepId }),
136
+ };
137
+ }
@@ -0,0 +1,37 @@
1
+ import type { PublicField } from "@engenty-wizards/shared/run";
2
+ import { type Library } from "@openuidev/react-lang";
3
+ import { z } from "zod";
4
+ /**
5
+ * The model's half of the Wizard component: its props, its description and the rules a system
6
+ * prompt gets. Nothing here draws, so a chat server builds its prompt from this file alone.
7
+ */
8
+ export declare const WIZARD_COMPONENT = "Wizard";
9
+ export declare const wizardProps: z.ZodObject<{
10
+ wizard: z.ZodString;
11
+ note: z.ZodOptional<z.ZodString>;
12
+ prefill: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13
+ }, z.core.$strip>;
14
+ export type WizardProps = z.infer<typeof wizardProps>;
15
+ export declare const WIZARD_DESCRIPTION = "Runs a predefined wizard inline: its pages, AI steps, review and result, exactly as its author defined them. Use it whenever a listed wizard fits the request; never rebuild a wizard's pages with Form components.";
16
+ /** The component as a prompt sees it: the same name and props, nothing to draw. */
17
+ export declare const WizardSpec: import("@openuidev/react-lang").DefinedComponent<z.ZodObject<{
18
+ wizard: z.ZodString;
19
+ note: z.ZodOptional<z.ZodString>;
20
+ prefill: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
21
+ }, z.core.$strip>>;
22
+ /**
23
+ * The library with the Wizard component among a Card's children. A server passes `WizardSpec`
24
+ * for its prompt, the browser the drawing `Wizard`; both make the same prompt.
25
+ */
26
+ export declare function addWizard<L extends Library>(library: L, component?: typeof WizardSpec): Library;
27
+ /** A wizard as the chat server lists it to the model. */
28
+ export interface WizardInfo {
29
+ token: string;
30
+ title: string;
31
+ description: string;
32
+ fields: PublicField[];
33
+ }
34
+ /** Reads the published wizards behind these tokens from a runtime; ones it cannot read are left out. */
35
+ export declare function loadWizards(runtimeUrl: string, tokens: string[]): Promise<WizardInfo[]>;
36
+ /** The rules a system prompt adds for the Wizard component: when to use it and which wizards exist. */
37
+ export declare function wizardRules(wizards: WizardInfo[]): string[];
@@ -0,0 +1,77 @@
1
+ import { defineComponent } from "@openuidev/react-lang";
2
+ import { z } from "zod";
3
+ /**
4
+ * The model's half of the Wizard component: its props, its description and the rules a system
5
+ * prompt gets. Nothing here draws, so a chat server builds its prompt from this file alone.
6
+ */
7
+ export const WIZARD_COMPONENT = "Wizard";
8
+ export const wizardProps = z.object({
9
+ wizard: z.string().describe("The token of a wizard from the list of available wizards"),
10
+ note: z
11
+ .string()
12
+ .optional()
13
+ .describe("One short sentence shown above the wizard: why it starts now"),
14
+ prefill: z
15
+ .record(z.string(), z.unknown())
16
+ .optional()
17
+ .describe("Answers already known from the conversation, by field id; the person still checks them"),
18
+ });
19
+ export const WIZARD_DESCRIPTION = "Runs a predefined wizard inline: its pages, AI steps, review and result, exactly as its author defined them. Use it whenever a listed wizard fits the request; never rebuild a wizard's pages with Form components.";
20
+ /** The component as a prompt sees it: the same name and props, nothing to draw. */
21
+ export const WizardSpec = defineComponent({
22
+ name: WIZARD_COMPONENT,
23
+ description: WIZARD_DESCRIPTION,
24
+ props: wizardProps,
25
+ component: () => null,
26
+ });
27
+ /**
28
+ * The library with the Wizard component among a Card's children. A server passes `WizardSpec`
29
+ * for its prompt, the browser the drawing `Wizard`; both make the same prompt.
30
+ */
31
+ export function addWizard(library, component = WizardSpec) {
32
+ if (!library.extend) {
33
+ throw new Error("This OpenUI library cannot be extended.");
34
+ }
35
+ return library.extend({ components: { add: [{ component, slots: ["Card"] }] } });
36
+ }
37
+ /** Reads the published wizards behind these tokens from a runtime; ones it cannot read are left out. */
38
+ export async function loadWizards(runtimeUrl, tokens) {
39
+ const base = runtimeUrl.replace(/\/+$/, "");
40
+ const found = await Promise.all(tokens.map(async (token) => {
41
+ try {
42
+ const res = await fetch(`${base}/api/public/wizards/${encodeURIComponent(token)}`);
43
+ if (!res.ok) {
44
+ return null;
45
+ }
46
+ const w = (await res.json());
47
+ return w.available
48
+ ? { token: w.token, title: w.title, description: w.description, fields: w.fields ?? [] }
49
+ : null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }));
55
+ return found.filter((w) => w !== null);
56
+ }
57
+ function fieldLine(f) {
58
+ const choices = f.options?.length
59
+ ? ` one of ${f.options.map((o) => JSON.stringify(o)).join(" | ")}`
60
+ : "";
61
+ return `${f.id} (${f.kind}${choices}${f.required ? ", required" : ""}): ${f.label}`;
62
+ }
63
+ /** The rules a system prompt adds for the Wizard component: when to use it and which wizards exist. */
64
+ export function wizardRules(wizards) {
65
+ if (!wizards.length) {
66
+ return [];
67
+ }
68
+ const list = wizards
69
+ .map((w) => `- "${w.title}" token: ${w.token} — ${w.description}\n fields: ${w.fields.map(fieldLine).join("; ") || "none"}`)
70
+ .join("\n");
71
+ return [
72
+ `Wizards are fixed workflows with their own pages, AI steps and results. When the person asks for something a wizard below does, answer with a Card that holds one short TextContent and Wizard("<token>", "<note>", {prefill}). Do not ask the wizard's questions yourself and do not build them as a Form: the wizard asks them.`,
73
+ `Prefill only answers the person has actually given in this conversation, by field id; leave out anything you would have to guess. Dates are YYYY-MM-DD.`,
74
+ `When a message says a wizard finished, its context holds the result: answer in plain words about it, and start a wizard again only when asked.`,
75
+ `Available wizards:\n${list}`,
76
+ ];
77
+ }
@@ -6,6 +6,9 @@
6
6
  "exports": {
7
7
  "./plugin": "./dist/plugin/index.js",
8
8
  "./plugin/studio": "./dist/plugin/studio.js",
9
- "./plugin/thread": "./dist/plugin/thread.js"
9
+ "./plugin/thread": "./dist/plugin/thread.js",
10
+ "./openui": "./dist/openui/index.js",
11
+ "./openui/server": "./dist/openui/server.js",
12
+ "./openui/styles.css": "./src/openui/styles.css"
10
13
  }
11
14
  }
@@ -95,7 +95,7 @@ export declare function isAudioValue(v: unknown): v is AudioValue;
95
95
  export declare function locationText(v: LocationValue): string;
96
96
  /** A field takes at most this many files. */
97
97
  export declare const MAX_FILES = 30;
98
- export declare const TOOL_IDS: readonly ["web_search", "web_fetch", "browser", "sandbox", "image", "http", "pages"];
98
+ export declare const TOOL_IDS: readonly ["web_search", "web_fetch", "browser", "sandbox", "image", "http", "pages", "code"];
99
99
  /**
100
100
  * A tool a plugin of the runtime adds: `<plugin id>.<tool name>`. A wizard that names one runs
101
101
  * only where that plugin is installed.
@@ -532,6 +532,7 @@ export declare const agentStepSchema: z.ZodObject<{
532
532
  }, z.core.$strip>>;
533
533
  tools: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
534
534
  browser: "browser";
535
+ code: "code";
535
536
  http: "http";
536
537
  image: "image";
537
538
  pages: "pages";
@@ -1278,6 +1279,7 @@ export declare const stepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1278
1279
  }, z.core.$strip>>;
1279
1280
  tools: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
1280
1281
  browser: "browser";
1282
+ code: "code";
1281
1283
  http: "http";
1282
1284
  image: "image";
1283
1285
  pages: "pages";
@@ -2277,6 +2279,7 @@ export declare const wizardSchema: z.ZodObject<{
2277
2279
  }, z.core.$strip>>;
2278
2280
  tools: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
2279
2281
  browser: "browser";
2282
+ code: "code";
2280
2283
  http: "http";
2281
2284
  image: "image";
2282
2285
  pages: "pages";
@@ -297,6 +297,7 @@ export const TOOL_IDS = [
297
297
  "image",
298
298
  "http",
299
299
  "pages",
300
+ "code",
300
301
  ];
301
302
  /**
302
303
  * A tool a plugin of the runtime adds: `<plugin id>.<tool name>`. A wizard that names one runs
@@ -2,6 +2,11 @@
2
2
  import { conditionsOf, dataRef, isOtherwise, listRef, pluginToolOf, ruleMatches, templateRefs, } from "./definition.js";
3
3
  /** What the standalone runner can hand over of a thinking step's text itself. */
4
4
  const BASE_FORMATS = new Set(["html", "md", "txt", "csv", "json"]);
5
+ /**
6
+ * A program over the step's tools: where programs do not run, the step calls its tools one by
7
+ * one and loses nothing. No need at all.
8
+ */
9
+ const OPTIONAL_TOOLS = new Set(["code"]);
5
10
  /** Tools the standalone runner has no way to run; `pages` only reads the space's pages, which it lacks. */
6
11
  const HARD_TOOLS = new Set(["web_search", "browser", "sandbox", "image"]);
7
12
  /**
@@ -99,8 +104,8 @@ export function needs(step, def) {
99
104
  if (plugin) {
100
105
  add(`plugin:${plugin.plugin}`, true);
101
106
  }
102
- else if (RUNNER_TOOLS.has(tool)) {
103
- // The site runs it.
107
+ else if (RUNNER_TOOLS.has(tool) || OPTIONAL_TOOLS.has(tool)) {
108
+ // The site runs it, or the step does without.
104
109
  }
105
110
  else if (HARD_TOOLS.has(tool)) {
106
111
  add(`tool:${tool}`, true);
@@ -214,6 +214,7 @@ declare const NOTES_EN: {
214
214
  readonly downloads: "Downloading {name}";
215
215
  readonly runs: "Running: {command}";
216
216
  readonly python: "Running Python";
217
+ readonly runsProgram: "Working through the data in one go";
217
218
  readonly drawing: "Drawing an image";
218
219
  readonly writesPage: "Writing the page “{title}”";
219
220
  readonly searchesDocuments: "Searching the documents: {query}";
@@ -634,6 +635,19 @@ export interface PublicWizard {
634
635
  translatable: boolean;
635
636
  /** The page in the viewer's language, where it was asked for (`?lang=`); null: the original. */
636
637
  translation: Omit<RunTranslation, "results"> | null;
638
+ /**
639
+ * What the wizard's pages ask, in order: an AI app that starts the wizard (the OpenUI
640
+ * component) can fill in what it already knows. The person still sees and sends each page.
641
+ */
642
+ fields: PublicField[];
643
+ }
644
+ /** A field of a wizard's page, as `PublicWizard.fields` lists it. */
645
+ export interface PublicField {
646
+ id: string;
647
+ label: string;
648
+ kind: string;
649
+ options?: string[];
650
+ required?: boolean;
637
651
  }
638
652
  /** The runner's own words in a language outside `LANGS`: `GET /api/public/texts/:lang`. */
639
653
  export interface UiTexts {
@@ -50,6 +50,7 @@ const NOTES_EN = {
50
50
  downloads: "Downloading {name}",
51
51
  runs: "Running: {command}",
52
52
  python: "Running Python",
53
+ runsProgram: "Working through the data in one go",
53
54
  drawing: "Drawing an image",
54
55
  writesPage: "Writing the page “{title}”",
55
56
  searchesDocuments: "Searching the documents: {query}",
@@ -339,6 +340,7 @@ const NOTES_DE = {
339
340
  waitsForSignIn: "Wartet auf deine Anmeldung bei {name}",
340
341
  downloads: "Lädt {name} herunter",
341
342
  runs: "Führt aus: {command}",
343
+ runsProgram: "Arbeitet die Daten in einem Zug durch",
342
344
  python: "Führt Python aus",
343
345
  drawing: "Zeichnet ein Bild",
344
346
  writesPage: "Schreibt die Seite „{title}“",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engenty/wizards-runtime",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "Make a wish. Get your wizard. It guides you step by step. Let AI do the work. Runs on your machine, on the AI subscription you already have. The runtime, which `npx wizards` installs.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "homepage": "https://engenty.ai",
@@ -42,6 +42,7 @@
42
42
  "@mastra/core": "1.77.0",
43
43
  "@mastra/docker": "0.8.0",
44
44
  "@mastra/mcp": "2.4.0",
45
+ "@mastra/quickjs": "0.1.3",
45
46
  "@modelcontextprotocol/client": "2.2.0",
46
47
  "@modelcontextprotocol/ext-apps": "2.0.3",
47
48
  "@modelcontextprotocol/server": "2.2.0",
@@ -73,7 +74,7 @@
73
74
  "node-pty": "1.1.0"
74
75
  },
75
76
  "bundleDependencies": [
76
- "@engenty/wizards",
77
- "@engenty-wizards/shared"
77
+ "@engenty-wizards/shared",
78
+ "@engenty/wizards"
78
79
  ]
79
80
  }