wizards 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/apps/runtime/dist/agents/architect.js +13 -26
  2. package/apps/runtime/dist/agents/studio-assistant.js +19 -22
  3. package/apps/runtime/dist/agents/subscription.js +10 -9
  4. package/apps/runtime/dist/app.js +9 -7
  5. package/apps/runtime/dist/assistant/knows.js +10 -9
  6. package/apps/runtime/dist/auth/index.js +7 -0
  7. package/apps/runtime/dist/connectors/external.js +3 -3
  8. package/apps/runtime/dist/connectors/index.js +23 -23
  9. package/apps/runtime/dist/connectors/via-account.js +6 -6
  10. package/apps/runtime/dist/documents/parse.js +3 -3
  11. package/apps/runtime/dist/engine/input.js +11 -11
  12. package/apps/runtime/dist/engine/requirements.js +7 -8
  13. package/apps/runtime/dist/engine/runner.js +10 -14
  14. package/apps/runtime/dist/engine/steps.js +6 -6
  15. package/apps/runtime/dist/engine/surface.js +2 -2
  16. package/apps/runtime/dist/film/step.js +5 -5
  17. package/apps/runtime/dist/harness/apple.js +2 -1
  18. package/apps/runtime/dist/harness/claude.js +2 -2
  19. package/apps/runtime/dist/harness/codex.js +2 -2
  20. package/apps/runtime/dist/harness/model.js +4 -5
  21. package/apps/runtime/dist/harness/terminal.js +4 -4
  22. package/apps/runtime/dist/media/conversation.js +18 -35
  23. package/apps/runtime/dist/media/transcribe.js +2 -1
  24. package/apps/runtime/dist/model-errors.js +1 -1
  25. package/apps/runtime/dist/models.js +32 -40
  26. package/apps/runtime/dist/plugins/runs.js +3 -5
  27. package/apps/runtime/dist/routes/api.js +9 -9
  28. package/apps/runtime/dist/routes/lang.js +14 -9
  29. package/apps/runtime/dist/routes/marketplace.js +6 -1
  30. package/apps/runtime/dist/routes/runs.js +5 -5
  31. package/apps/runtime/dist/routes/studio.js +38 -37
  32. package/apps/runtime/dist/services/access.js +4 -4
  33. package/apps/runtime/dist/services/architect.js +3 -12
  34. package/apps/runtime/dist/services/assistant-chats.js +2 -2
  35. package/apps/runtime/dist/services/brand.js +3 -3
  36. package/apps/runtime/dist/services/cloud.js +5 -5
  37. package/apps/runtime/dist/services/errors.js +2 -2
  38. package/apps/runtime/dist/services/knowledge-model.js +3 -3
  39. package/apps/runtime/dist/services/marketplace.js +18 -8
  40. package/apps/runtime/dist/services/package-file.js +9 -11
  41. package/apps/runtime/dist/services/package.js +8 -9
  42. package/apps/runtime/dist/services/profile.js +2 -1
  43. package/apps/runtime/dist/services/project-files.js +13 -13
  44. package/apps/runtime/dist/services/projects.js +7 -11
  45. package/apps/runtime/dist/services/runners.js +4 -4
  46. package/apps/runtime/dist/services/runs.js +7 -10
  47. package/apps/runtime/dist/services/shares.js +2 -2
  48. package/apps/runtime/dist/services/site.js +8 -11
  49. package/apps/runtime/dist/services/space-categories.js +7 -7
  50. package/apps/runtime/dist/services/space-data.js +20 -20
  51. package/apps/runtime/dist/services/spaces.js +7 -9
  52. package/apps/runtime/dist/services/wizards.js +11 -13
  53. package/apps/runtime/dist/store/index.js +2 -2
  54. package/apps/runtime/dist/texts.js +195 -0
  55. package/apps/runtime/dist/tools/plugin.js +7 -3
  56. package/apps/runtime/dist/widgets/step.js +2 -2
  57. package/apps/web/dist/assets/AboutDialog-BcTtA42P.js +1 -0
  58. package/apps/web/dist/assets/{ChangelogDialog-sdSC9eZj.js → ChangelogDialog-DMOWV_Iu.js} +1 -1
  59. package/apps/web/dist/assets/ChatView-CrnrYHjp.js +10 -0
  60. package/apps/web/dist/assets/EditorPage-DpEw3eOu.js +3 -0
  61. package/apps/web/dist/assets/{FlowPreview-B4-ipMdE.js → FlowPreview-CzmH4JUQ.js} +1 -1
  62. package/apps/web/dist/assets/{Harness-DwI4wFqj.js → Harness-DipH0Vec.js} +2 -2
  63. package/apps/web/dist/assets/{NewWizardChannels-DkI648G-.js → NewWizardChannels-DShu0PP6.js} +1 -1
  64. package/apps/web/dist/assets/NewWizardPage-HSa6PNJs.js +7 -0
  65. package/apps/web/dist/assets/OnboardingPage-CPADMs4c.js +1 -0
  66. package/apps/web/dist/assets/ProjectPage-DMPcrhgt.js +1 -0
  67. package/apps/web/dist/assets/PublicRunner-DZ5sWmtg.js +1 -0
  68. package/apps/web/dist/assets/{ReadOnly-Jvzgyelg.js → ReadOnly-B9xaq_M3.js} +1 -1
  69. package/apps/web/dist/assets/RunDrawer-9nMGhbSm.js +4 -0
  70. package/apps/web/dist/assets/{RunnerView-DN4sMYS6.js → RunnerView-NSSP3tc6.js} +1 -1
  71. package/apps/web/dist/assets/SettingsPage-Dy9MbwQh.js +1 -0
  72. package/apps/web/dist/assets/SetupPage-kemgI7v5.js +1 -0
  73. package/apps/web/dist/assets/{SharePage-CmowpiJG.js → SharePage-CQfIdcwy.js} +1 -1
  74. package/apps/web/dist/assets/{changelog-Cz8fZ-jG.js → changelog-BW40vzI6.js} +1 -1
  75. package/apps/web/dist/assets/{esm-J06O1nN_.js → esm-BF1nonbH.js} +1 -1
  76. package/apps/web/dist/assets/index-CnJ4Yzkf.css +1 -0
  77. package/apps/web/dist/assets/index-DQ9qAB4v.js +14 -0
  78. package/apps/web/dist/assets/{lock-ChDlFFHO.js → lock-DVvM6IsA.js} +1 -1
  79. package/apps/web/dist/assets/{phone-Cr7jz_kf.js → phone-3fG1okFs.js} +1 -1
  80. package/apps/web/dist/assets/{settings-menu-BAwHMXCR.js → settings-menu-D9fbi1Z0.js} +1 -1
  81. package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
  82. package/apps/web/dist/assets/{upload-DcAIMe8d.js → upload-BsMUhEfX.js} +1 -1
  83. package/apps/web/dist/index.html +3 -3
  84. package/apps/web/dist/mcp/flow.html +78 -78
  85. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
  86. package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
  87. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
  88. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
  89. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
  90. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
  91. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
  92. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
  93. package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
  94. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
  95. package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
  96. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
  97. package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
  98. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
  99. package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
  100. package/package.json +1 -1
  101. package/apps/web/dist/assets/AboutDialog-Ck6_TSVJ.js +0 -1
  102. package/apps/web/dist/assets/ChatView-S1H9Pcih.js +0 -10
  103. package/apps/web/dist/assets/EditorPage-BSfJXx4W.js +0 -3
  104. package/apps/web/dist/assets/NewWizardPage-dumNiSqM.js +0 -7
  105. package/apps/web/dist/assets/OnboardingPage-BAfHLLNT.js +0 -1
  106. package/apps/web/dist/assets/ProjectPage-fneLQr6R.js +0 -1
  107. package/apps/web/dist/assets/PublicRunner-CuxDAXK7.js +0 -1
  108. package/apps/web/dist/assets/RunDrawer-C_T1I77g.js +0 -4
  109. package/apps/web/dist/assets/SettingsPage-DjFvKMRz.js +0 -1
  110. package/apps/web/dist/assets/SetupPage-CzYwVUvh.js +0 -1
  111. package/apps/web/dist/assets/index-CZFfDGHy.css +0 -1
  112. package/apps/web/dist/assets/index-DZTDv2pA.js +0 -14
  113. package/apps/web/dist/assets/ui-C5_NcAmG.js +0 -134
@@ -1,3 +1,4 @@
1
+ import { isLang } from "@engenty-wizards/shared/definition";
1
2
  import { z } from "zod";
2
3
  import { linkedAccount } from "../auth/account.js";
3
4
  import { managed } from "../manage.js";
@@ -34,7 +35,7 @@ const langKey = (user) => `lang:${user.tenantId}:${user.id}`;
34
35
  */
35
36
  export async function userLang(user) {
36
37
  const lang = await readSetting(langKey(user));
37
- return lang === "de" || lang === "en" ? lang : null;
38
+ return isLang(lang) ? lang : null;
38
39
  }
39
40
  export async function saveUserLang(user, lang) {
40
41
  await writeSetting(langKey(user), lang);
@@ -1,5 +1,5 @@
1
1
  import { PROJECT_LIMITS, projectFileLimit, } from "@engenty-wizards/shared/projects";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { generateText } from "ai";
4
4
  import { and, asc, count, eq, max } from "drizzle-orm";
5
5
  import { db, schema } from "../db/client.js";
@@ -79,14 +79,14 @@ export async function addProjectFile(projectId, input) {
79
79
  const mime = documentMime(input.name, input.mime) || "application/octet-stream";
80
80
  if (!accepts(input.kind, mime)) {
81
81
  throw new ServiceError("invalid", input.kind === "logo"
82
- ? bilingual("Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP).", "A logo is an image (PNG, SVG, JPEG, WebP).")
82
+ ? note("logoImagePngSvg")
83
83
  : input.kind === "pattern"
84
- ? bilingual("Ein Muster ist ein Bild (SVG, PNG, JPEG, WebP).", "A pattern is an image (SVG, PNG, JPEG, WebP).")
85
- : bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen gehören ins Wissen.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models go here. Texts to look things up in go under knowledge."));
84
+ ? note("patternImageSvgPng")
85
+ : note("filesGoHere"));
86
86
  }
87
87
  const limit = projectFileLimit(input.kind, mime);
88
88
  if (!input.data.byteLength || input.data.byteLength > limit) {
89
- throw new ServiceError("invalid", bilingual(`Wähl eine Datei bis ${Math.round(limit / 1_000_000)} MB.`, `Choose a file of ${Math.round(limit / 1_000_000)} MB or less.`));
89
+ throw new ServiceError("invalid", note("chooseFileUpTo", { mb: Math.round(limit / 1_000_000) }));
90
90
  }
91
91
  const scope = and(eq(schema.projectFile.projectId, projectId), eq(schema.projectFile.kind, input.kind));
92
92
  const [{ n, last }] = await db
@@ -94,7 +94,7 @@ export async function addProjectFile(projectId, input) {
94
94
  .from(schema.projectFile)
95
95
  .where(scope);
96
96
  if (n >= PROJECT_LIMITS.files[input.kind]) {
97
- throw new ServiceError("refused", bilingual(`Hier haben höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien Platz.`, `There’s room for at most ${PROJECT_LIMITS.files[input.kind]} files here.`));
97
+ throw new ServiceError("refused", note("roomMostFiles", { count: PROJECT_LIMITS.files[input.kind] }));
98
98
  }
99
99
  const name = input.name.slice(0, 120) || "datei";
100
100
  const ref = await saveAsset({
@@ -150,21 +150,21 @@ export async function addProjectFileFromUrl(projectId, input) {
150
150
  headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
151
151
  signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
152
152
  }).catch(() => {
153
- throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address can’t be reached."));
153
+ throw new ServiceError("invalid", note("addressNotReached"));
154
154
  });
155
155
  if (!res.ok) {
156
- throw new ServiceError("invalid", bilingual(`Die Adresse antwortete mit ${res.status}.`, `The address responded with ${res.status}.`));
156
+ throw new ServiceError("invalid", note("addressResponded", { status: res.status }));
157
157
  }
158
158
  const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
159
159
  if (mime === "text/html" && kind !== "document") {
160
160
  await res.body?.cancel();
161
- throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address leads to a web page, not an image."), {
161
+ throw new ServiceError("invalid", note("addressLeadsWebPage"), {
162
162
  reason: "page",
163
163
  url: res.url,
164
164
  });
165
165
  }
166
166
  if (Number(res.headers.get("content-length") ?? 0) > projectFileLimit(kind, mime)) {
167
- throw new ServiceError("invalid", bilingual("Die Datei ist zu groß.", "The file is too large."));
167
+ throw new ServiceError("invalid", note("addressFileTooLarge"));
168
168
  }
169
169
  const data = new Uint8Array(await res.arrayBuffer());
170
170
  const name = decodeURIComponent(new URL(res.url).pathname.split("/").pop() || kind);
@@ -305,7 +305,7 @@ export async function reindexProjectFile(projectId, fileId) {
305
305
  await requireWritableProject(projectId);
306
306
  const row = await projectFile(projectId, fileId);
307
307
  if (row.kind !== "document") {
308
- throw new ServiceError("invalid", bilingual("Nur Dokumente stehen im Index.", "Only documents are in the index."));
308
+ throw new ServiceError("invalid", note("onlyDocumentsIndex"));
309
309
  }
310
310
  const [pending] = await db
311
311
  .update(schema.projectFile)
@@ -380,7 +380,7 @@ export async function putOriginFile(origin, input) {
380
380
  });
381
381
  const mime = documentMime(input.name, input.mime) || "application/octet-stream";
382
382
  if (!input.data.byteLength || input.data.byteLength > projectFileLimit("document", mime)) {
383
- throw new ServiceError("invalid", bilingual(`„${input.name}“ ist leer oder zu groß für ein Dokument.`, `“${input.name}” is empty or too large for a document.`));
383
+ throw new ServiceError("invalid", note("emptyTooLargeDocument", { name: input.name }));
384
384
  }
385
385
  const name = input.name.slice(0, 120) || "datei";
386
386
  const values = {
@@ -407,7 +407,7 @@ export async function putOriginFile(origin, input) {
407
407
  .from(schema.projectFile)
408
408
  .where(and(eq(schema.projectFile.projectId, input.space), eq(schema.projectFile.kind, "document")));
409
409
  if (n >= PROJECT_LIMITS.files.document) {
410
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${PROJECT_LIMITS.files.document} Dokumente.`, `A space can have at most ${PROJECT_LIMITS.files.document} documents.`));
410
+ throw new ServiceError("refused", note("spaceMostDocuments", { count: PROJECT_LIMITS.files.document }));
411
411
  }
412
412
  const ref = await saveAsset({ kind: "project", mime, name, data: input.data });
413
413
  id = ref.id;
@@ -1,5 +1,5 @@
1
1
  import { COLOR_SECTION_KINDS, cleanAssetSections, cleanLogoSections, FACT_TYPES, FONT_USES, FONT_WEIGHTS, GRADIENT_TYPES, orderColors, PROJECT_LIMITS, RULE_MARKS, } from "@engenty-wizards/shared/projects";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, count, eq, isNull } from "drizzle-orm";
4
4
  import { nanoid } from "nanoid";
5
5
  import { z } from "zod";
@@ -8,6 +8,7 @@ import { env } from "../env.js";
8
8
  import { managed, tenantInfo } from "../manage.js";
9
9
  import { spaceChanged } from "../plugins/events.js";
10
10
  import { currentTenant } from "../tenants/tenant.js";
11
+ import { serverText } from "../texts.js";
11
12
  import { mayBuild, requireAdmin, requireBuild, requireWritable } from "./access.js";
12
13
  import { notFound, ServiceError } from "./errors.js";
13
14
  import { forgetWizardLinks } from "./links.js";
@@ -148,10 +149,7 @@ const madeHere = () => and(eq(schema.project.tenantId, currentTenant()), isNull(
148
149
  /** The tenant's projects a local install synced. */
149
150
  const synced = () => and(eq(schema.project.tenantId, currentTenant()), eq(schema.project.origin, "local"));
150
151
  /** What the first space is called, in the language of whoever first opens the studio. */
151
- const FIRST_SPACE = {
152
- cloud: { de: "Meine Wizards", en: "My wizards" },
153
- local: { de: "Mein Computer", en: "My computer" },
154
- };
152
+ const FIRST_SPACE = { cloud: "firstSpaceCloud", local: "firstSpaceLocal" };
155
153
  /**
156
154
  * A tenant that builds here always has a project of its own: the first one is made when its
157
155
  * studio is first opened, named in that request's language.
@@ -163,7 +161,7 @@ async function ensureProject(lang = "en") {
163
161
  await db.insert(schema.project).values({
164
162
  id,
165
163
  tenantId: currentTenant(),
166
- name: FIRST_SPACE[env.manage.url ? "cloud" : "local"][lang],
164
+ name: serverText(lang, FIRST_SPACE[env.manage.url ? "cloud" : "local"]),
167
165
  });
168
166
  spaceChanged("space.created", id);
169
167
  }
@@ -230,9 +228,7 @@ export async function createProject(_userId, name) {
230
228
  .where(eq(schema.project.tenantId, currentTenant()));
231
229
  const limit = await projectLimit();
232
230
  if (n >= limit) {
233
- throw new ServiceError("refused", limit === 1
234
- ? bilingual("Hier ist nur ein Space möglich.", "Only one space is allowed here.")
235
- : bilingual(`Hier sind höchstens ${limit} Spaces möglich.`, `You can have at most ${limit} spaces here.`));
231
+ throw new ServiceError("refused", limit === 1 ? note("onlyOneSpaceAllowed") : note("mostSpaces", { count: limit }));
236
232
  }
237
233
  const id = nanoid(12);
238
234
  await db
@@ -256,7 +252,7 @@ export async function updateProject(userId, projectId, patch) {
256
252
  return { ...s, headers };
257
253
  });
258
254
  if (patch.facts && new Set(patch.facts.map((f) => f.key)).size !== patch.facts.length) {
259
- throw new ServiceError("invalid", bilingual("Zwei Einträge haben denselben Schlüssel.", "Two entries have the same key."));
255
+ throw new ServiceError("invalid", note("twoEntriesSameKey"));
260
256
  }
261
257
  await db
262
258
  .update(schema.project)
@@ -370,7 +366,7 @@ export async function deleteProject(userId, projectId) {
370
366
  await requireAdmin();
371
367
  const all = await db.query.project.findMany({ where: madeHere() });
372
368
  if (all.length <= 1) {
373
- throw new ServiceError("refused", bilingual("Der letzte Space lässt sich nicht löschen.", "You can’t delete the last space."));
369
+ throw new ServiceError("refused", note("notDeleteLastSpace"));
374
370
  }
375
371
  await removeProject(projectId);
376
372
  }
@@ -1,5 +1,5 @@
1
1
  import { wizardLang } from "@engenty-wizards/shared/definition";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { BUILT_IN_RUNNERS, CONVERSATION_RUNNERS, DEFAULT_RUNNER, DEFAULT_RUNNER_SETTINGS, runnerFit, } from "@engenty-wizards/shared/runners";
4
4
  import { env } from "../env.js";
5
5
  import { conversationAccess, conversationAvailable } from "../models.js";
@@ -72,14 +72,14 @@ export async function checkRunnerSettings(settings) {
72
72
  const ids = new Set(available.map((r) => r.id));
73
73
  const unknown = settings.enabled.find((id) => !ids.has(id));
74
74
  if (unknown) {
75
- throw new ServiceError("invalid", bilingual(`Einen Runner „${unknown}“ gibt es hier nicht.`, `There’s no runner “${unknown}” here.`));
75
+ throw new ServiceError("invalid", note("unknownRunner", { unknown }));
76
76
  }
77
77
  const enabled = [...new Set(settings.enabled)];
78
78
  if (!enabled.length) {
79
- throw new ServiceError("invalid", bilingual("Mindestens ein Runner muss eingeschaltet bleiben.", "At least one runner has to stay on."));
79
+ throw new ServiceError("invalid", note("leastOneRunnerStay"));
80
80
  }
81
81
  if (!enabled.includes(settings.default)) {
82
- throw new ServiceError("invalid", bilingual("Der Link muss einen eingeschalteten Runner öffnen.", "The link has to open a runner that’s turned on."));
82
+ throw new ServiceError("invalid", note("linkOpenRunnerTurned"));
83
83
  }
84
84
  return { default: settings.default, enabled };
85
85
  }
@@ -1,5 +1,5 @@
1
1
  import { FIELD_WAYS, formatsFor, isDecidedField, shownFields, slotText, wizardLang, } from "@engenty-wizards/shared/definition";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, count, desc, eq, gte, inArray, max, or, sql } from "drizzle-orm";
4
4
  import { canSpend, MICROS_PER_CREDIT } from "../credits/credits.js";
5
5
  import { db, schema } from "../db/client.js";
@@ -18,7 +18,7 @@ export async function startTestRun(userId, wizardId, autopilot) {
18
18
  const w = await ownedWizard(userId, wizardId);
19
19
  const { definition, files } = await requireClean(w);
20
20
  if (!(await canSpend())) {
21
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
21
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
22
22
  }
23
23
  const runId = await createRun({
24
24
  wizardId: w.id,
@@ -80,13 +80,10 @@ function fly(runId, answers, acceptReviews) {
80
80
  } while (again);
81
81
  }
82
82
  catch (err) {
83
- const answers = (lang) => err instanceof RunInputError
84
- ? fieldErrorsIn(err.errors, lang)
85
- .map((e) => `${e.field} – ${e.message}`)
86
- .join("; ")
87
- : "";
88
83
  const message = err instanceof RunInputError
89
- ? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or don’t fit: ${answers("en")}`)
84
+ ? note("answersMissing", {
85
+ answers: err.errors.map((e) => note("fieldError", { field: e.field, message: e.message })),
86
+ })
90
87
  : saidOf(err);
91
88
  await emitEvent(runId, null, "error", message).catch(() => undefined);
92
89
  land();
@@ -377,7 +374,7 @@ export async function startRun(userId, wizardId, answers = {}) {
377
374
  throw notFound();
378
375
  }
379
376
  if (!(await canSpend())) {
380
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
377
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
381
378
  }
382
379
  const runId = await createRun({
383
380
  wizardId: w.id,
@@ -391,7 +388,7 @@ export async function startRun(userId, wizardId, answers = {}) {
391
388
  throw new ServiceError("refused", saidOf(err));
392
389
  }
393
390
  if (err instanceof NoCreditsError) {
394
- throw new ServiceError("no_credits", bilingual("Dein Guthaben ist aufgebraucht.", "Your credits are used up."));
391
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
395
392
  }
396
393
  throw err;
397
394
  });
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { and, eq, isNotNull, lt } from "drizzle-orm";
3
3
  import { nanoid } from "nanoid";
4
4
  import { db, schema } from "../db/client.js";
@@ -13,7 +13,7 @@ export const shareUrlFor = (token) => `${env.publicUrl}/s/${token}`;
13
13
  /** Shares a finished run. Idempotent: the same run keeps the same link. */
14
14
  export async function shareRun(run) {
15
15
  if (run.status !== "done") {
16
- throw new ServiceError("refused", bilingual("Nur fertige Ergebnisse lassen sich teilen.", "Only finished results can be shared."));
16
+ throw new ServiceError("refused", note("onlyFinishedResultsShared"));
17
17
  }
18
18
  let token = run.shareToken;
19
19
  if (!token) {
@@ -1,11 +1,12 @@
1
1
  import { factKey, PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { nanoid } from "nanoid";
4
4
  import { z } from "zod";
5
5
  import { runStudioAssistant } from "../agents/studio-assistant.js";
6
6
  import { canSpend } from "../credits/credits.js";
7
7
  import { errorText } from "../model-errors.js";
8
8
  import { currentTenant } from "../tenants/tenant.js";
9
+ import { serverText } from "../texts.js";
9
10
  import { projectWritable, requireWritable } from "./access.js";
10
11
  import { apiSpace, apiSpaceDetails } from "./api.js";
11
12
  import { openChat, turnHistory } from "./assistant-chats.js";
@@ -167,15 +168,13 @@ function assistMessage(input) {
167
168
  }
168
169
  async function work(job, projectId, input) {
169
170
  try {
170
- const de = siteLang(input) === "de";
171
- job.activity = de
172
- ? "Übernimmt die Angaben der Website …"
173
- : "Taking over what the website says …";
171
+ const lang = siteLang(input);
172
+ job.activity = serverText(lang, "siteTakingOver");
174
173
  await fillFromSite(job.userId, projectId, input);
175
- job.activity = de ? "Schreibt die Seiten in Wissen …" : "Writing the pages into Knowledge …";
174
+ job.activity = serverText(lang, "siteWritingPages");
176
175
  job.pages = await writePages(projectId, input);
177
176
  if (input.assist && (await canSpend())) {
178
- job.activity = de ? "Liest die Angaben …" : "Reading the details …";
177
+ job.activity = serverText(lang, "siteReading");
179
178
  const project = await ownedProject(job.userId, projectId);
180
179
  // The person's open assistant chat gives the context; the turn is not stored in it.
181
180
  const chat = await openChat(job.user);
@@ -210,9 +209,7 @@ async function work(job, projectId, input) {
210
209
  job.error =
211
210
  err instanceof ServiceError
212
211
  ? errorText(err, siteLang(input))
213
- : siteLang(input) === "de"
214
- ? "Die Website ließ sich nicht übernehmen."
215
- : "The website could not be taken over.";
212
+ : serverText(siteLang(input), "siteFailed");
216
213
  console.error("[site]", err);
217
214
  }
218
215
  }
@@ -267,7 +264,7 @@ function siteJobView(job) {
267
264
  export function siteJob(jobId) {
268
265
  const job = jobs.get(jobId);
269
266
  if (!job || job.tenantId !== currentTenant()) {
270
- throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht, oder er ist schon beendet.", "This job doesn’t exist or has already finished."));
267
+ throw new ServiceError("not_found", note("jobNotExistAlready"));
271
268
  }
272
269
  return siteJobView(job);
273
270
  }
@@ -1,5 +1,5 @@
1
1
  import { CATEGORY_TYPES, dayOf, } from "@engenty-wizards/shared/knowledge";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { and, asc, eq, inArray, isNotNull, or, sql } from "drizzle-orm";
4
4
  import { nanoid } from "nanoid";
5
5
  import { db, schema } from "../db/client.js";
@@ -273,10 +273,10 @@ const MAX_CATEGORIES = 60;
273
273
  function checkDraft(draft) {
274
274
  const name = draft.name.replace(/\s+/g, " ").trim().slice(0, 80);
275
275
  if (!name) {
276
- throw new ServiceError("invalid", bilingual("Eine Kategorie braucht einen Namen.", "A category needs a name."));
276
+ throw new ServiceError("invalid", note("categoryNeedsName"));
277
277
  }
278
278
  if (!CATEGORY_TYPES.includes(draft.type)) {
279
- throw new ServiceError("invalid", bilingual(`Unbekannter Typ „${draft.type}“.`, `Unknown type “${draft.type}”.`));
279
+ throw new ServiceError("invalid", note("unknownCategoryType", { type: draft.type }));
280
280
  }
281
281
  return name;
282
282
  }
@@ -330,7 +330,7 @@ export async function putCategory(projectId, draft, opts = {}) {
330
330
  return known;
331
331
  }
332
332
  if (rows.length >= MAX_CATEGORIES) {
333
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space can have at most ${MAX_CATEGORIES} categories.`));
333
+ throw new ServiceError("refused", note("spaceMostCategories", { count: MAX_CATEGORIES }));
334
334
  }
335
335
  const id = nanoid(12);
336
336
  const [row] = await db
@@ -409,7 +409,7 @@ export async function updateCategory(categoryId, patch) {
409
409
  if (name !== row.name) {
410
410
  const clash = byName(await categoryRows(row.projectId), name);
411
411
  if (clash && clash.id !== row.id) {
412
- throw new ServiceError("invalid", bilingual(`Die Kategorie „${name}“ gibt es schon.`, `The category “${name}” already exists.`));
412
+ throw new ServiceError("invalid", note("categoryAlreadyExists", { name }));
413
413
  }
414
414
  }
415
415
  await db
@@ -459,7 +459,7 @@ export async function addCategoryValue(categoryId, value) {
459
459
  const category = await categoryRow(categoryId);
460
460
  await requireWritableProject(category.projectId);
461
461
  if (category.type !== "choice") {
462
- throw new ServiceError("invalid", bilingual("Werte gibt es nur bei Kategorien vom Typ Auswahl.", "Only choice categories have values."));
462
+ throw new ServiceError("invalid", note("onlyChoiceCategoriesValues"));
463
463
  }
464
464
  const ids = await addValues(category.id, [value]);
465
465
  return { id: [...ids.values()].at(-1) };
@@ -489,7 +489,7 @@ export async function mergeCategoryValues(fromId, intoId) {
489
489
  const from = await valueRow(fromId);
490
490
  const into = await valueRow(intoId);
491
491
  if (from.category.id !== into.category.id || fromId === intoId) {
492
- throw new ServiceError("invalid", bilingual("Nur Werte einer Kategorie lassen sich zusammenführen.", "Only values of one category can be merged."));
492
+ throw new ServiceError("invalid", note("onlyValuesOneCategory"));
493
493
  }
494
494
  await requireWritableProject(from.category.projectId);
495
495
  const t = schema.spaceItemCategory;
@@ -1,5 +1,5 @@
1
1
  import { TableColumnValueError, tableColumnsSchema, } from "@engenty-wizards/shared/engenty/data-tables";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { editCells, SPACE_DATA_LIMITS, } from "@engenty-wizards/shared/space-data";
4
4
  import { rowKey } from "@engenty-wizards/shared/store";
5
5
  import { and, asc, count, desc, eq, inArray, isNotNull, isNull, max } from "drizzle-orm";
@@ -7,6 +7,7 @@ import { nanoid } from "nanoid";
7
7
  import { db, schema } from "../db/client.js";
8
8
  import { saidOf } from "../model-errors.js";
9
9
  import { currentTenant } from "../tenants/tenant.js";
10
+ import { serverText } from "../texts.js";
10
11
  import { projectWritable, requireWritableProject } from "./access.js";
11
12
  import { notFound, ServiceError } from "./errors.js";
12
13
  import { filePaths, freeSlug, knowledgePage, pageTree, tablePaths } from "./knowledge.js";
@@ -23,7 +24,7 @@ async function ownerOf(projectId, wizardId) {
23
24
  columns: { id: true },
24
25
  });
25
26
  if (!w) {
26
- throw new ServiceError("invalid", bilingual("Diesen Wizard gibt es im Space nicht.", "This wizard isn’t in the space."));
27
+ throw new ServiceError("invalid", note("wizardNotSpace"));
27
28
  }
28
29
  return w.id;
29
30
  }
@@ -36,7 +37,7 @@ function cellError(err) {
36
37
  export function checkColumns(columns) {
37
38
  const parsed = tableColumnsSchema.safeParse(columns);
38
39
  if (!parsed.success) {
39
- throw new ServiceError("invalid", parsed.error.issues[0]?.message ?? bilingual("Ungültige Spalten.", "Invalid columns."));
40
+ throw new ServiceError("invalid", parsed.error.issues[0]?.message ?? note("invalidColumns"));
40
41
  }
41
42
  return parsed.data;
42
43
  }
@@ -104,11 +105,10 @@ async function tableCount(projectId) {
104
105
  .from(schema.spaceTable)
105
106
  .where(eq(schema.spaceTable.projectId, projectId));
106
107
  if (n >= SPACE_DATA_LIMITS.tables) {
107
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`, `A space can have at most ${SPACE_DATA_LIMITS.tables} tables.`));
108
+ throw new ServiceError("refused", note("spaceMostTables", { count: SPACE_DATA_LIMITS.tables }));
108
109
  }
109
110
  }
110
111
  export async function createTable(userId, projectId, input, lang = "en") {
111
- const de = lang === "de";
112
112
  const project = await ownedProject(userId, projectId);
113
113
  await requireWritableProject(project.id);
114
114
  await tableCount(project.id);
@@ -116,10 +116,10 @@ export async function createTable(userId, projectId, input, lang = "en") {
116
116
  ? checkColumns(input.columns)
117
117
  : input.format === "faq"
118
118
  ? [
119
- { id: "question", name: de ? "Frage" : "Question", type: "text" },
119
+ { id: "question", name: serverText(lang, "faqQuestion"), type: "text" },
120
120
  {
121
121
  id: "answer",
122
- name: de ? "Antwort" : "Answer",
122
+ name: serverText(lang, "faqAnswer"),
123
123
  type: "text",
124
124
  format: { style: "multiline" },
125
125
  },
@@ -127,7 +127,7 @@ export async function createTable(userId, projectId, input, lang = "en") {
127
127
  : [{ id: "name", name: "Name", type: "text" }];
128
128
  const id = nanoid(12);
129
129
  const wizardId = await ownerOf(project.id, input.wizardId);
130
- const title = input.title.trim() || (de ? "Tabelle" : "Table");
130
+ const title = input.title.trim() || serverText(lang, "tableTitle");
131
131
  await db.insert(schema.spaceTable).values({
132
132
  id,
133
133
  tenantId: currentTenant(),
@@ -187,7 +187,7 @@ export async function getTable(userId, tableId) {
187
187
  export async function updateTable(_userId, tableId, patch) {
188
188
  const table = await writableTable(tableId);
189
189
  if (table.list && (patch.columns !== undefined || patch.wizardId !== undefined)) {
190
- throw new ServiceError("refused", bilingual("Diese Tabelle gehört zu einer Liste des Wizards. Ihre Spalten kommen aus dem Wizard.", "This table belongs to one of the wizard’s lists. Its columns come from the wizard."));
190
+ throw new ServiceError("refused", note("tableBelongsOneWizard"));
191
191
  }
192
192
  const title = patch.title !== undefined ? patch.title.trim() || table.title : table.title;
193
193
  const wizardId = patch.wizardId !== undefined ? await ownerOf(table.projectId, patch.wizardId) : table.wizardId;
@@ -250,7 +250,7 @@ async function refuseClash(tableId, key, rowId) {
250
250
  columns: { id: true },
251
251
  });
252
252
  if (other && other.id !== rowId) {
253
- throw new ServiceError("invalid", bilingual("Diesen Eintrag gibt es schon.", "This entry already exists."));
253
+ throw new ServiceError("invalid", note("entryAlreadyExists"));
254
254
  }
255
255
  }
256
256
  /** After a row of a table of Knowledge changed: its categories from its columns, its unit. */
@@ -269,7 +269,7 @@ export async function addTableRow(_userId, tableId, cells = {}) {
269
269
  .from(schema.spaceTableRow)
270
270
  .where(eq(schema.spaceTableRow.tableId, table.id));
271
271
  if (n >= SPACE_DATA_LIMITS.rowsPerTable) {
272
- throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table can have at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
272
+ throw new ServiceError("refused", note("tableMostRows", { count: SPACE_DATA_LIMITS.rowsPerTable }));
273
273
  }
274
274
  let value;
275
275
  try {
@@ -334,7 +334,7 @@ async function pageRow(pageId) {
334
334
  }
335
335
  function checkMarkdown(markdown) {
336
336
  if (markdown.length > SPACE_DATA_LIMITS.pageChars) {
337
- throw new ServiceError("refused", bilingual(`Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`, `A page can have at most ${SPACE_DATA_LIMITS.pageChars.toLocaleString("en")} characters.`));
337
+ throw new ServiceError("refused", note("pageMostChars", { count: SPACE_DATA_LIMITS.pageChars }));
338
338
  }
339
339
  return markdown;
340
340
  }
@@ -344,7 +344,7 @@ async function pageCount(projectId, more = 1) {
344
344
  .from(schema.spacePage)
345
345
  .where(eq(schema.spacePage.projectId, projectId));
346
346
  if (n + more > SPACE_DATA_LIMITS.pages) {
347
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space can have at most ${SPACE_DATA_LIMITS.pages} pages.`));
347
+ throw new ServiceError("refused", note("spaceMostPages", { count: SPACE_DATA_LIMITS.pages }));
348
348
  }
349
349
  }
350
350
  /** A page of the space's own that a sub-page may stand under: never one of its own sub-pages. */
@@ -357,11 +357,11 @@ async function parentOf(projectId, parentId, pageId) {
357
357
  columns: { id: true, wizardId: true, parentId: true },
358
358
  });
359
359
  if (!parent || parent.wizardId) {
360
- throw new ServiceError("invalid", bilingual("Diese Seite kann keine Unterseiten haben.", "This page can’t have subpages."));
360
+ throw new ServiceError("invalid", note("pageNotSubpages"));
361
361
  }
362
362
  for (let at = parent.id; at && pageId;) {
363
363
  if (at === pageId) {
364
- throw new ServiceError("invalid", bilingual("Eine Seite kann nicht ihre eigene Unterseite sein.", "A page can’t be its own subpage."));
364
+ throw new ServiceError("invalid", note("pageNotOwnSubpage"));
365
365
  }
366
366
  const up = await db.query.spacePage.findFirst({
367
367
  where: eq(schema.spacePage.id, at),
@@ -492,7 +492,7 @@ export async function runPages(projectId, wizardId) {
492
492
  export async function writeRunPage(projectId, wizardId, input) {
493
493
  const title = input.title.trim();
494
494
  if (!title) {
495
- throw new ServiceError("invalid", bilingual("Eine Seite braucht einen Titel.", "A page needs a title."));
495
+ throw new ServiceError("invalid", note("pageNeedsTitle"));
496
496
  }
497
497
  const existing = (await db.query.spacePage.findMany({
498
498
  where: and(eq(schema.spacePage.projectId, projectId), eq(schema.spacePage.wizardId, wizardId)),
@@ -513,7 +513,7 @@ export async function writeRunPage(projectId, wizardId, input) {
513
513
  .from(schema.spacePage)
514
514
  .where(eq(schema.spacePage.projectId, projectId));
515
515
  if (n >= SPACE_DATA_LIMITS.pages) {
516
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space can have at most ${SPACE_DATA_LIMITS.pages} pages.`));
516
+ throw new ServiceError("refused", note("spaceMostPages", { count: SPACE_DATA_LIMITS.pages }));
517
517
  }
518
518
  const id = nanoid(12);
519
519
  await db.insert(schema.spacePage).values({
@@ -532,7 +532,7 @@ async function originSpace(space) {
532
532
  columns: { id: true },
533
533
  });
534
534
  if (!project) {
535
- throw new ServiceError("not_found", bilingual(`Den Space „${space}“ gibt es nicht.`, `There’s no space “${space}”.`));
535
+ throw new ServiceError("not_found", note("unknownSpace", { space }));
536
536
  }
537
537
  await requireWritableProject(project.id);
538
538
  return project.id;
@@ -566,7 +566,7 @@ export async function putOriginPage(origin, input) {
566
566
  })
567
567
  : null;
568
568
  if (input.parent && !parent) {
569
- throw new ServiceError("invalid", bilingual(`Es gibt keine Seite mit dem Schlüssel „${input.parent}“, unter die diese Seite könnte.`, `There’s no page with the key “${input.parent}” to put this page under.`));
569
+ throw new ServiceError("invalid", note("noParentPage", { parent: input.parent }));
570
570
  }
571
571
  const title = input.title.replace(/\s+/g, " ").trim().slice(0, 200) || "Seite";
572
572
  const markdown = checkMarkdown(input.markdown);
@@ -630,7 +630,7 @@ export async function putOriginTable(origin, input) {
630
630
  }
631
631
  const columns = checkColumns(input.columns);
632
632
  if (input.rows.length > SPACE_DATA_LIMITS.rowsPerTable) {
633
- throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table can have at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
633
+ throw new ServiceError("refused", note("tableMostRows", { count: SPACE_DATA_LIMITS.rowsPerTable }));
634
634
  }
635
635
  let cells;
636
636
  try {
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { tableColumnsSchema } from "@engenty-wizards/shared/engenty/data-tables";
3
3
  import { CATEGORY_TYPES } from "@engenty-wizards/shared/knowledge";
4
4
  import { PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
5
- import { bilingual } from "@engenty-wizards/shared/run";
5
+ import { note } from "@engenty-wizards/shared/run";
6
6
  import { SPACE_DATA_LIMITS } from "@engenty-wizards/shared/space-data";
7
7
  import { WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
8
8
  import { and, count, eq, inArray, isNull, notInArray, sql } from "drizzle-orm";
@@ -150,9 +150,7 @@ async function requireRoom(spaceId) {
150
150
  .where(and(counted, sql `${schema.project.id} <> ${spaceId}`));
151
151
  const limit = await projectLimit();
152
152
  if (others.length >= limit) {
153
- throw new ServiceError("refused", limit === 1
154
- ? bilingual("Dieses Konto hat hier schon einen Space.", "This account already has a space here.")
155
- : bilingual(`Dieses Konto hat hier schon ${limit} Spaces.`, `This account already has ${limit} spaces here.`), {
153
+ throw new ServiceError("refused", limit === 1 ? note("accountAlreadySpace") : note("accountAlreadySpaces", { count: limit }), {
156
154
  reason: "space_limit",
157
155
  limit,
158
156
  spaces: others.filter((p) => p.origin === "local").map((p) => ({ id: p.id, name: p.name })),
@@ -166,7 +164,7 @@ async function requireWizardRoom(spaceId) {
166
164
  .from(schema.wizard)
167
165
  .where(eq(schema.wizard.projectId, spaceId));
168
166
  if ((row?.n ?? 0) >= env.limits.syncedWizards) {
169
- throw new ServiceError("refused", bilingual(`Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, `There’s room for at most ${env.limits.syncedWizards} published wizards here.`), { reason: "wizard_limit", limit: env.limits.syncedWizards });
167
+ throw new ServiceError("refused", note("roomMostPublishedWizards", { count: env.limits.syncedWizards }), { reason: "wizard_limit", limit: env.limits.syncedWizards });
170
168
  }
171
169
  }
172
170
  const hashOf = (data) => createHash("sha256").update(data).digest("hex");
@@ -200,7 +198,7 @@ async function syncLogo(projectId, logo) {
200
198
  async function syncSpace(spaceId, space) {
201
199
  const existing = await syncedProject(spaceId);
202
200
  if (existing && existing.origin !== "local") {
203
- throw new ServiceError("refused", bilingual("Einen Space mit dieser Kennung gibt es hier schon, und er kommt nicht von deinem Computer.", "A space with this ID already exists here and didn’t come from your computer."), {
201
+ throw new ServiceError("refused", note("spaceIdAlreadyExists"), {
204
202
  reason: "id_taken",
205
203
  });
206
204
  }
@@ -251,7 +249,7 @@ export async function syncWizard(rawSpaceId, rawWizardId, input) {
251
249
  where: and(eq(schema.wizard.id, wizardId), eq(schema.wizard.tenantId, currentTenant())),
252
250
  });
253
251
  if (existing && existing.projectId !== spaceId) {
254
- throw new ServiceError("refused", bilingual("Ein Wizard mit dieser Kennung gehört hier schon zu einem anderen Space.", "A wizard with this ID already belongs to another space here."), {
252
+ throw new ServiceError("refused", note("wizardIdAlreadyBelongs"), {
255
253
  reason: "id_taken",
256
254
  });
257
255
  }
@@ -507,7 +505,7 @@ export async function syncSpaceData(rawSpaceId, input) {
507
505
  : [],
508
506
  ]);
509
507
  if ([...tablesHere, ...pagesHere].some((x) => x.projectId !== spaceId || x.wizardId !== null)) {
510
- throw new ServiceError("refused", bilingual("Diese Kennung ist hier schon vergeben.", "This ID is already in use here."), {
508
+ throw new ServiceError("refused", note("idAlreadyUse"), {
511
509
  reason: "id_taken",
512
510
  });
513
511
  }
@@ -585,7 +583,7 @@ async function syncCategories(spaceId, categories, assignments) {
585
583
  })
586
584
  : [];
587
585
  if (taken.some((c) => c.projectId !== spaceId)) {
588
- throw new ServiceError("refused", bilingual("Diese Kennung ist hier schon vergeben.", "This ID is already in use here."), {
586
+ throw new ServiceError("refused", note("idAlreadyUse"), {
589
587
  reason: "id_taken",
590
588
  });
591
589
  }