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,6 +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
+ import { note, saidText } from "@engenty-wizards/shared/run";
4
4
  import { classProblem, filmClient } from "../models.js";
5
5
  // The steps every run passes through, and the choices that lead around a missing model, are
6
6
  // pure and shared (packages/shared/src/fit.ts): the standalone check uses the same.
@@ -273,13 +273,12 @@ export function blockingMessage(missing) {
273
273
  if (!blocking.length) {
274
274
  return null;
275
275
  }
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
- })
281
- .join(" ");
282
- return bilingual(line("de"), line("en"));
276
+ return blocking
277
+ .map((m) => note("blockedSteps", {
278
+ steps: m.steps.map((s) => note("quoted", { text: s.title })),
279
+ problem: m.problem,
280
+ }))
281
+ .reduce((first, second) => note("joined", { first, second }));
283
282
  }
284
283
  /** The class a marketplace capability stands for; the rest need no model of their own. */
285
284
  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 { bilingual, saidText, } from "@engenty-wizards/shared/run";
3
+ import { note, 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";
@@ -43,7 +43,7 @@ export class RunInputError extends NotedError {
43
43
  }
44
44
  export class NoCreditsError extends NotedError {
45
45
  constructor() {
46
- super(bilingual("Dieser Wizard hat gerade kein Guthaben mehr. Versuch es später noch einmal.", "This wizard has run out of credits for now. Try again later."));
46
+ super(note("wizardRunOutCredits"));
47
47
  }
48
48
  }
49
49
  /** The runs this process drives right now, with the handle that stops each. */
@@ -113,19 +113,19 @@ function friendly(err) {
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 bilingual("Das Guthaben beim Modellanbieter reicht dafür nicht aus (Videos brauchen dort ein Mindestguthaben). Lade dort auf und versuch es noch einmal.", "Your credit with the model provider isn’t enough for this (videos need a minimum balance there). Top up there and try again.");
116
+ return note("providerCreditLow");
117
117
  }
118
118
  if (/rate.?limit|429/i.test(msg)) {
119
- return bilingual("Der KI-Dienst ist gerade ausgelastet. Versuch es gleich noch einmal.", "The AI service is busy right now. Try again in a moment.");
119
+ return note("aiServiceBusyRight");
120
120
  }
121
121
  if (/timeout|timed out/i.test(msg)) {
122
- return bilingual("Der Schritt hat zu lange gedauert. Versuch es noch einmal.", "The step took too long. Try again.");
122
+ return note("stepTookTooLong");
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 bilingual("Der KI-Dienst ist gerade nicht erreichbar. Versuch es später noch einmal.", "The AI service can’t be reached right now. Try again later.");
126
+ return note("aiServiceNotReached");
127
127
  }
128
- return bilingual(`Dieser Schritt hat nicht geklappt: ${msg.slice(0, 300)}`, `This step failed: ${msg.slice(0, 300)}`);
128
+ return note("stepFailed", { detail: 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
@@ -637,7 +637,7 @@ export async function submitPage(runId, stepId, input) {
637
637
  (choice.values.length === 0 || choice.values.some((v) => String(v) === String(answer)))) {
638
638
  errors.push({
639
639
  field: id,
640
- message: bilingual("Das ist hier gerade nicht verfügbar.", "That isn’t available here right now."),
640
+ message: note("notAvailableNow"),
641
641
  });
642
642
  }
643
643
  }
@@ -653,9 +653,7 @@ export async function submitPage(runId, stepId, input) {
653
653
  for (const id of Array.isArray(value) ? value : value ? [value] : []) {
654
654
  const asset = await db.query.asset.findFirst({ where: eq(schema.asset.id, String(id)) });
655
655
  if (asset?.runId !== runId) {
656
- throw new RunInputError([
657
- { field: field.id, message: bilingual("Datei nicht gefunden", "File not found") },
658
- ]);
656
+ throw new RunInputError([{ field: field.id, message: note("fileNotFound") }]);
659
657
  }
660
658
  }
661
659
  }
@@ -666,9 +664,7 @@ export async function submitPage(runId, stepId, input) {
666
664
  values[field.id] = account.label;
667
665
  }
668
666
  else if (field.required) {
669
- throw new RunInputError([
670
- { field: field.id, message: bilingual("Verbinde zuerst", "Connect first") },
671
- ]);
667
+ throw new RunInputError([{ field: field.id, message: note("connectFirst") }]);
672
668
  }
673
669
  }
674
670
  }
@@ -1,7 +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
+ import { note as runNote } from "@engenty-wizards/shared/run";
5
5
  import { SURFACE_GUIDE, validateSurface, } from "@engenty-wizards/shared/surface";
6
6
  import { Agent } from "@mastra/core/agent";
7
7
  import { generateText, Output } from "ai";
@@ -379,7 +379,7 @@ function agentEntries(step, ctx) {
379
379
  async function runAgentEach(step, ctx) {
380
380
  const entries = agentEntries(step, ctx);
381
381
  if (!entries.length) {
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
+ throw new StepError(runNote("noEntriesStepWork"));
383
383
  }
384
384
  // The view, if any, is composed once over the table of all entries.
385
385
  const single = {
@@ -554,7 +554,7 @@ export async function runAgentStep(step, ctx) {
554
554
  const text = step.output.format === "json" ? everything : closing || everything;
555
555
  if (step.output.format !== "json") {
556
556
  if (!text && !assets.length) {
557
- throw new StepError(bilingual("Der Schritt hat kein Ergebnis geliefert.", "The step didn’t return a result."));
557
+ throw new StepError(runNote("stepNotReturnResult"));
558
558
  }
559
559
  return { text, assets: assets.length ? assets : undefined, at: new Date().toISOString() };
560
560
  }
@@ -755,7 +755,7 @@ async function writeHtml(step, ctx, kind) {
755
755
  .replace(/```\s*$/, "")
756
756
  .trim();
757
757
  if (!/<html|<!doctype/i.test(html)) {
758
- throw new StepError(bilingual("Das Dokument konnte nicht erstellt werden.", "The document couldn’t be created."));
758
+ throw new StepError(runNote("documentNotCreated"));
759
759
  }
760
760
  // On screen (preview, PNG) a print document needs the margin its @page rule gives it on paper.
761
761
  return kind === "document"
@@ -806,7 +806,7 @@ async function inParallel(items, limit, work) {
806
806
  async function runMediaStep(step, ctx, asset) {
807
807
  const entries = entriesOf(step, ctx);
808
808
  if (!entries.length) {
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
+ throw new StepError(runNote("stepMissingSourcesImages"));
810
810
  }
811
811
  const previous = ctx.state.outputs[step.id];
812
812
  const before = previous?.assets?.filter((a) => a.kind === asset) ?? [];
@@ -898,7 +898,7 @@ export async function runGenerateStep(step, ctx) {
898
898
  case "voice": {
899
899
  const text = renderTemplate(step.prompt, ctx.scope).trim().slice(0, 4000);
900
900
  if (!text) {
901
- throw new StepError(bilingual("Es gibt keinen Text zum Vorlesen.", "There is no text to read aloud."));
901
+ throw new StepError(runNote("noTextReadAloud"));
902
902
  }
903
903
  const note = ctx.state.notes[step.id];
904
904
  const media = await generateSpeechMedia({
@@ -1,5 +1,5 @@
1
1
  import { assembleSurface, dataRef, listRef, } from "@engenty-wizards/shared/definition";
2
- import { bilingual } from "@engenty-wizards/shared/run";
2
+ import { note } from "@engenty-wizards/shared/run";
3
3
  import { validateSurface } from "@engenty-wizards/shared/surface";
4
4
  import { decide } from "./decide.js";
5
5
  import { answersAsText, resolveRef } from "./template.js";
@@ -14,7 +14,7 @@ export async function runSurfaceStep(step, ctx) {
14
14
  const components = step.components ?? (await decidedComponents(step, ctx));
15
15
  const issues = validateSurface(components, Object.keys(data));
16
16
  if (issues.length) {
17
- throw new StepError(bilingual(`Die Ansicht hat Fehler: ${issues.map((i) => i.message).join(" ")}`, `The view has errors: ${issues.map((i) => i.message).join(" ")}`));
17
+ throw new StepError(note("viewErrors", { detail: issues.map((i) => i.message).join(" ") }));
18
18
  }
19
19
  return { surface: { components, data }, at: new Date().toISOString() };
20
20
  }
@@ -2,12 +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
+ import { note as runNote } from "@engenty-wizards/shared/run";
6
6
  import { StepError } from "../engine/types.js";
7
7
  import { CLAUDE_BIN, claudeEnv } from "../harness/claude.js";
8
8
  import { failureOf } from "../harness/model.js";
9
9
  import { openBridge } from "../mcp/bridge.js";
10
- import { errorText } from "../model-errors.js";
10
+ import { saidOf } from "../model-errors.js";
11
11
  import { filmClient } from "../models.js";
12
12
  import { prepareFilmFolder, savedSession, saveSession } from "./project.js";
13
13
  import { runHyperframes } from "./tools.js";
@@ -229,13 +229,13 @@ export async function runFilmStep(step, ctx) {
229
229
  if (ctx.signal.aborted) {
230
230
  throw err;
231
231
  }
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 try carries on from there.`));
232
+ throw new StepError(runNote("filmClientStopped", { name: client.name, detail: err.message }));
233
233
  });
234
234
  const result = last;
235
235
  if (!result || result.is_error) {
236
236
  const detail = String(result?.result ?? (stderr.trim() || "no result")).slice(0, 400);
237
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 try carries on from there.`));
238
+ throw new StepError(runNote("filmWorkSaved", { problem: saidOf(failure) }));
239
239
  }
240
240
  return result;
241
241
  };
@@ -265,7 +265,7 @@ export async function runFilmStep(step, ctx) {
265
265
  }
266
266
  const problem = renderProblem(render.out);
267
267
  if (attempt >= RENDER_FIXES) {
268
- throw new StepError(bilingual(`Der Film ließ sich nicht rendern: ${problem.slice(0, 400)}`, `The film couldn’t be rendered: ${problem.slice(0, 400)}`));
268
+ throw new StepError(runNote("filmRenderFailed", { detail: problem.slice(0, 400) }));
269
269
  }
270
270
  await ctx.emit("tool", { code: "filmFix" });
271
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));
@@ -2,6 +2,7 @@ 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 { langOf, SPEECH_LOCALES } from "@engenty-wizards/shared/definition";
5
6
  import { nanoid } from "nanoid";
6
7
  import { env } from "../env.js";
7
8
  import { ffmpeg, hasFfmpeg } from "../media/ffmpeg.js";
@@ -168,7 +169,7 @@ export function appleModel(alias) {
168
169
  /** The Mac's own language: what a voice note is most likely in, unless the call names one. */
169
170
  function systemLocale() {
170
171
  const locale = Intl.DateTimeFormat().resolvedOptions().locale;
171
- return /^[a-z]{2}-[A-Z]{2}$/.test(locale) ? locale : locale.startsWith("de") ? "de-DE" : "en-US";
172
+ return /^[a-z]{2}-[A-Z]{2}$/.test(locale) ? locale : SPEECH_LOCALES[langOf(locale) ?? "en"];
172
173
  }
173
174
  /** What AVAudioFile reads as it is; a browser's WebM or Ogg (Opus) is turned into WAV first. */
174
175
  function audioExtension(mediaType) {
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { resolveEnv, run } from "./env.js";
3
3
  import { failureOf, HarnessModel, lastJson, runClient, } from "./model.js";
4
4
  /**
@@ -143,5 +143,5 @@ export const claude = {
143
143
  },
144
144
  login: { args: ["auth", "login"], interactive: false },
145
145
  model: (alias, effort) => new ClaudeCodeModel(claude, alias || "sonnet", effort),
146
- exhausted: bilingual("Das Anthropic-Konto, mit dem Claude Code angemeldet ist, hat kein Guthaben. Melde Claude Code unter Einstellungen → Modelle mit deinem Abo an.", "The Anthropic account Claude Code is signed in with has no credit. Sign Claude Code in with your subscription under Settings → Models."),
146
+ exhausted: note("anthropicAccountClaudeCode"),
147
147
  };
@@ -1,7 +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
+ import { note } from "@engenty-wizards/shared/run";
5
5
  import { resolveEnv, run } from "./env.js";
6
6
  import { failureOf, HarnessImageModel, HarnessModel, runClient, } from "./model.js";
7
7
  /**
@@ -225,5 +225,5 @@ export const codex = {
225
225
  login: { args: ["login"], interactive: false },
226
226
  model: (alias, effort) => new CodexModel(codex, alias || "default", effort),
227
227
  image: { alias: "image", model: (alias) => new CodexImageModel(codex, alias || "image") },
228
- exhausted: bilingual("Das Kontingent des ChatGPT-Kontos, mit dem Codex angemeldet ist, ist aufgebraucht. Versuch es später noch einmal oder wähle einen anderen Client.", "The ChatGPT account Codex is signed in with has used up its quota. Try again later or choose another client."),
228
+ exhausted: note("chatgptAccountCodexSigned"),
229
229
  };
@@ -1,17 +1,16 @@
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
+ import { note } from "@engenty-wizards/shared/run";
5
5
  import { nanoid } from "nanoid";
6
6
  import { env } from "../env.js";
7
7
  import { openBridge } from "../mcp/bridge.js";
8
8
  import { ModelUnavailableError } from "../model-errors.js";
9
9
  import { extensionOf, jsonInstruction, parseJsonAnswer, renderPrompt } from "./prompt.js";
10
10
  const workDir = join(env.dataDir, "harness");
11
- export const signedOut = (h) => bilingual(`${h.name} ist auf diesem Computer nicht angemeldet. Melde dich unter Einstellungen → Modelle an.`, `${h.name} isn’t signed in on this computer. Sign in under Settings → Models.`);
12
- export const notInstalled = (h) => bilingual(`${h.name} ist auf diesem Computer nicht installiert. Installiere es unter Einstellungen → Modelle.`, `${h.name} isn’t installed on this computer. Install it under Settings → Models.`);
13
- export const exhausted = (h) => h.exhausted ??
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.`);
11
+ export const signedOut = (h) => note("clientNotSignedIn", { name: h.name });
12
+ export const notInstalled = (h) => note("clientNotInstalled", { name: h.name });
13
+ export const exhausted = (h) => h.exhausted ?? note("clientNoCredit", { name: h.name });
15
14
  /** The failure a client reports, as the person should read it. */
16
15
  export function failureOf(h, detail) {
17
16
  // The client's own words stay in the log: the sentence below sums them up for the person.
@@ -1,7 +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
+ import { note } from "@engenty-wizards/shared/run";
5
5
  import { nanoid } from "nanoid";
6
6
  import { ServiceError } from "../services/errors.js";
7
7
  const BUFFER = 200_000;
@@ -37,7 +37,7 @@ async function ptyModule() {
37
37
  return { spawn };
38
38
  }
39
39
  catch {
40
- throw new ServiceError("refused", bilingual("Das eingebaute Terminal ist auf diesem Computer nicht verfügbar. Führe den Befehl in deinem eigenen Terminal aus.", "The built-in terminal isn’t available on this computer. Run the command in your own terminal."));
40
+ throw new ServiceError("refused", note("builtTerminalNotAvailable"));
41
41
  }
42
42
  }
43
43
  function emit(session, event) {
@@ -85,7 +85,7 @@ export async function openTerminal(opts) {
85
85
  });
86
86
  }
87
87
  catch (err) {
88
- throw new ServiceError("refused", bilingual(`Der Befehl konnte nicht starten: ${err.message}`, `The command couldn’t start: ${err.message}`));
88
+ throw new ServiceError("refused", note("commandNotStarted", { detail: err.message }));
89
89
  }
90
90
  const session = {
91
91
  id,
@@ -147,7 +147,7 @@ export async function openTerminal(opts) {
147
147
  function sessionOf(id) {
148
148
  const session = sessions.get(id);
149
149
  if (!session) {
150
- throw new ServiceError("not_found", bilingual("Dieses Terminal gibt es nicht mehr.", "This terminal no longer exists."));
150
+ throw new ServiceError("not_found", note("terminalNoLongerExists"));
151
151
  }
152
152
  return session;
153
153
  }
@@ -1,3 +1,4 @@
1
+ import { LANG_NAMES } from "@engenty-wizards/shared/definition";
1
2
  import { NotedError } from "../model-errors.js";
2
3
  import { conversationAccess } from "../models.js";
3
4
  /**
@@ -10,8 +11,8 @@ const SECRETS_URL = "https://api.openai.com/v1/realtime/client_secrets";
10
11
  const CALLS_URL = "https://api.openai.com/v1/realtime/calls";
11
12
  /** How long a minted secret opens a session; the session itself goes on until it ends. */
12
13
  const SECRET_TTL_SECONDS = 600;
13
- /** The voice the wizard speaks with, by language. */
14
- const VOICES = { de: "marin", en: "marin" };
14
+ /** The voice the wizard speaks with: one that speaks every language engenty does. */
15
+ const VOICE = "marin";
15
16
  /** What the model may do: one tool per command of the run page, run by the browser. */
16
17
  export const CONVERSATION_TOOLS = [
17
18
  {
@@ -97,41 +98,23 @@ export const CONVERSATION_TOOLS = [
97
98
  parameters: { type: "object", properties: {}, additionalProperties: false },
98
99
  },
99
100
  ];
100
- /** The rules the model speaks by, in the wizard's language. */
101
+ /** The rules the model speaks by: written in English, it speaks the wizard's language. */
101
102
  export function conversationInstructions(view) {
102
- const de = view.lang === "de";
103
- const about = [
104
- `${de ? "Du bist" : "You are"} ${view.wizard.title}.`,
105
- view.wizard.description,
106
- view.wizard.intro,
107
- ]
103
+ const about = [`You are ${view.wizard.title}.`, view.wizard.description, view.wizard.intro]
108
104
  .filter(Boolean)
109
105
  .join(" ");
110
- const rules = de
111
- ? [
112
- "Du führst ein Gespräch, das einen Wizard Schritt für Schritt ausfüllt. Die Person sieht den Wizard auch auf dem Bildschirm.",
113
- "Sprich Deutsch, kurz und freundlich, wie am Telefon. Ein Satz, dann die nächste Frage.",
114
- "Frag ein Feld nach dem anderen, in der Reihenfolge des Zustands. Nach jeder Antwort rufst du set_field auf. Sind alle Felder beantwortet, die du per Sprache füllen kannst, rufst du submit_page auf.",
115
- "Bei einer Auswahl nennst du die Möglichkeiten (höchstens vier auf einmal) und gibst genau eine davon weiter.",
116
- "E-Mail-Adressen, Links und Zahlen wiederholst du zur Bestätigung, bevor du sie weitergibst.",
117
- "Ein Feld, das den Bildschirm braucht (ein Foto, eine Datei, eine Unterschrift, eine Verbindung), füllt die Person dort aus: sag das in einem Satz und warte auf den nächsten Zustand.",
118
- "Während der Wizard arbeitet, sagst du in einem Satz, was er gerade tut, und wartest. Erfinde nichts.",
119
- "Bei einer Prüfung liest du den Text kurz zusammengefasst vor und fragst, ob er passt. Dann accept_review, oder regenerate mit dem Wunsch der Person.",
120
- "Am Ende sagst du, was entstanden ist und dass es auf dem Bildschirm liegt.",
121
- "Ist die Kamera an, kannst du mit look sehen, was die Person zeigt, und mit take_photo ein Foto in ein Fotofeld legen; ein Fotofeld fragst du dann so: „Halte es in die Kamera, ich mache das Foto.“ Ist die Kamera aus, bitte die Person, sie einzuschalten, oder lass das Feld dem Bildschirm.",
122
- ]
123
- : [
124
- "You hold a conversation that fills in a wizard step by step. The person also sees the wizard on the screen.",
125
- "Speak English, short and friendly, as on the phone. One sentence, then the next question.",
126
- "Ask one field at a time, in the order of the state. After each answer call set_field. When every field you can fill by voice is answered, call submit_page.",
127
- "For a choice, name the options (at most four at a time) and pass exactly one of them on.",
128
- "Read e-mail addresses, links and numbers back before passing them on.",
129
- "A field that needs the screen (a photo, a file, a signature, a connection) is filled there by the person: say so in one sentence and wait for the next state.",
130
- "While the wizard works, say in one sentence what it is doing and wait. Never make anything up.",
131
- "At a review, sum the text up in a few words and ask whether it fits. Then accept_review, or regenerate with what the person wants changed.",
132
- "At the end, say what was made and that it is on the screen.",
133
- "With the camera on you can see what the person shows with look, and put a photo into a photo field with take_photo; ask for a photo field like this: 'Hold it to the camera, I will take the picture.' With the camera off, ask the person to switch it on, or leave the field to the screen.",
134
- ];
106
+ const rules = [
107
+ "You hold a conversation that fills in a wizard step by step. The person also sees the wizard on the screen.",
108
+ `Speak ${LANG_NAMES[view.lang]} (${view.lang}), short and friendly, as on the phone. One sentence, then the next question.`,
109
+ "Ask one field at a time, in the order of the state. After each answer call set_field. When every field you can fill by voice is answered, call submit_page.",
110
+ "For a choice, name the options (at most four at a time) and pass exactly one of them on.",
111
+ "Read e-mail addresses, links and numbers back before passing them on.",
112
+ "A field that needs the screen (a photo, a file, a signature, a connection) is filled there by the person: say so in one sentence and wait for the next state.",
113
+ "While the wizard works, say in one sentence what it is doing and wait. Never make anything up.",
114
+ "At a review, sum the text up in a few words and ask whether it fits. Then accept_review, or regenerate with what the person wants changed.",
115
+ "At the end, say what was made and that it is on the screen.",
116
+ "With the camera on you can see what the person shows with look, and put a photo into a photo field with take_photo; ask for a photo field like this: 'Hold it to the camera, I will take the picture.' With the camera off, ask the person to switch it on, or leave the field to the screen.",
117
+ ];
135
118
  return `${about}\n\n${rules.join("\n")}`;
136
119
  }
137
120
  /** Mints the secret a browser opens a session with; a failure carries a note for the run's language. */
@@ -157,7 +140,7 @@ export async function mintConversation(view) {
157
140
  noise_reduction: { type: "far_field" },
158
141
  turn_detection: { type: "server_vad", threshold: 0.7, silence_duration_ms: 600 },
159
142
  },
160
- output: { voice: VOICES[view.lang] },
143
+ output: { voice: VOICE },
161
144
  },
162
145
  },
163
146
  }),
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
+ import { SPEECH_LOCALES } from "@engenty-wizards/shared/definition";
3
4
  import { generateText, experimental_transcribe as transcribe } from "ai";
4
5
  import { costOf, listenerModel, transcriptionCostUsd } from "../models.js";
5
6
  import { ffmpeg, hasFfmpeg, probeMedia, withTempDir } from "./ffmpeg.js";
@@ -18,7 +19,7 @@ export async function transcribeAudio(input) {
18
19
  audio: input.bytes,
19
20
  abortSignal: input.abortSignal,
20
21
  ...(input.language
21
- ? { providerOptions: { apple: { locale: input.language === "en" ? "en-US" : "de-DE" } } }
22
+ ? { providerOptions: { apple: { locale: SPEECH_LOCALES[input.language] } } }
22
23
  : {}),
23
24
  });
24
25
  return {
@@ -1,6 +1,6 @@
1
1
  import { noteText } from "@engenty-wizards/shared/run";
2
2
  /**
3
- * A failure the person reads, given as a note of `RUN_NOTES` (or `bilingual` words) where it has
3
+ * A failure the person reads, given as a note of `RUN_NOTES` where it has
4
4
  * one: a run shows it in the wizard's language, a response in the asker's, and the message keeps
5
5
  * the English line for the log. Free text has no note.
6
6
  */