wizards 0.4.3 → 0.4.5
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/agents/architect.js +32 -13
- package/apps/runtime/dist/agents/subscription.js +12 -12
- package/apps/runtime/dist/app.js +9 -5
- package/apps/runtime/dist/assistant/knows.js +2 -2
- package/apps/runtime/dist/authoring/example.js +21 -20
- package/apps/runtime/dist/authoring/guide.js +4 -2
- package/apps/runtime/dist/connectors/imap.js +4 -4
- package/apps/runtime/dist/connectors/index.js +40 -15
- package/apps/runtime/dist/connectors/plugin-connections.js +3 -2
- package/apps/runtime/dist/connectors/via-account.js +8 -6
- package/apps/runtime/dist/docs/dev/plugins/connections.md +1 -1
- package/apps/runtime/dist/docs/dev/plugins/reference.md +5 -1
- package/apps/runtime/dist/docs/dev/plugins/routes.md +35 -1
- package/apps/runtime/dist/docs/dev/plugins/server.md +27 -1
- package/apps/runtime/dist/docs/dev/plugins/starters.md +1 -1
- package/apps/runtime/dist/documents/parse.js +5 -3
- package/apps/runtime/dist/engine/events.js +1 -1
- package/apps/runtime/dist/engine/input.js +28 -8
- package/apps/runtime/dist/engine/prepare.js +2 -2
- package/apps/runtime/dist/engine/requirements.js +12 -3
- package/apps/runtime/dist/engine/runner.js +34 -23
- package/apps/runtime/dist/engine/steps.js +6 -5
- package/apps/runtime/dist/engine/surface.js +2 -1
- package/apps/runtime/dist/engine/template.js +1 -1
- package/apps/runtime/dist/engine/types.js +2 -1
- package/apps/runtime/dist/film/project.js +1 -1
- package/apps/runtime/dist/film/skills.js +2 -2
- package/apps/runtime/dist/film/sounds.js +1 -1
- package/apps/runtime/dist/film/step.js +7 -4
- package/apps/runtime/dist/film/tools.js +1 -1
- package/apps/runtime/dist/geocode.js +1 -1
- package/apps/runtime/dist/harness/apple.js +0 -5
- package/apps/runtime/dist/harness/claude.js +2 -1
- package/apps/runtime/dist/harness/codex.js +2 -1
- package/apps/runtime/dist/harness/model.js +4 -3
- package/apps/runtime/dist/harness/terminal.js +4 -3
- package/apps/runtime/dist/manage.js +29 -0
- package/apps/runtime/dist/model-errors.js +8 -3
- package/apps/runtime/dist/models.js +58 -38
- package/apps/runtime/dist/plugins/channels.js +77 -0
- package/apps/runtime/dist/plugins/loader.js +9 -1
- package/apps/runtime/dist/plugins/public.js +16 -2
- package/apps/runtime/dist/plugins/registry.js +1 -0
- package/apps/runtime/dist/plugins/runs.js +2 -1
- package/apps/runtime/dist/routes/api.js +16 -5
- package/apps/runtime/dist/routes/lang.js +13 -0
- package/apps/runtime/dist/routes/plugin-public.js +65 -12
- package/apps/runtime/dist/routes/runs.js +12 -19
- package/apps/runtime/dist/routes/studio.js +40 -28
- package/apps/runtime/dist/services/access.js +4 -3
- package/apps/runtime/dist/services/architect.js +15 -4
- package/apps/runtime/dist/services/assistant-chats.js +2 -1
- package/apps/runtime/dist/services/brand.js +3 -2
- package/apps/runtime/dist/services/errors.js +7 -5
- package/apps/runtime/dist/services/knowledge-model.js +3 -2
- package/apps/runtime/dist/services/marketplace.js +6 -5
- package/apps/runtime/dist/services/package.js +10 -3
- package/apps/runtime/dist/services/profile.js +12 -0
- package/apps/runtime/dist/services/project-files.js +13 -12
- package/apps/runtime/dist/services/projects.js +11 -6
- package/apps/runtime/dist/services/runs.js +18 -11
- package/apps/runtime/dist/services/shares.js +2 -1
- package/apps/runtime/dist/services/site.js +21 -10
- package/apps/runtime/dist/services/space-categories.js +7 -6
- package/apps/runtime/dist/services/space-data.js +19 -16
- package/apps/runtime/dist/services/spaces.js +8 -7
- package/apps/runtime/dist/services/wizards.js +41 -20
- package/apps/runtime/dist/store/errors.js +2 -1
- package/apps/runtime/dist/store/index.js +21 -4
- package/apps/runtime/dist/store/shared-lists.js +2 -3
- package/apps/runtime/dist/tools/index.js +17 -2
- package/apps/runtime/dist/tools/plugin.js +3 -2
- package/apps/runtime/dist/widgets/render.js +1 -1
- package/apps/runtime/dist/widgets/step.js +2 -1
- package/apps/web/dist/assets/{AboutDialog-DWaBKvI6.js → AboutDialog-BCtnYRj2.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-BcM1gXPM.js → ChangelogDialog-DBjoIIHL.js} +1 -1
- package/apps/web/dist/assets/ChatView-BcyUYN0Q.js +10 -0
- package/apps/web/dist/assets/EditorPage-Ce1QD-DK.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-S1s1yRaP.js → FlowPreview-BtpAdyt-.js} +1 -1
- package/apps/web/dist/assets/{Harness-DILx5Edb.js → Harness-DJJ_yMLY.js} +2 -2
- package/apps/web/dist/assets/NewWizardChannels-BrYLMV6F.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-827zwhhW.js +7 -0
- package/apps/web/dist/assets/{OnboardingPage-xoGrKIJ-.js → OnboardingPage-jC6FidpF.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-BYcM1-1H.js +1 -0
- package/apps/web/dist/assets/{PublicRunner-Cab2jUy6.js → PublicRunner-BofZl0MO.js} +1 -1
- package/apps/web/dist/assets/{ReadOnly-DQPHmvS0.js → ReadOnly-B4nIGkoj.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-Dx-BRCkY.js +4 -0
- package/apps/web/dist/assets/RunnerView-CDCp61aq.js +1 -0
- package/apps/web/dist/assets/SettingsPage-B49nLHwv.js +1 -0
- package/apps/web/dist/assets/SetupPage-4tzzHUq0.js +1 -0
- package/apps/web/dist/assets/SharePage-Bs4s73h4.js +1 -0
- package/apps/web/dist/assets/changelog-D-12LwZB.js +1 -0
- package/apps/web/dist/assets/{circle-alert-DCBV7n5k.js → circle-alert-BueS7qO8.js} +1 -1
- package/apps/web/dist/assets/esm-CjGdsKU4.js +7 -0
- package/apps/web/dist/assets/index-BRDn-5v_.js +14 -0
- package/apps/web/dist/assets/index-mlnAPOdf.css +1 -0
- package/apps/web/dist/assets/{lock-DlzTfwxP.js → lock-CkXYnQux.js} +1 -1
- package/apps/web/dist/assets/{phone-BFD815nc.js → phone-CHkEf-Dz.js} +1 -1
- package/apps/web/dist/assets/settings-menu-CrVpArM_.js +1 -0
- package/apps/web/dist/assets/ui-DrdqFaQt.js +134 -0
- package/apps/web/dist/assets/{upload-BEf5-d6X.js → upload-CzkkZqAo.js} +1 -1
- package/apps/web/dist/index.html +4 -4
- package/apps/web/dist/mcp/flow.html +76 -76
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +98 -2
- package/node_modules/@engenty-wizards/plugin-sdk/dist/thread.js +6 -1
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +6 -2
- package/node_modules/@engenty-wizards/shared/dist/definition.js +13 -5
- package/node_modules/@engenty-wizards/shared/dist/knowledge.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +11 -1
- package/node_modules/@engenty-wizards/shared/dist/run.js +11 -1
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +8 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +8 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/ChatView-D0RRIl15.js +0 -10
- package/apps/web/dist/assets/EditorPage-DrJ6e_2q.js +0 -3
- package/apps/web/dist/assets/NewWizardChannels-CEILDADp.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-DxOtK2E-.js +0 -7
- package/apps/web/dist/assets/ProjectPage-6E5z4nZA.js +0 -1
- package/apps/web/dist/assets/RunDrawer-B3FNYpgZ.js +0 -4
- package/apps/web/dist/assets/RunnerView-D6sGVnRy.js +0 -1
- package/apps/web/dist/assets/SettingsPage-DHtIqmOJ.js +0 -1
- package/apps/web/dist/assets/SetupPage-Cn9iQbJx.js +0 -1
- package/apps/web/dist/assets/SharePage-CMr72PeU.js +0 -1
- package/apps/web/dist/assets/changelog-DfmWTlav.js +0 -1
- package/apps/web/dist/assets/esm-BPfTI3lL.js +0 -7
- package/apps/web/dist/assets/index-B-D8UvL9.js +0 -14
- package/apps/web/dist/assets/index-CmxVazOf.css +0 -1
- package/apps/web/dist/assets/settings-menu-BJm0CwOj.js +0 -1
- package/apps/web/dist/assets/ui-DTuixH4y.js +0 -134
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Routes
|
|
3
3
|
description: HTTP routes below the plugin's own address — signed in, inside the person's tenant.
|
|
4
|
-
version: 0.3
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
A plugin's routes live below `/api/studio/plugins/<id>`, and for programs with a key below
|
|
@@ -130,6 +130,40 @@ const address = await server.publicUrl(`/signal/${source.token}`);
|
|
|
130
130
|
- A body is taken up to 4 MB. Answers and errors work as for the plugin's other routes.
|
|
131
131
|
- A tenant without the plugin, or a suspended one, answers `404`.
|
|
132
132
|
|
|
133
|
+
## Shared routes
|
|
134
|
+
|
|
135
|
+
Some services post to one webhook for everyone: one account of the runtime's, one app. A shared
|
|
136
|
+
route has one address for all tenants, and the call itself says which tenant it is for, with
|
|
137
|
+
the ref the plugin handed the service before:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// Handed to the service with the call it is about to make: names the tenant.
|
|
141
|
+
const ref = await server.publicRef();
|
|
142
|
+
|
|
143
|
+
server.registerSharedRoute({
|
|
144
|
+
method: "POST",
|
|
145
|
+
path: "/incoming",
|
|
146
|
+
handler: async ({ request, inTenant }) => {
|
|
147
|
+
const raw = await request.text();
|
|
148
|
+
if (!signedByTheService(raw, request.headers)) {
|
|
149
|
+
throw Object.assign(new Error("Bad signature."), { status: 401, code: "bad_signature" });
|
|
150
|
+
}
|
|
151
|
+
const event = JSON.parse(raw);
|
|
152
|
+
return inTenant(event.ref, () => pickUp(event));
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// The address to enter at the service, once: the same for every tenant.
|
|
157
|
+
server.sharedUrl("/incoming");
|
|
158
|
+
// https://…/api/public/plugins/<id>/shared/incoming
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- The handler runs outside any tenant: check the service's signature, then `inTenant`.
|
|
162
|
+
- `inTenant(ref, work)` runs `work` inside the tenant of that ref, as a public route of the
|
|
163
|
+
tenant would run. A ref no tenant has, a suspended tenant and one without the plugin throw
|
|
164
|
+
`404` / `not_found`.
|
|
165
|
+
- `publicRef()` is the part of `publicUrl` that names the tenant; it is the same every time.
|
|
166
|
+
|
|
133
167
|
## From the studio half
|
|
134
168
|
|
|
135
169
|
`studio.api` calls the plugin's own routes and gives the parsed JSON:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The server half
|
|
3
3
|
description: The function a plugin exports, what it is handed, and the rules it runs under.
|
|
4
|
-
version: 0.3
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
The server half is a file whose default export is a function. The runtime calls it once when the
|
|
@@ -43,6 +43,8 @@ export default definePlugin((wizards) => {
|
|
|
43
43
|
| `index.put(entry)`, `index.remove(space, key?)` | Texts in the space's search index | [The space](./space.md#the-search-index) |
|
|
44
44
|
| `registerHttpRoute(route)` | A route below `/api/studio/plugins/<id>` | [Routes](./routes.md) |
|
|
45
45
|
| `registerPublicRoute(route)`, `publicUrl(path)` | A route anyone may call, and its address | [Routes](./routes.md#public-routes) |
|
|
46
|
+
| `registerSharedRoute(route)`, `sharedUrl(path)`, `publicRef()` | One route for every tenant, and the ref that names one | [Routes](./routes.md#shared-routes) |
|
|
47
|
+
| `channels.get()`, `request`, `withdraw`, `book` | The numbers the Manage-App gives the tenant | below |
|
|
46
48
|
| `registerRunner(runner)` | A door a wizard runs through, beside the page and the chat | [Runners](./runners.md) |
|
|
47
49
|
| `runs.start(…)`, `report`, `answerPage`, … | Drives runs from the plugin's own door | [Runners](./runners.md#drive-a-run) |
|
|
48
50
|
| `speech.conversation()` | The tenant's realtime speech model, for a call | [Runners](./runners.md#a-call) |
|
|
@@ -85,6 +87,30 @@ Either way a plugin never names a tenant:
|
|
|
85
87
|
- State kept in a variable of the module is shared by all tenants. Keep per-tenant state in a
|
|
86
88
|
table.
|
|
87
89
|
|
|
90
|
+
## Numbers of the Manage-App
|
|
91
|
+
|
|
92
|
+
On engenty.ai a team gets its number for texts and calls from engenty: it asks for one, an
|
|
93
|
+
admin assigns it. `server.channels` reads it for the current tenant and books what it carries;
|
|
94
|
+
the prices are the Manage-App's ([manage contract](https://github.com/engenty/wizards/blob/main/docs/manage-contract.md#numbers-of-a-tenant)).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const channels = await server.channels.get();
|
|
98
|
+
// { number: { number: "+43…", country: "AT", kinds: ["sms", "call"], providerRef: "PN…" } | null,
|
|
99
|
+
// request: { id, country, wish, createdAt } | null,
|
|
100
|
+
// shared: [{ number, country, kinds }] }
|
|
101
|
+
|
|
102
|
+
await server.channels.request({ userId: request.user.id, country: "AT", lang: "de" });
|
|
103
|
+
await server.channels.book({ kind: "sms", quantity: segments, key: messageSid, runId });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `get` is kept for a minute; `request` and `withdraw` forget it.
|
|
107
|
+
- `request` throws `403` / `plan` on a plan without a number and `409` / `has_number` when
|
|
108
|
+
the team has one. Asking again answers the open request.
|
|
109
|
+
- `book`: `sms` by segment, sent or received, `call` by started minute. The same `key` books
|
|
110
|
+
once.
|
|
111
|
+
- A runtime without a Manage-App has no numbers: `get` gives `null`, `request` and `withdraw`
|
|
112
|
+
throw `404` / `not_managed`, `book` books nothing.
|
|
113
|
+
|
|
88
114
|
## Asking a model
|
|
89
115
|
|
|
90
116
|
`server.generate` asks one of the tenant's models, the same ones its wizards run on, and is paid
|
|
@@ -27,7 +27,7 @@ server.registerStarter({
|
|
|
27
27
|
| `id` | Lower case, digits and `-`, starting with a letter, at most 40 characters. Unique in the plugin |
|
|
28
28
|
| `title` | What the list of templates calls it |
|
|
29
29
|
| `description` | What it makes, in one or two sentences. Shown under the title |
|
|
30
|
-
| `language` | `"de"` or `"en"`, the language its texts are in. Default: `"
|
|
30
|
+
| `language` | `"de"` or `"en"`, the language its texts are in. Default: `"en"` |
|
|
31
31
|
| `definition` | A complete wizard definition: the JSON the studio keeps and `get_authoring_guide` describes |
|
|
32
32
|
| `files` | Files of the wizard's workspace by path: widgets, sample data. A string is written as UTF-8, a `Uint8Array` as it is |
|
|
33
33
|
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
5
6
|
import { generateText, Output } from "ai";
|
|
6
7
|
import PostalMime from "postal-mime";
|
|
7
8
|
import { z } from "zod";
|
|
@@ -11,9 +12,10 @@ import { splitMarkdownByPageBreaks } from "../engenty/doc-converter/page-break.j
|
|
|
11
12
|
import { LocalProvider } from "../engenty/doc-converter/providers/local/index.js";
|
|
12
13
|
import { invoiceSchema, receiptSchema } from "../engenty/document-scanner/schemas/index.js";
|
|
13
14
|
import { env } from "../env.js";
|
|
15
|
+
import { NotedError } from "../model-errors.js";
|
|
14
16
|
import { costOf, textModel } from "../models.js";
|
|
15
17
|
import { htmlToMarkdown } from "../render/convert.js";
|
|
16
|
-
export class UnreadableDocument extends
|
|
18
|
+
export class UnreadableDocument extends NotedError {
|
|
17
19
|
}
|
|
18
20
|
const DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
19
21
|
const XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
@@ -168,7 +170,7 @@ async function read(input, opts) {
|
|
|
168
170
|
const vision = new VisionProvider(opts.charge, opts.signal, opts.call);
|
|
169
171
|
if (mime.startsWith("image/")) {
|
|
170
172
|
if (!vision.canConvert(mime)) {
|
|
171
|
-
throw new UnreadableDocument(`Bilder vom Typ ${mime} kann ich nicht lesen (PNG, JPEG, WebP gehen).`);
|
|
173
|
+
throw new UnreadableDocument(bilingual(`Bilder vom Typ ${mime} kann ich nicht lesen (PNG, JPEG, WebP gehen).`, `I cannot read images of type ${mime} (PNG, JPEG and WebP work).`));
|
|
172
174
|
}
|
|
173
175
|
const result = await vision.convert(data, name, mime);
|
|
174
176
|
return { markdown: result.markdown, pages: 1, via: "vision" };
|
|
@@ -212,7 +214,7 @@ async function read(input, opts) {
|
|
|
212
214
|
if (mime === "application/json" || mime.startsWith("text/")) {
|
|
213
215
|
return { markdown: text(), pages: null, via: "text" };
|
|
214
216
|
}
|
|
215
|
-
throw new UnreadableDocument(`„${name}“ (${mime}) kann ich nicht lesen.`);
|
|
217
|
+
throw new UnreadableDocument(bilingual(`„${name}“ (${mime}) kann ich nicht lesen.`, `I cannot read “${name}” (${mime}).`));
|
|
216
218
|
}
|
|
217
219
|
// --- structured extraction -----------------------------------------------------
|
|
218
220
|
/** The scanner's schemas say nothing about the currency; SaaS invoices come in several. */
|
|
@@ -7,7 +7,7 @@ const bus = new EventEmitter();
|
|
|
7
7
|
bus.setMaxListeners(0);
|
|
8
8
|
export async function emitEvent(runId, stepId, type, said, asset) {
|
|
9
9
|
const note = typeof said === "string" ? null : said;
|
|
10
|
-
const message = note ? noteText(note, "
|
|
10
|
+
const message = note ? noteText(note, "en") : said;
|
|
11
11
|
const [row] = await db
|
|
12
12
|
.insert(schema.runEvent)
|
|
13
13
|
.values({
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { isDecidedField, isLocationValue, MAX_FILES, offeredSlot, shownFields, } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { bilingual, saidText } from "@engenty-wizards/shared/run";
|
|
3
|
+
/** The field errors in a language, for whoever reads them. */
|
|
4
|
+
export function fieldErrorsIn(errors, lang) {
|
|
5
|
+
return errors.map((e) => ({ field: e.field, message: saidText(e.message, lang) }));
|
|
6
|
+
}
|
|
2
7
|
function empty(v) {
|
|
3
8
|
return v === undefined || v === null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
4
9
|
}
|
|
@@ -137,36 +142,48 @@ export function readPageInput(step, input, context = {}) {
|
|
|
137
142
|
continue;
|
|
138
143
|
}
|
|
139
144
|
if (field.required && empty(v) && field.kind !== "toggle") {
|
|
140
|
-
errors.push({ field: field.id, message: "Pflichtfeld" });
|
|
145
|
+
errors.push({ field: field.id, message: bilingual("Pflichtfeld", "Required") });
|
|
141
146
|
continue;
|
|
142
147
|
}
|
|
143
148
|
if (Array.isArray(v) && (field.kind === "image" || field.kind === "file")) {
|
|
144
149
|
if (field.min && v.length < field.min) {
|
|
145
|
-
errors.push({
|
|
150
|
+
errors.push({
|
|
151
|
+
field: field.id,
|
|
152
|
+
message: bilingual(`Bitte mindestens ${field.min} auswählen`, `Please choose at least ${field.min}`),
|
|
153
|
+
});
|
|
146
154
|
continue;
|
|
147
155
|
}
|
|
148
156
|
if (field.max && v.length > field.max) {
|
|
149
157
|
errors.push({
|
|
150
158
|
field: field.id,
|
|
151
|
-
message: `Höchstens ${field.max} – bitte einige entfernen`,
|
|
159
|
+
message: bilingual(`Höchstens ${field.max} – bitte einige entfernen`, `At most ${field.max} – please remove some`),
|
|
152
160
|
});
|
|
153
161
|
continue;
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
164
|
if (field.kind === "number" && Number.isNaN(v)) {
|
|
157
|
-
errors.push({
|
|
165
|
+
errors.push({
|
|
166
|
+
field: field.id,
|
|
167
|
+
message: bilingual("Bitte eine Zahl eingeben", "Please enter a number"),
|
|
168
|
+
});
|
|
158
169
|
continue;
|
|
159
170
|
}
|
|
160
171
|
if (field.kind === "select" && !empty(v) && !field.options?.includes(String(v))) {
|
|
161
|
-
errors.push({ field: field.id, message: "Ungültige Auswahl" });
|
|
172
|
+
errors.push({ field: field.id, message: bilingual("Ungültige Auswahl", "Invalid choice") });
|
|
162
173
|
continue;
|
|
163
174
|
}
|
|
164
175
|
if (field.kind === "slot" && !empty(v) && !field.options?.includes(String(v))) {
|
|
165
|
-
errors.push({
|
|
176
|
+
errors.push({
|
|
177
|
+
field: field.id,
|
|
178
|
+
message: bilingual("Dieser Termin wird nicht angeboten", "This time is not offered"),
|
|
179
|
+
});
|
|
166
180
|
continue;
|
|
167
181
|
}
|
|
168
182
|
if (field.kind === "email" && !empty(v) && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(v))) {
|
|
169
|
-
errors.push({
|
|
183
|
+
errors.push({
|
|
184
|
+
field: field.id,
|
|
185
|
+
message: bilingual("Keine gültige E-Mail-Adresse", "Not a valid email address"),
|
|
186
|
+
});
|
|
170
187
|
continue;
|
|
171
188
|
}
|
|
172
189
|
if (field.kind === "url" && !empty(v)) {
|
|
@@ -174,7 +191,10 @@ export function readPageInput(step, input, context = {}) {
|
|
|
174
191
|
new URL(String(v));
|
|
175
192
|
}
|
|
176
193
|
catch {
|
|
177
|
-
errors.push({
|
|
194
|
+
errors.push({
|
|
195
|
+
field: field.id,
|
|
196
|
+
message: bilingual("Keine gültige URL", "Not a valid URL"),
|
|
197
|
+
});
|
|
178
198
|
continue;
|
|
179
199
|
}
|
|
180
200
|
}
|
|
@@ -68,7 +68,7 @@ async function listenTo(ctx, id, label, value) {
|
|
|
68
68
|
if (!found || found.row.runId !== ctx.runId || !/^(audio|video)\//.test(found.row.mime)) {
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
await ctx.emit("info",
|
|
71
|
+
await ctx.emit("info", { code: "listen", params: { label } });
|
|
72
72
|
try {
|
|
73
73
|
const { pieces, costUsd } = await listenPieces({
|
|
74
74
|
bytes: new Uint8Array(found.data),
|
|
@@ -85,6 +85,6 @@ async function listenTo(ctx, id, label, value) {
|
|
|
85
85
|
}
|
|
86
86
|
// The step goes on without the words; the next one tries again.
|
|
87
87
|
console.error(`[run ${ctx.runId}] listen ${id}`, err);
|
|
88
|
-
await ctx.emit("info",
|
|
88
|
+
await ctx.emit("info", { code: "listenFailed", params: { label } });
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { conditionMatches, conditionsOf, isDecidedField, isDecisionStep, isOtherwise, MODEL_CLASSES, } from "@engenty-wizards/shared/definition";
|
|
2
2
|
import { closedChoicesFor, openValue, otherwiseIndex, unavoidableFrom, unavoidableSteps, } from "@engenty-wizards/shared/fit";
|
|
3
|
+
import { bilingual, saidText } from "@engenty-wizards/shared/run";
|
|
3
4
|
import { classProblem, filmClient } from "../models.js";
|
|
4
5
|
// The steps every run passes through, and the choices that lead around a missing model, are
|
|
5
6
|
// pure and shared (packages/shared/src/fit.ts): the standalone check uses the same.
|
|
@@ -220,8 +221,12 @@ export async function stepsWithoutModel(def) {
|
|
|
220
221
|
const missing = await missingClasses();
|
|
221
222
|
return new Set(def.steps.filter((s) => stepClasses(s).some((cls) => missing.has(cls))).map((s) => s.id));
|
|
222
223
|
}
|
|
224
|
+
/** The classes this wizard needs that this runtime cannot serve, in a language; empty = every step can run. */
|
|
225
|
+
export async function missingModels(def, lang = "en") {
|
|
226
|
+
return (await missingModelsSaid(def)).map((m) => ({ ...m, problem: saidText(m.problem, lang) }));
|
|
227
|
+
}
|
|
223
228
|
/** The classes this wizard needs that this runtime cannot serve; empty = every step can run. */
|
|
224
|
-
export async function
|
|
229
|
+
export async function missingModelsSaid(def) {
|
|
225
230
|
const unavoidable = unavoidableSteps(def);
|
|
226
231
|
const needs = new Map();
|
|
227
232
|
for (const step of def.steps) {
|
|
@@ -268,9 +273,13 @@ export function blockingMessage(missing) {
|
|
|
268
273
|
if (!blocking.length) {
|
|
269
274
|
return null;
|
|
270
275
|
}
|
|
271
|
-
|
|
272
|
-
.map((m) =>
|
|
276
|
+
const line = (lang) => blocking
|
|
277
|
+
.map((m) => {
|
|
278
|
+
const steps = m.steps.map((s) => (lang === "de" ? `„${s.title}“` : `“${s.title}”`));
|
|
279
|
+
return `${steps.join(", ")}: ${saidText(m.problem, lang)}`;
|
|
280
|
+
})
|
|
273
281
|
.join(" ");
|
|
282
|
+
return bilingual(line("de"), line("en"));
|
|
274
283
|
}
|
|
275
284
|
/** The class a marketplace capability stands for; the rest need no model of their own. */
|
|
276
285
|
const CAPABILITY_CLASS = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { marginOf, } from "@engenty-wizards/shared/decision";
|
|
2
2
|
import { branchValues, cardsFromData, conditionsOf, dataRef, followRules, formatsFor, isDecidedField, LIST_FORMATS, listRef, loopKey, MAX_SHOWN_FIELDS, optionsFromData, otherwiseOf, pageNeeds, whenHolds, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
3
|
-
import {
|
|
3
|
+
import { bilingual, saidText, } from "@engenty-wizards/shared/run";
|
|
4
4
|
import { CONVERSATION_RUNNERS, DEFAULT_RUNNER_SETTINGS } from "@engenty-wizards/shared/runners";
|
|
5
5
|
import { eq } from "drizzle-orm";
|
|
6
6
|
import { nanoid } from "nanoid";
|
|
@@ -12,7 +12,7 @@ import { env } from "../env.js";
|
|
|
12
12
|
import { saveAsset } from "../files/storage.js";
|
|
13
13
|
import { managed, tenantInfo } from "../manage.js";
|
|
14
14
|
import { hasFfmpeg } from "../media/ffmpeg.js";
|
|
15
|
-
import { NotedError, noteOf } from "../model-errors.js";
|
|
15
|
+
import { NotedError, noteOf, saidOf } from "../model-errors.js";
|
|
16
16
|
import { conversationAvailable, freeTierRefusal, freeTierText, ModelUnavailableError, noteFreeTierRefusal, } from "../models.js";
|
|
17
17
|
import { runEnded } from "../plugins/events.js";
|
|
18
18
|
import { projectFiles } from "../services/project-files.js";
|
|
@@ -26,7 +26,7 @@ import { clearDeviceCalls } from "./device.js";
|
|
|
26
26
|
import { emitEvent, recentEvents, signalChanged } from "./events.js";
|
|
27
27
|
import { readPageInput } from "./input.js";
|
|
28
28
|
import { pushRun } from "./push.js";
|
|
29
|
-
import { blockingMessage, closedChoices,
|
|
29
|
+
import { blockingMessage, closedChoices, missingModelsSaid, openValue, stepsWithoutModel, } from "./requirements.js";
|
|
30
30
|
import { releaseResources, resourcesFor } from "./resources.js";
|
|
31
31
|
import { runAutomaticStep } from "./steps.js";
|
|
32
32
|
import { answersAsText, resolveRef } from "./template.js";
|
|
@@ -41,9 +41,9 @@ export class RunInputError extends NotedError {
|
|
|
41
41
|
this.errors = errors;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
export class NoCreditsError extends
|
|
44
|
+
export class NoCreditsError extends NotedError {
|
|
45
45
|
constructor() {
|
|
46
|
-
super("Dieser Wizard hat gerade kein Guthaben mehr. Bitte später erneut versuchen.");
|
|
46
|
+
super(bilingual("Dieser Wizard hat gerade kein Guthaben mehr. Bitte später erneut versuchen.", "This wizard has no credits left right now. Please try again later."));
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/** The runs this process drives right now, with the handle that stops each. */
|
|
@@ -99,7 +99,7 @@ function stepOf(def, id) {
|
|
|
99
99
|
}
|
|
100
100
|
function friendly(err) {
|
|
101
101
|
if (err instanceof StepError || err instanceof ModelUnavailableError) {
|
|
102
|
-
return err
|
|
102
|
+
return saidOf(err);
|
|
103
103
|
}
|
|
104
104
|
// An own AI Gateway key on Vercel's free tier: the person can top it up or pick another way.
|
|
105
105
|
const refused = freeTierRefusal(err);
|
|
@@ -109,32 +109,32 @@ function friendly(err) {
|
|
|
109
109
|
const msg = err?.message ?? String(err);
|
|
110
110
|
// The model-gateway answers 402 once the tenant's credits are used up.
|
|
111
111
|
if (/insufficient_credits|\b402\b/i.test(msg)) {
|
|
112
|
-
return new NoCreditsError()
|
|
112
|
+
return saidOf(new NoCreditsError());
|
|
113
113
|
}
|
|
114
114
|
// A runtime that runs alone pays its provider itself: its admin can top the account up.
|
|
115
115
|
if (!managed && /minimum balance|insufficient_funds|top up your credits/i.test(msg)) {
|
|
116
|
-
return "Das Guthaben beim Modell-Anbieter reicht dafür nicht aus (Videos brauchen dort ein Mindestguthaben). Bitte dort aufladen und noch einmal versuchen.";
|
|
116
|
+
return bilingual("Das Guthaben beim Modell-Anbieter reicht dafür nicht aus (Videos brauchen dort ein Mindestguthaben). Bitte dort aufladen und noch einmal versuchen.", "The credit with the model provider is not enough for this (videos need a minimum balance there). Please top up there and try again.");
|
|
117
117
|
}
|
|
118
118
|
if (/rate.?limit|429/i.test(msg)) {
|
|
119
|
-
return "Der KI-Dienst ist gerade ausgelastet. Bitte gleich noch einmal versuchen.";
|
|
119
|
+
return bilingual("Der KI-Dienst ist gerade ausgelastet. Bitte gleich noch einmal versuchen.", "The AI service is busy right now. Please try again in a moment.");
|
|
120
120
|
}
|
|
121
121
|
if (/timeout|timed out/i.test(msg)) {
|
|
122
|
-
return "Der Schritt hat zu lange gedauert. Bitte noch einmal versuchen.";
|
|
122
|
+
return bilingual("Der Schritt hat zu lange gedauert. Bitte noch einmal versuchen.", "The step took too long. Please try again.");
|
|
123
123
|
}
|
|
124
124
|
// The platform's own provider account (budget, quota, key): nothing the person can fix.
|
|
125
125
|
if (/budget|quota|insufficient|billing|credit balance|api key|unauthori[sz]ed|401|403/i.test(msg)) {
|
|
126
|
-
return "Der KI-Dienst ist gerade nicht erreichbar. Bitte später noch einmal versuchen.";
|
|
126
|
+
return bilingual("Der KI-Dienst ist gerade nicht erreichbar. Bitte später noch einmal versuchen.", "The AI service cannot be reached right now. Please try again later.");
|
|
127
127
|
}
|
|
128
|
-
return `Dieser Schritt ist fehlgeschlagen: ${msg.slice(0, 300)}
|
|
128
|
+
return bilingual(`Dieser Schritt ist fehlgeschlagen: ${msg.slice(0, 300)}`, `This step failed: ${msg.slice(0, 300)}`);
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* A step that failed ends the run. A failure with a note (Apple Intelligence's) goes into the log
|
|
132
132
|
* as that note, so a runner shows it in its language; the run's error is in the wizard's.
|
|
133
133
|
*/
|
|
134
134
|
async function failRun(run, stepId, err) {
|
|
135
|
-
const
|
|
136
|
-
const message =
|
|
137
|
-
await emitEvent(run.id, stepId, "error",
|
|
135
|
+
const said = noteOf(err) ?? friendly(err);
|
|
136
|
+
const message = saidText(said, wizardLang(run.definition));
|
|
137
|
+
await emitEvent(run.id, stepId, "error", said);
|
|
138
138
|
await updateRun(run.id, { status: "failed", error: message });
|
|
139
139
|
}
|
|
140
140
|
// --- creating & driving ------------------------------------------------------
|
|
@@ -143,7 +143,7 @@ export async function createRun(input) {
|
|
|
143
143
|
const first = input.definition.steps[0];
|
|
144
144
|
const state = { values: {}, outputs: {}, history: [], notes: {} };
|
|
145
145
|
// A step every run passes through needs a model this runtime cannot serve: refuse before anything is held.
|
|
146
|
-
const blocked = blockingMessage(await
|
|
146
|
+
const blocked = blockingMessage(await missingModelsSaid(input.definition));
|
|
147
147
|
if (blocked) {
|
|
148
148
|
throw new ModelUnavailableError(blocked);
|
|
149
149
|
}
|
|
@@ -219,7 +219,11 @@ async function take(runId, tenant) {
|
|
|
219
219
|
await withTenant(tenant, () => drive(runId, abort.signal))
|
|
220
220
|
.catch(async (err) => {
|
|
221
221
|
console.error(`[run ${runId}]`, err);
|
|
222
|
-
await withTenant(tenant, () =>
|
|
222
|
+
await withTenant(tenant, async () => {
|
|
223
|
+
const run = await loadRun(runId);
|
|
224
|
+
const lang = run ? wizardLang(run.definition) : "en";
|
|
225
|
+
await updateRun(runId, { status: "failed", error: saidText(friendly(err), lang) });
|
|
226
|
+
}).catch(() => undefined);
|
|
223
227
|
})
|
|
224
228
|
.finally(async () => {
|
|
225
229
|
clearInterval(beat);
|
|
@@ -631,14 +635,17 @@ export async function submitPage(runId, stepId, input) {
|
|
|
631
635
|
const answer = values[id];
|
|
632
636
|
if (answer !== undefined &&
|
|
633
637
|
(choice.values.length === 0 || choice.values.some((v) => String(v) === String(answer)))) {
|
|
634
|
-
errors.push({
|
|
638
|
+
errors.push({
|
|
639
|
+
field: id,
|
|
640
|
+
message: bilingual("Das ist hier gerade nicht verfügbar.", "That is not available here right now."),
|
|
641
|
+
});
|
|
635
642
|
}
|
|
636
643
|
}
|
|
637
644
|
if (errors.length) {
|
|
638
645
|
throw new RunInputError(errors);
|
|
639
646
|
}
|
|
640
647
|
const connections = step.fields.some((f) => f.kind === "connection")
|
|
641
|
-
? await connectionViews(run.definition.connections ?? [], scopeOf(run), await projectIdOf(run))
|
|
648
|
+
? await connectionViews(run.definition.connections ?? [], scopeOf(run), await projectIdOf(run), wizardLang(run.definition))
|
|
642
649
|
: [];
|
|
643
650
|
for (const field of step.fields) {
|
|
644
651
|
if (field.kind === "image" || field.kind === "file") {
|
|
@@ -646,7 +653,9 @@ export async function submitPage(runId, stepId, input) {
|
|
|
646
653
|
for (const id of Array.isArray(value) ? value : value ? [value] : []) {
|
|
647
654
|
const asset = await db.query.asset.findFirst({ where: eq(schema.asset.id, String(id)) });
|
|
648
655
|
if (asset?.runId !== runId) {
|
|
649
|
-
throw new RunInputError([
|
|
656
|
+
throw new RunInputError([
|
|
657
|
+
{ field: field.id, message: bilingual("Datei nicht gefunden", "File not found") },
|
|
658
|
+
]);
|
|
650
659
|
}
|
|
651
660
|
}
|
|
652
661
|
}
|
|
@@ -657,7 +666,9 @@ export async function submitPage(runId, stepId, input) {
|
|
|
657
666
|
values[field.id] = account.label;
|
|
658
667
|
}
|
|
659
668
|
else if (field.required) {
|
|
660
|
-
throw new RunInputError([
|
|
669
|
+
throw new RunInputError([
|
|
670
|
+
{ field: field.id, message: bilingual("Bitte zuerst verbinden", "Please connect first") },
|
|
671
|
+
]);
|
|
661
672
|
}
|
|
662
673
|
}
|
|
663
674
|
}
|
|
@@ -701,7 +712,7 @@ export async function reviewStep(runId, stepId, action) {
|
|
|
701
712
|
if (!step.show.includes(action.target)) {
|
|
702
713
|
throw new RunConflict("Unknown target");
|
|
703
714
|
}
|
|
704
|
-
state.notes[action.target] = action.note.slice(0, 2000) || "
|
|
715
|
+
state.notes[action.target] = action.note.slice(0, 2000) || "Please make a new variant.";
|
|
705
716
|
// A step with several results makes only the named ones again.
|
|
706
717
|
if (action.items?.length) {
|
|
707
718
|
state.redo = { ...state.redo, [action.target]: action.items };
|
|
@@ -884,7 +895,7 @@ export async function runView(run, brand) {
|
|
|
884
895
|
ask: run.status === "running" ? run.ask : null,
|
|
885
896
|
lists,
|
|
886
897
|
connections: step?.type === "page" && step.fields.some((f) => f.kind === "connection")
|
|
887
|
-
? await connectionViews(def.connections ?? [], scopeOf(run), await projectIdOf(run))
|
|
898
|
+
? await connectionViews(def.connections ?? [], scopeOf(run), await projectIdOf(run), wizardLang(def))
|
|
888
899
|
: [],
|
|
889
900
|
keeps: Boolean(def.lists?.length || def.connections?.length),
|
|
890
901
|
shareUrl: run.shareToken ? `${env.publicUrl}/s/${run.shareToken}` : null,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pOf } from "@engenty-wizards/shared/decision";
|
|
2
2
|
import { allFields, dataRef, isDecisionStep, LOCALES, templateRefs, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
3
3
|
import { mainLogo, ruleLines } from "@engenty-wizards/shared/projects";
|
|
4
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
4
5
|
import { SURFACE_GUIDE, validateSurface, } from "@engenty-wizards/shared/surface";
|
|
5
6
|
import { Agent } from "@mastra/core/agent";
|
|
6
7
|
import { generateText, Output } from "ai";
|
|
@@ -378,7 +379,7 @@ function agentEntries(step, ctx) {
|
|
|
378
379
|
async function runAgentEach(step, ctx) {
|
|
379
380
|
const entries = agentEntries(step, ctx);
|
|
380
381
|
if (!entries.length) {
|
|
381
|
-
throw new StepError("Es gibt keine Einträge, für die dieser Schritt arbeiten könnte.");
|
|
382
|
+
throw new StepError(bilingual("Es gibt keine Einträge, für die dieser Schritt arbeiten könnte.", "There are no entries for this step to work on."));
|
|
382
383
|
}
|
|
383
384
|
// The view, if any, is composed once over the table of all entries.
|
|
384
385
|
const single = {
|
|
@@ -553,7 +554,7 @@ export async function runAgentStep(step, ctx) {
|
|
|
553
554
|
const text = step.output.format === "json" ? everything : closing || everything;
|
|
554
555
|
if (step.output.format !== "json") {
|
|
555
556
|
if (!text && !assets.length) {
|
|
556
|
-
throw new StepError("Der Schritt hat kein Ergebnis geliefert.");
|
|
557
|
+
throw new StepError(bilingual("Der Schritt hat kein Ergebnis geliefert.", "The step did not deliver a result."));
|
|
557
558
|
}
|
|
558
559
|
return { text, assets: assets.length ? assets : undefined, at: new Date().toISOString() };
|
|
559
560
|
}
|
|
@@ -754,7 +755,7 @@ async function writeHtml(step, ctx, kind) {
|
|
|
754
755
|
.replace(/```\s*$/, "")
|
|
755
756
|
.trim();
|
|
756
757
|
if (!/<html|<!doctype/i.test(html)) {
|
|
757
|
-
throw new StepError("Das Dokument konnte nicht erstellt werden.");
|
|
758
|
+
throw new StepError(bilingual("Das Dokument konnte nicht erstellt werden.", "The document could not be made."));
|
|
758
759
|
}
|
|
759
760
|
// On screen (preview, PNG) a print document needs the margin its @page rule gives it on paper.
|
|
760
761
|
return kind === "document"
|
|
@@ -805,7 +806,7 @@ async function inParallel(items, limit, work) {
|
|
|
805
806
|
async function runMediaStep(step, ctx, asset) {
|
|
806
807
|
const entries = entriesOf(step, ctx);
|
|
807
808
|
if (!entries.length) {
|
|
808
|
-
throw new StepError("Für diesen Schritt fehlen die Vorlagen (Bilder oder Szenen).");
|
|
809
|
+
throw new StepError(bilingual("Für diesen Schritt fehlen die Vorlagen (Bilder oder Szenen).", "This step is missing its sources (images or scenes)."));
|
|
809
810
|
}
|
|
810
811
|
const previous = ctx.state.outputs[step.id];
|
|
811
812
|
const before = previous?.assets?.filter((a) => a.kind === asset) ?? [];
|
|
@@ -897,7 +898,7 @@ export async function runGenerateStep(step, ctx) {
|
|
|
897
898
|
case "voice": {
|
|
898
899
|
const text = renderTemplate(step.prompt, ctx.scope).trim().slice(0, 4000);
|
|
899
900
|
if (!text) {
|
|
900
|
-
throw new StepError("Es gibt keinen Text zum Vorlesen.");
|
|
901
|
+
throw new StepError(bilingual("Es gibt keinen Text zum Vorlesen.", "There is no text to read aloud."));
|
|
901
902
|
}
|
|
902
903
|
const note = ctx.state.notes[step.id];
|
|
903
904
|
const media = await generateSpeechMedia({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assembleSurface, dataRef, listRef, } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
2
3
|
import { validateSurface } from "@engenty-wizards/shared/surface";
|
|
3
4
|
import { decide } from "./decide.js";
|
|
4
5
|
import { answersAsText, resolveRef } from "./template.js";
|
|
@@ -13,7 +14,7 @@ export async function runSurfaceStep(step, ctx) {
|
|
|
13
14
|
const components = step.components ?? (await decidedComponents(step, ctx));
|
|
14
15
|
const issues = validateSurface(components, Object.keys(data));
|
|
15
16
|
if (issues.length) {
|
|
16
|
-
throw new StepError(`Die Ansicht ist fehlerhaft: ${issues.map((i) => i.message).join(" ")}`);
|
|
17
|
+
throw new StepError(bilingual(`Die Ansicht ist fehlerhaft: ${issues.map((i) => i.message).join(" ")}`, `The view is faulty: ${issues.map((i) => i.message).join(" ")}`));
|
|
17
18
|
}
|
|
18
19
|
return { surface: { components, data }, at: new Date().toISOString() };
|
|
19
20
|
}
|
|
@@ -11,7 +11,7 @@ function money(n, currency, locale) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
/** Line items as a Markdown table with their totals — what a model needs to write a document. */
|
|
14
|
-
export function itemsAsMarkdown(field, value, values, lang = "
|
|
14
|
+
export function itemsAsMarkdown(field, value, values, lang = "en") {
|
|
15
15
|
const locale = LOCALES[lang];
|
|
16
16
|
const totals = itemsTotals(field, value, values);
|
|
17
17
|
const cols = field.columns ?? [];
|
|
@@ -250,7 +250,7 @@ export async function prepareFilmFolder(step, ctx) {
|
|
|
250
250
|
const preset = { "16:9": "landscape", "9:16": "portrait", "1:1": "square" }[step.format];
|
|
251
251
|
const init = await runHyperframes(["init", "film", "--non-interactive", "--skip-transcribe", `--resolution=${preset}`], { cwd: root, signal: ctx.signal, timeoutMs: 5 * 60_000 });
|
|
252
252
|
if (init.code !== 0 || !existsSync(join(root, "film"))) {
|
|
253
|
-
throw new Error(`
|
|
253
|
+
throw new Error(`The film project could not be set up: ${init.out.slice(-300)}`);
|
|
254
254
|
}
|
|
255
255
|
await put(root, ".engenty/inputs", stamp);
|
|
256
256
|
return { root, brief };
|
|
@@ -21,7 +21,7 @@ const fetching = new Map();
|
|
|
21
21
|
export function platformSkills(name) {
|
|
22
22
|
const pkg = PLATFORM_SKILLS[name];
|
|
23
23
|
if (!pkg) {
|
|
24
|
-
return Promise.reject(new Error(`
|
|
24
|
+
return Promise.reject(new Error(`There is no skill package “${name}”.`));
|
|
25
25
|
}
|
|
26
26
|
const dir = join(env.dataDir, "skills", `${pkg.name}@${pkg.version.slice(0, 12)}`);
|
|
27
27
|
if (existsSync(join(dir, ".complete"))) {
|
|
@@ -37,7 +37,7 @@ export function platformSkills(name) {
|
|
|
37
37
|
async function fetchPackage(pkg, dir) {
|
|
38
38
|
const res = await fetch(pkg.tarball);
|
|
39
39
|
if (!res.ok) {
|
|
40
|
-
throw new Error(`
|
|
40
|
+
throw new Error(`The skill package “${pkg.name}” could not be loaded (${res.status}).`);
|
|
41
41
|
}
|
|
42
42
|
const tmp = `${dir}.part`;
|
|
43
43
|
await rm(tmp, { recursive: true, force: true });
|
|
@@ -108,7 +108,7 @@ async function build() {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
if (!sounds.length) {
|
|
111
|
-
throw new Error("
|
|
111
|
+
throw new Error("The sounds for films could not be loaded.");
|
|
112
112
|
}
|
|
113
113
|
sounds.sort((a, b) => a.file.localeCompare(b.file));
|
|
114
114
|
await writeFile(join(tmp, "catalog.json"), JSON.stringify({
|
|
@@ -2,10 +2,12 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile, rm, stat } from "node:fs/promises";
|
|
4
4
|
import { isAbsolute, join, normalize } from "node:path";
|
|
5
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
5
6
|
import { StepError } from "../engine/types.js";
|
|
6
7
|
import { CLAUDE_BIN, claudeEnv } from "../harness/claude.js";
|
|
7
8
|
import { failureOf } from "../harness/model.js";
|
|
8
9
|
import { openBridge } from "../mcp/bridge.js";
|
|
10
|
+
import { errorText } from "../model-errors.js";
|
|
9
11
|
import { filmClient } from "../models.js";
|
|
10
12
|
import { prepareFilmFolder, savedSession, saveSession } from "./project.js";
|
|
11
13
|
import { runHyperframes } from "./tools.js";
|
|
@@ -227,12 +229,13 @@ export async function runFilmStep(step, ctx) {
|
|
|
227
229
|
if (ctx.signal.aborted) {
|
|
228
230
|
throw err;
|
|
229
231
|
}
|
|
230
|
-
throw new StepError(`${client.name} hat beim Film aufgehört: ${err.message}. Die Arbeit ist gespeichert; ein neuer Versuch macht dort weiter.`);
|
|
232
|
+
throw new StepError(bilingual(`${client.name} hat beim Film aufgehört: ${err.message}. Die Arbeit ist gespeichert; ein neuer Versuch macht dort weiter.`, `${client.name} stopped working on the film: ${err.message}. The work is saved; a new attempt carries on from there.`));
|
|
231
233
|
});
|
|
232
234
|
const result = last;
|
|
233
235
|
if (!result || result.is_error) {
|
|
234
|
-
const detail = String(result?.result ?? (stderr.trim() || "
|
|
235
|
-
|
|
236
|
+
const detail = String(result?.result ?? (stderr.trim() || "no result")).slice(0, 400);
|
|
237
|
+
const failure = failureOf(client, detail);
|
|
238
|
+
throw new StepError(bilingual(`${errorText(failure, "de")} Die Arbeit ist gespeichert; ein neuer Versuch macht dort weiter.`, `${errorText(failure, "en")} The work is saved; a new attempt carries on from there.`));
|
|
236
239
|
}
|
|
237
240
|
return result;
|
|
238
241
|
};
|
|
@@ -262,7 +265,7 @@ export async function runFilmStep(step, ctx) {
|
|
|
262
265
|
}
|
|
263
266
|
const problem = renderProblem(render.out);
|
|
264
267
|
if (attempt >= RENDER_FIXES) {
|
|
265
|
-
throw new StepError(`Der Film ließ sich nicht rendern: ${problem.slice(0, 400)}`);
|
|
268
|
+
throw new StepError(bilingual(`Der Film ließ sich nicht rendern: ${problem.slice(0, 400)}`, `The film could not be rendered: ${problem.slice(0, 400)}`));
|
|
266
269
|
}
|
|
267
270
|
await ctx.emit("tool", { code: "filmFix" });
|
|
268
271
|
const fixed = await turn(`The final render of film/ failed:\n\n${problem}\n\nFind the cause in the composition's code, fix it, run \`check\` and a draft render through the hyperframes tool until both pass, and end with one line on what you fixed.`, await savedSession(root));
|
|
@@ -40,7 +40,7 @@ async function install() {
|
|
|
40
40
|
`hyperframes@${HYPERFRAMES_VERSION}`,
|
|
41
41
|
], { cwd: home, timeoutMs: 10 * 60_000 });
|
|
42
42
|
if (code !== 0 || !existsSync(entry)) {
|
|
43
|
-
throw new Error(`HyperFrames
|
|
43
|
+
throw new Error(`HyperFrames could not be installed: ${out.slice(-400)}`);
|
|
44
44
|
}
|
|
45
45
|
return entry;
|
|
46
46
|
}
|