@engenty/wizards-runtime 0.7.8 → 0.7.9
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/clients.js +118 -0
- package/apps/runtime/dist/db/client.js +37 -3
- package/apps/runtime/dist/db/migrations/tenant/0017_run_client.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0017_snapshot.json +3333 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +7 -0
- package/apps/runtime/dist/db/schema.js +2 -0
- package/apps/runtime/dist/docs/dev/api.md +39 -0
- package/apps/runtime/dist/docs/dev/index.md +1 -0
- package/apps/runtime/dist/docs/dev/integration/openui.md +5 -3
- package/apps/runtime/dist/docs/dev/meta.json +1 -1
- package/apps/runtime/dist/docs/user/ai-apps.md +14 -3
- package/apps/runtime/dist/engine/runner.js +1 -0
- package/apps/runtime/dist/index.js +13 -4
- package/apps/runtime/dist/plugin.js +24 -8
- package/apps/runtime/dist/routes/runs.js +9 -0
- package/apps/web/dist/assets/AboutDialog-CShc0mRo.js +1 -0
- package/apps/web/dist/assets/ChangelogDialog-C4dVSwtN.js +1 -0
- package/apps/web/dist/assets/ChatView-KkOboyOo.js +10 -0
- package/apps/web/dist/assets/EditorPage-lQ3YgsFT.js +4 -0
- package/apps/web/dist/assets/{FlowPreview-Dx8AjXvE.js → FlowPreview-oX_Emm7u.js} +1 -1
- package/apps/web/dist/assets/{Harness-NvIb36TG.js → Harness-D6mgLWTb.js} +2 -2
- package/apps/web/dist/assets/{NewWizardChannels-B2YgtJlt.js → NewWizardChannels-CPlh_37J.js} +1 -1
- package/apps/web/dist/assets/NewWizardPage-D0FVJtXK.js +7 -0
- package/apps/web/dist/assets/OnboardingPage-BXPOMrQR.js +1 -0
- package/apps/web/dist/assets/ProjectPage-DroTprqs.js +1 -0
- package/apps/web/dist/assets/PublicRunner-j3qoKjQQ.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-CGNHlgUT.js → ReadOnly-CKgSQ2z1.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-CJZ_ISAd.js +4 -0
- package/apps/web/dist/assets/RunnerView-BW1h4x63.js +1 -0
- package/apps/web/dist/assets/SettingsPage-BHZ0Z-og.js +1 -0
- package/apps/web/dist/assets/SetupPage-CDkYkjJX.js +1 -0
- package/apps/web/dist/assets/{SharePage-hG7Z6SLy.js → SharePage-BlVjVjHG.js} +1 -1
- package/apps/web/dist/assets/changelog-Be0i5Jjw.js +1 -0
- package/apps/web/dist/assets/engenty-WpMi3xwq.js +1 -0
- package/apps/web/dist/assets/{esm-DVk3RJij.js → esm-C-ZWeuhU.js} +1 -1
- package/apps/web/dist/assets/{fluffy-engenty-ClRou7yu.js → fluffy-engenty-DPNHytrz.js} +135 -16
- package/apps/web/dist/assets/goose-helmet-Cvc7KjUg.bin +0 -0
- package/apps/web/dist/assets/goose-helmet-livery-CZD9u0XS.webp +0 -0
- package/apps/web/dist/assets/index-C4aHMcg9.css +1 -0
- package/apps/web/dist/assets/index-C61HAltK.js +14 -0
- package/apps/web/dist/assets/oss-credits-qGhcbqiM.js +1 -0
- package/apps/web/dist/assets/{phone-CXXHXYnA.js → phone-BjmhIKuT.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-DXzZCIMx.js → settings-menu-Dpkp3f56.js} +1 -1
- package/apps/web/dist/assets/{ui-DQ4L2npj.js → ui-DMmT-cSO.js} +4 -4
- package/apps/web/dist/assets/{upload-BnYHIfu5.js → upload-JX9BcMTq.js} +1 -1
- package/apps/web/dist/assets/{wrench-BrUJ7SV6.js → wrench-BSxnPs2_.js} +1 -1
- package/apps/web/dist/index.html +4 -4
- package/apps/web/dist/mcp/flow.html +141 -22
- package/node_modules/@engenty/wizards/package.json +1 -4
- package/node_modules/@engenty-wizards/shared/dist/claude-plugin.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/claude-plugin.js +10 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +2 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +4 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +28 -6
- package/plugin/README.md +58 -0
- package/plugin/hooks/engenty.ts +140 -0
- package/plugin/hooks/hooks.json +1 -0
- package/plugin/hooks/pane.test.tsx +177 -0
- package/plugin/hooks/register.tsx +396 -0
- package/plugin/types/index.d.ts +44 -0
- package/apps/web/dist/assets/AboutDialog-ByZyHXDj.js +0 -1
- package/apps/web/dist/assets/ChangelogDialog-QHYP0jAv.js +0 -1
- package/apps/web/dist/assets/ChatView-DupTtI8K.js +0 -10
- package/apps/web/dist/assets/EditorPage-k6qouLeS.js +0 -4
- package/apps/web/dist/assets/NewWizardPage-BgLY6SL6.js +0 -7
- package/apps/web/dist/assets/OnboardingPage-CxhwefJU.js +0 -1
- package/apps/web/dist/assets/ProjectPage-6ceSSS7W.js +0 -1
- package/apps/web/dist/assets/PublicRunner-6lQvReDx.js +0 -1
- package/apps/web/dist/assets/RunDrawer-TVGNnWyx.js +0 -4
- package/apps/web/dist/assets/RunnerView-DInSK0VZ.js +0 -1
- package/apps/web/dist/assets/SettingsPage-D3k5pXN5.js +0 -1
- package/apps/web/dist/assets/SetupPage-Bj5frdzY.js +0 -1
- package/apps/web/dist/assets/changelog-CO9vKjV0.js +0 -1
- package/apps/web/dist/assets/engenty-eXZ-EAwd.js +0 -1
- package/apps/web/dist/assets/index-DfaoPjNW.css +0 -1
- package/apps/web/dist/assets/index-DlDsr9Lc.js +0 -14
- package/apps/web/dist/assets/oss-credits-X2JPiMOR.js +0 -1
- package/node_modules/@engenty/wizards/dist/openui/Wizard.d.ts +0 -20
- package/node_modules/@engenty/wizards/dist/openui/Wizard.js +0 -62
- package/node_modules/@engenty/wizards/dist/openui/index.d.ts +0 -9
- package/node_modules/@engenty/wizards/dist/openui/index.js +0 -9
- package/node_modules/@engenty/wizards/dist/openui/message.d.ts +0 -11
- package/node_modules/@engenty/wizards/dist/openui/message.js +0 -69
- package/node_modules/@engenty/wizards/dist/openui/run.d.ts +0 -8
- package/node_modules/@engenty/wizards/dist/openui/run.js +0 -137
- package/node_modules/@engenty/wizards/dist/openui/server.d.ts +0 -37
- package/node_modules/@engenty/wizards/dist/openui/server.js +0 -77
- package/plugin/.mcp.json +0 -8
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { env } from "./env.js";
|
|
2
|
+
import { allowed } from "./limits.js";
|
|
3
|
+
import { managed } from "./manage.js";
|
|
4
|
+
/**
|
|
5
|
+
* The clients of the public run API: who starts runs here (docs/content/dev/api.md,
|
|
6
|
+
* docs/plans/api-clients.md). A client names itself in the `x-wizards-client` header: `web` (the
|
|
7
|
+
* runner in a browser), `app` (the mobile app), `cli`, or the id of a client registered at the
|
|
8
|
+
* Manage-App. The id is no secret: it carries the terms, the limits and the revocation of a
|
|
9
|
+
* client, not its proof. The run keeps it.
|
|
10
|
+
*
|
|
11
|
+
* A managed runtime fetches the list of clients from its Manage-App at the start and once an
|
|
12
|
+
* hour, and keeps the last list when a fetch fails. Without a list every client may start runs:
|
|
13
|
+
* the Manage-App being down must not close the wizards. A runtime that runs alone fetches
|
|
14
|
+
* nothing and refuses nothing — its API is the person's own.
|
|
15
|
+
*/
|
|
16
|
+
export const CLIENT_HEADER = "x-wizards-client";
|
|
17
|
+
/** The clients this repository ships: the runner, the mobile app, the command line. */
|
|
18
|
+
export const OWN_CLIENTS = ["web", "app", "cli"];
|
|
19
|
+
const ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
20
|
+
/** The client a request names, or null when it names none or a malformed one. */
|
|
21
|
+
export function clientOf(c) {
|
|
22
|
+
const id = c.req.header(CLIENT_HEADER)?.trim().toLowerCase() ?? "";
|
|
23
|
+
return ID.test(id) ? id : null;
|
|
24
|
+
}
|
|
25
|
+
let list = null;
|
|
26
|
+
/** The list in force; tests set it, a managed runtime fetches it. */
|
|
27
|
+
export function setClientList(next) {
|
|
28
|
+
list = next;
|
|
29
|
+
}
|
|
30
|
+
const HOUR = 3600_000;
|
|
31
|
+
/**
|
|
32
|
+
* Why `client` may not start a run now, or null when it may. Counts the try against the
|
|
33
|
+
* client's hourly cap, so call it once per start.
|
|
34
|
+
*/
|
|
35
|
+
export function clientRefused(client) {
|
|
36
|
+
if (!list) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const known = list.clients.find((c) => c.id === client);
|
|
40
|
+
const cap = known ? known.runsPerHour : list.unknown.runsPerHour;
|
|
41
|
+
if (known ? known.revoked : !list.unknown.allow) {
|
|
42
|
+
return {
|
|
43
|
+
status: 403,
|
|
44
|
+
code: "client_refused",
|
|
45
|
+
note: "clientRefused",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (cap !== null && !allowed(`client:${client ?? "unknown"}`, cap, HOUR)) {
|
|
49
|
+
return {
|
|
50
|
+
status: 429,
|
|
51
|
+
code: "client_limit",
|
|
52
|
+
note: "clientLimit",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** The list as the Manage-App sends it, checked; throws on a shape this runtime does not know. */
|
|
58
|
+
export function parseClientList(body) {
|
|
59
|
+
const raw = body;
|
|
60
|
+
if (!Array.isArray(raw?.clients)) {
|
|
61
|
+
throw new Error("no clients");
|
|
62
|
+
}
|
|
63
|
+
const clients = raw.clients.map((c) => {
|
|
64
|
+
if (typeof c?.id !== "string" || !ID.test(c.id)) {
|
|
65
|
+
throw new Error("bad client id");
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
id: c.id,
|
|
69
|
+
name: typeof c.name === "string" ? c.name : c.id,
|
|
70
|
+
runsPerHour: typeof c.runsPerHour === "number" && c.runsPerHour >= 0 ? c.runsPerHour : null,
|
|
71
|
+
revoked: c.revoked === true,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const unknown = (raw.unknown ?? {});
|
|
75
|
+
return {
|
|
76
|
+
clients,
|
|
77
|
+
unknown: {
|
|
78
|
+
allow: unknown.allow !== false,
|
|
79
|
+
runsPerHour: typeof unknown.runsPerHour === "number" && unknown.runsPerHour >= 0
|
|
80
|
+
? unknown.runsPerHour
|
|
81
|
+
: null,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Fetches the Manage-App's list. A Manage-App without the call (404) means no list: everything
|
|
87
|
+
* may start runs. Any other failure keeps the list from before.
|
|
88
|
+
*/
|
|
89
|
+
export async function loadClientList() {
|
|
90
|
+
if (!managed) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(`${env.manage.url}/v1/clients`, {
|
|
95
|
+
headers: { authorization: `Bearer ${env.manage.serviceKey}` },
|
|
96
|
+
signal: AbortSignal.timeout(10_000),
|
|
97
|
+
});
|
|
98
|
+
if (res.status === 404) {
|
|
99
|
+
list = null;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
throw new Error(`HTTP ${res.status}`);
|
|
104
|
+
}
|
|
105
|
+
list = parseClientList(await res.json());
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.warn(`[clients] the Manage-App's client list was not read: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** A managed runtime: the list now, and again every hour. */
|
|
112
|
+
export function startClientChecks() {
|
|
113
|
+
if (!managed) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
void loadClientList();
|
|
117
|
+
setInterval(() => void loadClientList(), HOUR).unref();
|
|
118
|
+
}
|
|
@@ -21,8 +21,10 @@ function open(url, authToken) {
|
|
|
21
21
|
}
|
|
22
22
|
async function prepare(client, url) {
|
|
23
23
|
if (url.startsWith("file:")) {
|
|
24
|
-
|
|
24
|
+
// The wait first: studio, doors and runner share the files, and switching to WAL waits
|
|
25
|
+
// too while another process recovers the log after a restart.
|
|
25
26
|
await client.execute("PRAGMA busy_timeout = 5000");
|
|
27
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
26
28
|
}
|
|
27
29
|
await client.execute("PRAGMA foreign_keys = ON");
|
|
28
30
|
}
|
|
@@ -52,6 +54,28 @@ function tenantUrl(id, dbUrl) {
|
|
|
52
54
|
}
|
|
53
55
|
return `file:${join(env.dataDir, "tenants", `${id}.db`)}`;
|
|
54
56
|
}
|
|
57
|
+
function isBusy(err) {
|
|
58
|
+
for (let e = err; e; e = e.cause) {
|
|
59
|
+
if (String(e.code ?? "").startsWith("SQLITE_BUSY")) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/** `fn` again while another process holds the database: 1, 2, 4, 8 and 16 seconds apart. */
|
|
66
|
+
async function whileBusy(fn) {
|
|
67
|
+
for (let attempt = 0;; attempt++) {
|
|
68
|
+
try {
|
|
69
|
+
return await fn();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (attempt >= 5 || !isBusy(err)) {
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
55
79
|
async function openTenant(id) {
|
|
56
80
|
let row = await controlDb.query.tenant.findFirst({ where: eq(control.tenant.id, id) });
|
|
57
81
|
if (!row) {
|
|
@@ -61,10 +85,20 @@ async function openTenant(id) {
|
|
|
61
85
|
const url = tenantUrl(id, row.dbUrl);
|
|
62
86
|
const client = open(url, url.startsWith("file:") ? "" : env.tenantDbAuthToken);
|
|
63
87
|
clients.set(id, client);
|
|
64
|
-
await prepare(client, url);
|
|
65
88
|
const db = makeTenantDb(client);
|
|
66
89
|
// Every database is brought to the current schema when it is first opened.
|
|
67
|
-
|
|
90
|
+
try {
|
|
91
|
+
await whileBusy(async () => {
|
|
92
|
+
await prepare(client, url);
|
|
93
|
+
await migrate(db, { migrationsFolder: migrationsFolder("tenant") });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
// The next use opens it afresh (tenantDb forgets the failed attempt).
|
|
98
|
+
clients.delete(id);
|
|
99
|
+
client.close();
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
68
102
|
for (const hook of openHooks) {
|
|
69
103
|
await hook(id, client);
|
|
70
104
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `run` ADD `client` text;
|