wizards 0.4.4 → 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/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/model-errors.js +8 -3
- package/apps/runtime/dist/models.js +58 -38
- 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/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-nLn3vZAN.js → AboutDialog-BCtnYRj2.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-Cbx3mN2p.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-Bvj3R8g4.js → FlowPreview-BtpAdyt-.js} +1 -1
- package/apps/web/dist/assets/{Harness-D-Z3MbYq.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-CdoAfnI2.js → OnboardingPage-jC6FidpF.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-BYcM1-1H.js +1 -0
- package/apps/web/dist/assets/{PublicRunner-CHwK3X75.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 +4 -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-1VuHE2Q7.js +0 -10
- package/apps/web/dist/assets/EditorPage-Cp3jqQsk.js +0 -3
- package/apps/web/dist/assets/NewWizardChannels-DCF7JoL0.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-BTlgq7yN.js +0 -7
- package/apps/web/dist/assets/ProjectPage-CoPSdDO9.js +0 -1
- package/apps/web/dist/assets/RunDrawer-CmGuqp0p.js +0 -4
- package/apps/web/dist/assets/RunnerView-DiaQiDbw.js +0 -1
- package/apps/web/dist/assets/SettingsPage-NzmFrJXb.js +0 -1
- package/apps/web/dist/assets/SetupPage-8mBuezdY.js +0 -1
- package/apps/web/dist/assets/SharePage-C-T5fkp1.js +0 -1
- package/apps/web/dist/assets/changelog-CsxPWKJ-.js +0 -1
- package/apps/web/dist/assets/esm-CnR8rDPi.js +0 -7
- package/apps/web/dist/assets/index-CmxVazOf.css +0 -1
- package/apps/web/dist/assets/index-D57rKyGN.js +0 -14
- package/apps/web/dist/assets/settings-menu-D-UqnL1p.js +0 -1
- package/apps/web/dist/assets/ui-DTuixH4y.js +0 -134
|
@@ -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
|
}
|
|
@@ -19,7 +19,7 @@ export function placeLabel(result) {
|
|
|
19
19
|
* The address of a position, when the server has a geocoder (`GEOCODER_URL`). Calls go out one
|
|
20
20
|
* at a time and at most one a second — what a public Nominatim asks of its users.
|
|
21
21
|
*/
|
|
22
|
-
export function reverseGeocode(lat, lng, language = "
|
|
22
|
+
export function reverseGeocode(lat, lng, language = "en") {
|
|
23
23
|
if (!env.geocoderUrl) {
|
|
24
24
|
return Promise.resolve(null);
|
|
25
25
|
}
|
|
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { release } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { noteText } from "@engenty-wizards/shared/run";
|
|
6
5
|
import { nanoid } from "nanoid";
|
|
7
6
|
import { env } from "../env.js";
|
|
8
7
|
import { ffmpeg, hasFfmpeg } from "../media/ffmpeg.js";
|
|
@@ -103,10 +102,6 @@ export function appleUnavailableNote(status) {
|
|
|
103
102
|
return { code: "appleDevice" };
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
|
-
/** What to tell the person when Apple Intelligence cannot answer, in German (the settings). */
|
|
107
|
-
export function appleUnavailable(status) {
|
|
108
|
-
return noteText(appleUnavailableNote(status), "de");
|
|
109
|
-
}
|
|
110
105
|
/**
|
|
111
106
|
* The failure the helper reports, as the person should read it: a note, so a run shows it in the
|
|
112
107
|
* wizard's language.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
1
2
|
import { resolveEnv, run } from "./env.js";
|
|
2
3
|
import { failureOf, HarnessModel, lastJson, runClient, } from "./model.js";
|
|
3
4
|
/**
|
|
@@ -142,5 +143,5 @@ export const claude = {
|
|
|
142
143
|
},
|
|
143
144
|
login: { args: ["auth", "login"], interactive: false },
|
|
144
145
|
model: (alias, effort) => new ClaudeCodeModel(claude, alias || "sonnet", effort),
|
|
145
|
-
exhausted: "Das Anthropic-Konto, mit dem Claude Code angemeldet ist, hat kein Guthaben. Melde Claude Code mit deinem Abo an: in den Einstellungen unter „Modelle“.",
|
|
146
|
+
exhausted: bilingual("Das Anthropic-Konto, mit dem Claude Code angemeldet ist, hat kein Guthaben. Melde Claude Code mit deinem Abo an: in den Einstellungen unter „Modelle“.", "The Anthropic account Claude Code is signed in with has no credit. Sign Claude Code in with your subscription: under “Models” in the settings."),
|
|
146
147
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
4
5
|
import { resolveEnv, run } from "./env.js";
|
|
5
6
|
import { failureOf, HarnessImageModel, HarnessModel, runClient, } from "./model.js";
|
|
6
7
|
/**
|
|
@@ -224,5 +225,5 @@ export const codex = {
|
|
|
224
225
|
login: { args: ["login"], interactive: false },
|
|
225
226
|
model: (alias, effort) => new CodexModel(codex, alias || "default", effort),
|
|
226
227
|
image: { alias: "image", model: (alias) => new CodexImageModel(codex, alias || "image") },
|
|
227
|
-
exhausted: "Das Kontingent des ChatGPT-Kontos, mit dem Codex angemeldet ist, ist aufgebraucht. Später noch einmal versuchen oder einen anderen Client wählen.",
|
|
228
|
+
exhausted: bilingual("Das Kontingent des ChatGPT-Kontos, mit dem Codex angemeldet ist, ist aufgebraucht. Später noch einmal versuchen oder einen anderen Client wählen.", "The quota of the ChatGPT account Codex is signed in with is used up. Try again later or pick another client."),
|
|
228
229
|
};
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
4
5
|
import { nanoid } from "nanoid";
|
|
5
6
|
import { env } from "../env.js";
|
|
6
7
|
import { openBridge } from "../mcp/bridge.js";
|
|
7
8
|
import { ModelUnavailableError } from "../model-errors.js";
|
|
8
9
|
import { extensionOf, jsonInstruction, parseJsonAnswer, renderPrompt } from "./prompt.js";
|
|
9
10
|
const workDir = join(env.dataDir, "harness");
|
|
10
|
-
export const signedOut = (h) => `${h.name} ist auf diesem Gerät nicht angemeldet. Melde dich in den Einstellungen unter „Modelle“ an
|
|
11
|
-
export const notInstalled = (h) => `${h.name} ist auf diesem Gerät nicht installiert. Installiere es in den Einstellungen unter „Modelle
|
|
11
|
+
export const signedOut = (h) => bilingual(`${h.name} ist auf diesem Gerät nicht angemeldet. Melde dich in den Einstellungen unter „Modelle“ an.`, `${h.name} is not signed in on this device. Sign in under “Models” in the settings.`);
|
|
12
|
+
export const notInstalled = (h) => bilingual(`${h.name} ist auf diesem Gerät nicht installiert. Installiere es in den Einstellungen unter „Modelle“.`, `${h.name} is not installed on this device. Install it under “Models” in the settings.`);
|
|
12
13
|
export const exhausted = (h) => h.exhausted ??
|
|
13
|
-
`Das Konto, mit dem ${h.name} angemeldet ist, hat kein Guthaben oder Kontingent mehr
|
|
14
|
+
bilingual(`Das Konto, mit dem ${h.name} angemeldet ist, hat kein Guthaben oder Kontingent mehr.`, `The account ${h.name} is signed in with has no credit or quota left.`);
|
|
14
15
|
/** The failure a client reports, as the person should read it. */
|
|
15
16
|
export function failureOf(h, detail) {
|
|
16
17
|
// The client's own words stay in the log: the sentence below sums them up for the person.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { accessSync, chmodSync, constants } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { bilingual } from "@engenty-wizards/shared/run";
|
|
4
5
|
import { nanoid } from "nanoid";
|
|
5
6
|
import { ServiceError } from "../services/errors.js";
|
|
6
7
|
const BUFFER = 200_000;
|
|
@@ -36,7 +37,7 @@ async function ptyModule() {
|
|
|
36
37
|
return { spawn };
|
|
37
38
|
}
|
|
38
39
|
catch {
|
|
39
|
-
throw new ServiceError("refused", "Das eingebaute Terminal ist auf diesem Gerät nicht verfügbar. Führe den Befehl in deinem eigenen Terminal aus.");
|
|
40
|
+
throw new ServiceError("refused", bilingual("Das eingebaute Terminal ist auf diesem Gerät nicht verfügbar. Führe den Befehl in deinem eigenen Terminal aus.", "The built-in terminal is not available on this device. Run the command in your own terminal."));
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
function emit(session, event) {
|
|
@@ -84,7 +85,7 @@ export async function openTerminal(opts) {
|
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
catch (err) {
|
|
87
|
-
throw new ServiceError("refused", `Der Befehl konnte nicht starten: ${err.message}`);
|
|
88
|
+
throw new ServiceError("refused", bilingual(`Der Befehl konnte nicht starten: ${err.message}`, `The command could not start: ${err.message}`));
|
|
88
89
|
}
|
|
89
90
|
const session = {
|
|
90
91
|
id,
|
|
@@ -146,7 +147,7 @@ export async function openTerminal(opts) {
|
|
|
146
147
|
function sessionOf(id) {
|
|
147
148
|
const session = sessions.get(id);
|
|
148
149
|
if (!session) {
|
|
149
|
-
throw new ServiceError("not_found", "Dieses Terminal gibt es nicht mehr.");
|
|
150
|
+
throw new ServiceError("not_found", bilingual("Dieses Terminal gibt es nicht mehr.", "This terminal is gone."));
|
|
150
151
|
}
|
|
151
152
|
return session;
|
|
152
153
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { noteText } from "@engenty-wizards/shared/run";
|
|
2
2
|
/**
|
|
3
|
-
* A failure the person reads, given as a note of `RUN_NOTES`
|
|
4
|
-
* the wizard's language,
|
|
3
|
+
* A failure the person reads, given as a note of `RUN_NOTES` (or `bilingual` words) where it has
|
|
4
|
+
* one: a run shows it in the wizard's language, a response in the asker's, and the message keeps
|
|
5
|
+
* the English line for the log. Free text has no note.
|
|
5
6
|
*/
|
|
6
7
|
export class NotedError extends Error {
|
|
7
8
|
note;
|
|
8
9
|
constructor(said) {
|
|
9
|
-
super(typeof said === "string" ? said : noteText(said, "
|
|
10
|
+
super(typeof said === "string" ? said : noteText(said, "en"));
|
|
10
11
|
this.note = typeof said === "string" ? null : said;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
@@ -17,6 +18,10 @@ export class ModelUnavailableError extends NotedError {
|
|
|
17
18
|
export function noteOf(err) {
|
|
18
19
|
return err instanceof NotedError ? err.note : null;
|
|
19
20
|
}
|
|
21
|
+
/** What a failure says, to pass on: its note where it has one, else its message. */
|
|
22
|
+
export function saidOf(err) {
|
|
23
|
+
return noteOf(err) ?? err?.message ?? String(err);
|
|
24
|
+
}
|
|
20
25
|
/** A failure in a language: its note where it has one, else its message as it is. */
|
|
21
26
|
export function errorText(err, lang) {
|
|
22
27
|
const note = noteOf(err);
|