wizards 0.5.7 → 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 (108) 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 +6 -6
  5. package/apps/runtime/dist/assistant/knows.js +10 -9
  6. package/apps/runtime/dist/connectors/external.js +3 -3
  7. package/apps/runtime/dist/connectors/index.js +23 -23
  8. package/apps/runtime/dist/connectors/via-account.js +6 -6
  9. package/apps/runtime/dist/documents/parse.js +3 -3
  10. package/apps/runtime/dist/engine/input.js +11 -11
  11. package/apps/runtime/dist/engine/requirements.js +7 -8
  12. package/apps/runtime/dist/engine/runner.js +10 -14
  13. package/apps/runtime/dist/engine/steps.js +6 -6
  14. package/apps/runtime/dist/engine/surface.js +2 -2
  15. package/apps/runtime/dist/film/step.js +5 -5
  16. package/apps/runtime/dist/harness/apple.js +2 -1
  17. package/apps/runtime/dist/harness/claude.js +2 -2
  18. package/apps/runtime/dist/harness/codex.js +2 -2
  19. package/apps/runtime/dist/harness/model.js +4 -5
  20. package/apps/runtime/dist/harness/terminal.js +4 -4
  21. package/apps/runtime/dist/media/conversation.js +18 -35
  22. package/apps/runtime/dist/media/transcribe.js +2 -1
  23. package/apps/runtime/dist/model-errors.js +1 -1
  24. package/apps/runtime/dist/models.js +32 -40
  25. package/apps/runtime/dist/plugins/runs.js +3 -5
  26. package/apps/runtime/dist/routes/api.js +9 -9
  27. package/apps/runtime/dist/routes/lang.js +14 -9
  28. package/apps/runtime/dist/routes/runs.js +5 -5
  29. package/apps/runtime/dist/routes/studio.js +38 -37
  30. package/apps/runtime/dist/services/access.js +4 -4
  31. package/apps/runtime/dist/services/architect.js +3 -12
  32. package/apps/runtime/dist/services/assistant-chats.js +2 -2
  33. package/apps/runtime/dist/services/brand.js +3 -3
  34. package/apps/runtime/dist/services/cloud.js +5 -5
  35. package/apps/runtime/dist/services/errors.js +2 -2
  36. package/apps/runtime/dist/services/knowledge-model.js +3 -3
  37. package/apps/runtime/dist/services/marketplace.js +3 -3
  38. package/apps/runtime/dist/services/package-file.js +9 -11
  39. package/apps/runtime/dist/services/package.js +8 -9
  40. package/apps/runtime/dist/services/profile.js +2 -1
  41. package/apps/runtime/dist/services/project-files.js +13 -13
  42. package/apps/runtime/dist/services/projects.js +7 -11
  43. package/apps/runtime/dist/services/runners.js +4 -4
  44. package/apps/runtime/dist/services/runs.js +7 -10
  45. package/apps/runtime/dist/services/shares.js +2 -2
  46. package/apps/runtime/dist/services/site.js +8 -11
  47. package/apps/runtime/dist/services/space-categories.js +7 -7
  48. package/apps/runtime/dist/services/space-data.js +20 -20
  49. package/apps/runtime/dist/services/spaces.js +7 -9
  50. package/apps/runtime/dist/services/wizards.js +11 -13
  51. package/apps/runtime/dist/store/index.js +2 -2
  52. package/apps/runtime/dist/texts.js +195 -0
  53. package/apps/runtime/dist/tools/plugin.js +7 -3
  54. package/apps/runtime/dist/widgets/step.js +2 -2
  55. package/apps/web/dist/assets/AboutDialog-BcTtA42P.js +1 -0
  56. package/apps/web/dist/assets/{ChangelogDialog-rAtYZjxz.js → ChangelogDialog-DMOWV_Iu.js} +1 -1
  57. package/apps/web/dist/assets/ChatView-CrnrYHjp.js +10 -0
  58. package/apps/web/dist/assets/EditorPage-DpEw3eOu.js +3 -0
  59. package/apps/web/dist/assets/{FlowPreview--3-yI51e.js → FlowPreview-CzmH4JUQ.js} +1 -1
  60. package/apps/web/dist/assets/{Harness-B9tvFe0U.js → Harness-DipH0Vec.js} +2 -2
  61. package/apps/web/dist/assets/{NewWizardChannels-BxrBhC9U.js → NewWizardChannels-DShu0PP6.js} +1 -1
  62. package/apps/web/dist/assets/NewWizardPage-HSa6PNJs.js +7 -0
  63. package/apps/web/dist/assets/{OnboardingPage-DPdH9sSj.js → OnboardingPage-CPADMs4c.js} +1 -1
  64. package/apps/web/dist/assets/ProjectPage-DMPcrhgt.js +1 -0
  65. package/apps/web/dist/assets/{PublicRunner-Bv3TMKw4.js → PublicRunner-DZ5sWmtg.js} +1 -1
  66. package/apps/web/dist/assets/{ReadOnly-CmqIrOAR.js → ReadOnly-B9xaq_M3.js} +1 -1
  67. package/apps/web/dist/assets/RunDrawer-9nMGhbSm.js +4 -0
  68. package/apps/web/dist/assets/{RunnerView-Cd1VAlIA.js → RunnerView-NSSP3tc6.js} +1 -1
  69. package/apps/web/dist/assets/SettingsPage-Dy9MbwQh.js +1 -0
  70. package/apps/web/dist/assets/SetupPage-kemgI7v5.js +1 -0
  71. package/apps/web/dist/assets/{SharePage-BT80wI1I.js → SharePage-CQfIdcwy.js} +1 -1
  72. package/apps/web/dist/assets/changelog-BW40vzI6.js +1 -0
  73. package/apps/web/dist/assets/{esm-BTVd6EFo.js → esm-BF1nonbH.js} +1 -1
  74. package/apps/web/dist/assets/index-DQ9qAB4v.js +14 -0
  75. package/apps/web/dist/assets/{lock-ByHFI96G.js → lock-DVvM6IsA.js} +1 -1
  76. package/apps/web/dist/assets/{phone-KPwxAM1R.js → phone-3fG1okFs.js} +1 -1
  77. package/apps/web/dist/assets/{settings-menu-K5bg3QE3.js → settings-menu-D9fbi1Z0.js} +1 -1
  78. package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
  79. package/apps/web/dist/assets/{upload-BsIL8tNR.js → upload-BsMUhEfX.js} +1 -1
  80. package/apps/web/dist/index.html +2 -2
  81. package/apps/web/dist/mcp/flow.html +77 -77
  82. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
  83. package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
  84. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
  85. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
  86. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
  87. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
  88. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
  89. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
  90. package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
  91. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
  92. package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
  93. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
  94. package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
  95. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
  96. package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
  97. package/package.json +1 -1
  98. package/apps/web/dist/assets/AboutDialog-t9bayaIs.js +0 -1
  99. package/apps/web/dist/assets/ChatView-C3VqLT98.js +0 -10
  100. package/apps/web/dist/assets/EditorPage-D3Ef24b9.js +0 -3
  101. package/apps/web/dist/assets/NewWizardPage-C1_hsQPr.js +0 -7
  102. package/apps/web/dist/assets/ProjectPage-P40R1cPE.js +0 -1
  103. package/apps/web/dist/assets/RunDrawer-9qG1G-OY.js +0 -4
  104. package/apps/web/dist/assets/SettingsPage-DFz4UMfo.js +0 -1
  105. package/apps/web/dist/assets/SetupPage-B0UBNMB9.js +0 -1
  106. package/apps/web/dist/assets/changelog-BpSBMjpm.js +0 -1
  107. package/apps/web/dist/assets/index-D8vwgldH.js +0 -14
  108. package/apps/web/dist/assets/ui-4bQx9I1f.js +0 -134
@@ -15,6 +15,7 @@ import { deleteFile, listFiles, readFileText, writeFile } from "../services/file
15
15
  import { checkDraftWidget } from "../services/widgets.js";
16
16
  import { editWizard, ownedWizard, writeDraft } from "../services/wizards.js";
17
17
  import { currentTenant } from "../tenants/tenant.js";
18
+ import { serverText } from "../texts.js";
18
19
  import { assertPublicUrl, safeFetch } from "../tools/net-guard.js";
19
20
  import { runScript } from "../widgets/script.js";
20
21
  import { thoughtLine } from "./thought.js";
@@ -54,7 +55,7 @@ HOW YOU WORK
54
55
  }
55
56
  /** Turns a tool call into the line the admin sees while it runs. */
56
57
  function activity(tool, args, lang = "en") {
57
- const de = lang === "de";
58
+ const say = (key, params) => serverText(lang, key, params);
58
59
  const host = (u) => {
59
60
  try {
60
61
  return new URL(String(u)).hostname;
@@ -66,42 +67,28 @@ function activity(tool, args, lang = "en") {
66
67
  switch (tool) {
67
68
  case "replace_wizard":
68
69
  case "edit_wizard":
69
- return de ? "Baut den Ablauf …" : "Building the flow …";
70
+ return say("doingFlow");
70
71
  case "write_file":
71
72
  return args.path
72
- ? de
73
- ? `Schreibt ${String(args.path)} …`
74
- : `Writing ${String(args.path)} …`
75
- : de
76
- ? "Schreibt eine Datei …"
77
- : "Writing a file …";
73
+ ? say("doingWriteFile", { path: String(args.path) })
74
+ : say("doingWriteAFile");
78
75
  case "download_files":
79
- return de ? "Lädt Daten herunter …" : "Downloading data …";
76
+ return say("doingDownload");
80
77
  case "web_fetch":
81
- return args.url
82
- ? de
83
- ? `Liest ${host(args.url)} …`
84
- : `Reading ${host(args.url)} …`
85
- : de
86
- ? "Liest im Web …"
87
- : "Reading the web …";
78
+ return args.url ? say("doingReadSite", { host: host(args.url) }) : say("doingReadWeb");
88
79
  case "web_search":
89
- return de ? "Sucht im Web …" : "Searching the web …";
80
+ return say("doingWebSearch");
90
81
  case "check_widget":
91
- return de ? "Testet das Widget …" : "Testing the widget …";
82
+ return say("doingTestWidget");
92
83
  case "run_script":
93
84
  return args.output
94
- ? de
95
- ? `Bereitet ${String(args.output)} vor …`
96
- : `Preparing ${String(args.output)} …`
97
- : de
98
- ? "Rechnet …"
99
- : "Calculating …";
85
+ ? say("doingPrepare", { output: String(args.output) })
86
+ : say("doingCalculate");
100
87
  case "list_files":
101
88
  case "read_file":
102
- return de ? "Sieht sich die Dateien an …" : "Looking at the files …";
89
+ return say("doingLookFiles");
103
90
  case "delete_file":
104
- return de ? "Räumt Dateien auf …" : "Tidying up files …";
91
+ return say("doingTidyFiles");
105
92
  default:
106
93
  return null;
107
94
  }
@@ -15,6 +15,7 @@ import { documentText } from "../services/project-index.js";
15
15
  import { ownedProject, updateProject } from "../services/projects.js";
16
16
  import { categoryRows } from "../services/space-categories.js";
17
17
  import { currentTenant } from "../tenants/tenant.js";
18
+ import { serverText } from "../texts.js";
18
19
  import { assistantPageOf, assistantToolsOf } from "../tools/plugin.js";
19
20
  import { readWebsite } from "./website.js";
20
21
  /** How a message written meanwhile reaches the model. */
@@ -60,19 +61,19 @@ HOW YOU WORK
60
61
  - Call independent tools together. Work in few steps.
61
62
  - Finish with one to three sentences: what you did or found, what is still open, and at most one question.`;
62
63
  const LABELS = {
63
- save_logo: { de: "Holt das Logo …", en: "Fetching the logo …" },
64
- save_file: { de: "Holt eine Datei …", en: "Fetching a file …" },
65
- set_logo_sections: { de: "Ordnet die Logos …", en: "Grouping the logos …" },
66
- set_asset_sections: { de: "Ordnet die Assets …", en: "Grouping the assets …" },
67
- set_typography: { de: "Trägt die Schriften ein …", en: "Entering the fonts …" },
68
- set_gradients: { de: "Trägt die Verläufe ein …", en: "Entering the gradients …" },
69
- set_rules: { de: "Trägt Regeln ein …", en: "Entering rules …" },
70
- set_profile: { de: "Schreibt Titel und Beschreibung …", en: "Writing title and description …" },
71
- set_facts: { de: "Trägt Fakten ein …", en: "Entering facts …" },
72
- set_colors: { de: "Trägt die Farben ein …", en: "Entering the colours …" },
73
- look_at_file: { de: "Sieht sich eine Datei an …", en: "Looking at a file …" },
74
- describe_file: { de: "Beschreibt eine Datei …", en: "Describing a file …" },
75
- web_search: { de: "Sucht im Web …", en: "Searching the web …" },
64
+ save_logo: "doingSaveLogo",
65
+ save_file: "doingSaveFile",
66
+ set_logo_sections: "doingSetLogoSections",
67
+ set_asset_sections: "doingSetAssetSections",
68
+ set_typography: "doingSetTypography",
69
+ set_gradients: "doingSetGradients",
70
+ set_rules: "doingSetRules",
71
+ set_profile: "doingSetProfile",
72
+ set_facts: "doingSetFacts",
73
+ set_colors: "doingSetColors",
74
+ look_at_file: "doingLookAtFile",
75
+ describe_file: "doingDescribeFile",
76
+ web_search: "doingWebSearch",
76
77
  };
77
78
  function activity(tool, args, lang) {
78
79
  const host = (u) => {
@@ -85,20 +86,16 @@ function activity(tool, args, lang) {
85
86
  };
86
87
  if (tool === "read_website") {
87
88
  const at = args.url ? host(args.url) : "";
88
- return lang === "de"
89
- ? at
90
- ? `Liest ${at} …`
91
- : "Liest die Website …"
92
- : at
93
- ? `Reading ${at} …`
94
- : "Reading the website …";
89
+ return at
90
+ ? serverText(lang, "doingReadSite", { host: at })
91
+ : serverText(lang, "doingReadWebsite");
95
92
  }
96
93
  const key = tool === "save_file" && args.kind === "logo"
97
94
  ? "save_logo"
98
95
  : tool === "remove_facts"
99
96
  ? "set_facts"
100
97
  : tool;
101
- return LABELS[key]?.[lang] ?? null;
98
+ return LABELS[key] ? serverText(lang, LABELS[key]) : null;
102
99
  }
103
100
  async function attempt(run) {
104
101
  try {
@@ -886,7 +883,7 @@ export async function runStudioAssistant(input) {
886
883
  else if (chunk.type === "tool-call") {
887
884
  // The model's own search has no tool of ours that could say so.
888
885
  if (payload.toolName === "web_search") {
889
- input.onActivity?.(LABELS.web_search[input.lang]);
886
+ input.onActivity?.(serverText(input.lang, LABELS.web_search));
890
887
  }
891
888
  // Keep what it says before and after a tool call apart.
892
889
  if (reply.trim() && !reply.endsWith("\n\n")) {
@@ -9,6 +9,7 @@ import { detectHarness, signedOut } from "../harness/index.js";
9
9
  import { localModelSettings } from "../models.js";
10
10
  import { ServiceError } from "../services/errors.js";
11
11
  import { deleteSetting, readSetting, writeSetting } from "../settings.js";
12
+ import { serverText } from "../texts.js";
12
13
  import { thoughtLine } from "./thought.js";
13
14
  /** The AI clients installed on this machine whose subscription can answer the studio chat. */
14
15
  export async function subscriptionClients() {
@@ -38,18 +39,18 @@ const SYSTEM = (wizardId) => [
38
39
  ].join("\n");
39
40
  const BUILDING = /edit_wizard|write_wizard|write_file|delete_file|create_wizard/;
40
41
  const ACTIVITY = [
41
- [/get_authoring_guide/, { de: "Liest die Bauanleitung", en: "Reading the building guide" }],
42
- [/get_starter|list_starters/, { de: "Sieht sich eine Vorlage an", en: "Looking at a template" }],
43
- [/get_wizard|list_files|read_file/, { de: "Liest den Wizard", en: "Reading the wizard" }],
44
- [/write_file|delete_file/, { de: "Schreibt Dateien", en: "Writing files" }],
45
- [/edit_wizard|write_wizard/, { de: "Baut den Wizard um", en: "Rebuilding the wizard" }],
46
- [/check_widget|preview/, { de: "Prüft das Widget", en: "Checking the widget" }],
47
- [/start_test_run|get_test_run/, { de: "Testet den Wizard", en: "Testing the wizard" }],
48
- [/find_connectors|import_connector/, { de: "Sucht einen Dienst", en: "Looking for a service" }],
42
+ [/get_authoring_guide/, "doingGuide"],
43
+ [/get_starter|list_starters/, "doingTemplate"],
44
+ [/get_wizard|list_files|read_file/, "doingReadWizard"],
45
+ [/write_file|delete_file/, "doingWriteFiles"],
46
+ [/edit_wizard|write_wizard/, "doingRebuild"],
47
+ [/check_widget|preview/, "doingCheckWidget"],
48
+ [/start_test_run|get_test_run/, "doingTestWizard"],
49
+ [/find_connectors|import_connector/, "doingFindService"],
49
50
  ];
50
51
  /** The activity for one tool call, naming the file or service it is about when it says. */
51
52
  function activity(name, args, lang = "en") {
52
- const label = ACTIVITY.find(([re]) => re.test(name))?.[1][lang] ?? (lang === "de" ? "Arbeitet" : "Working");
53
+ const label = serverText(lang, ACTIVITY.find(([re]) => re.test(name))?.[1] ?? "doingWork");
53
54
  const about = [args.path, args.query].find((v) => typeof v === "string" && v.trim());
54
55
  return about ? `${label}: ${String(about)} …` : `${label} …`;
55
56
  }
@@ -23,7 +23,7 @@ import { PLUGIN_NAME, pluginArchive, pluginMarketplace } from "./plugin.js";
23
23
  import { apiRoutes } from "./routes/api.js";
24
24
  import { browserReport, localDebugRoutes } from "./routes/debug.js";
25
25
  import { internalRoutes } from "./routes/internal.js";
26
- import { inLang, notFoundAnswer, requestLang } from "./routes/lang.js";
26
+ import { notFoundAnswer, requestLang, say } from "./routes/lang.js";
27
27
  import { marketplaceGuest, marketplaceStudio } from "./routes/marketplace.js";
28
28
  import { publicPluginRoutes } from "./routes/plugin-public.js";
29
29
  import { pluginRoutes } from "./routes/plugins.js";
@@ -44,13 +44,13 @@ app.onError((err, c) => {
44
44
  return c.json(err.toJSON(requestLang(c)), err.status);
45
45
  }
46
46
  if (err instanceof ZodError) {
47
- return c.json({ error: inLang(c, "Ungültige Eingabe", "Invalid input"), issues: err.issues }, 400);
47
+ return c.json({ error: say(c, "invalidInput"), issues: err.issues }, 400);
48
48
  }
49
49
  if (err instanceof HTTPException) {
50
50
  return err.getResponse();
51
51
  }
52
52
  console.error(err);
53
- return c.json({ error: inLang(c, "Interner Fehler", "Internal error") }, 500);
53
+ return c.json({ error: say(c, "internalError") }, 500);
54
54
  });
55
55
  // --- who may talk to this server at all ---------------------------------------------
56
56
  // A runtime that runs alone listens on the loopback interface. A web page in the person's
@@ -202,13 +202,13 @@ if (serves.studio) {
202
202
  const user = await principalOf(c);
203
203
  if (!user) {
204
204
  return c.json({
205
- error: inLang(c, "Du bist nicht angemeldet.", "You’re not signed in."),
205
+ error: say(c, "notSignedIn"),
206
206
  code: "unauthorized",
207
207
  }, 401);
208
208
  }
209
209
  if ((await tenantStatus(user.tenantId)) === "suspended") {
210
210
  return c.json({
211
- error: inLang(c, "Dieses Konto ist gesperrt.", "This account is suspended."),
211
+ error: say(c, "accountSuspended"),
212
212
  code: "suspended",
213
213
  }, 403);
214
214
  }
@@ -227,7 +227,7 @@ if (serves.runner) {
227
227
  // answers are text; an upload is a photo, a recording or a clip from a phone (routes/delivery.ts).
228
228
  const tooLarge = (max) => bodyLimit({
229
229
  maxSize: max,
230
- onError: (c) => c.json({ error: inLang(c, "Das ist zu groß.", "That is too large."), code: "too_large" }, 413),
230
+ onError: (c) => c.json({ error: say(c, "tooLarge"), code: "too_large" }, 413),
231
231
  });
232
232
  const answers = tooLarge(4 * 1024 * 1024);
233
233
  const upload = tooLarge(48 * 1024 * 1024);
@@ -13,6 +13,7 @@ import { pluginToolsOf } from "../plugins/registry.js";
13
13
  import { earlierChats, readChat, searchChats } from "../services/assistant-chats.js";
14
14
  import { asLang, marketplaceDetail, searchMarketplace } from "../services/marketplace.js";
15
15
  import { listWizards } from "../services/wizards.js";
16
+ import { serverText } from "../texts.js";
16
17
  import { attempt } from "../tools/shared.js";
17
18
  import { readDoc, searchDocs } from "./docs.js";
18
19
  /** Where the account lives: the runtime's Manage-App, or the account a local install links. */
@@ -45,7 +46,7 @@ export function knowsTools(ctx) {
45
46
  description: "Search the engenty wizards user guide and plugin guide of this installed version. Gives the best sections with an excerpt and their engenty.ai/docs link. Use it before you explain how something works.",
46
47
  inputSchema: z.object({ query: z.string().min(2).max(200) }),
47
48
  execute: async ({ query }) => {
48
- ctx.doing(ctx.lang === "de" ? "Liest in der Anleitung …" : "Reading the guide …");
49
+ ctx.doing(serverText(ctx.lang, "doingReadGuide"));
49
50
  return { hits: searchDocs(query) };
50
51
  },
51
52
  }),
@@ -57,7 +58,7 @@ export function knowsTools(ctx) {
57
58
  from: z.number().int().min(0).optional(),
58
59
  }),
59
60
  execute: async ({ page, from }) => {
60
- ctx.doing(ctx.lang === "de" ? "Liest in der Anleitung …" : "Reading the guide …");
61
+ ctx.doing(serverText(ctx.lang, "doingReadGuide"));
61
62
  return readDoc(page, from ?? 0);
62
63
  },
63
64
  }),
@@ -66,7 +67,7 @@ export function knowsTools(ctx) {
66
67
  description: "The authoring guide the wizard editor and AI apps build with: how a wizard is written (steps, fields, flows, tools, outputs, model classes). Long: read it in parts with `from`.",
67
68
  inputSchema: z.object({ from: z.number().int().min(0).optional() }),
68
69
  execute: ({ from }) => attempt(async () => {
69
- ctx.doing(ctx.lang === "de" ? "Liest die Bauanleitung …" : "Reading the authoring guide …");
70
+ ctx.doing(serverText(ctx.lang, "doingAuthoringGuide"));
70
71
  const guide = authoringGuide(ctx.project?.mcpServers ?? [], ctx.project ? await listConnectors(ctx.project.id) : [], await pluginToolsOf(user.tenantId));
71
72
  const start = Math.min(from ?? 0, guide.length);
72
73
  return {
@@ -80,7 +81,7 @@ export function knowsTools(ctx) {
80
81
  description: "Search the gallery of wizard templates (engenty.ai/wizards and the plugins' starters): title, pitch, what it needs that this install has no model for, and its price per run in credits.",
81
82
  inputSchema: z.object({ query: z.string().max(200).optional() }),
82
83
  execute: ({ query }) => attempt(async () => {
83
- ctx.doing(ctx.lang === "de" ? "Sucht in der Galerie …" : "Searching the gallery …");
84
+ ctx.doing(serverText(ctx.lang, "doingGallery"));
84
85
  const hits = await searchMarketplace({
85
86
  q: query ?? "",
86
87
  lang: asLang(ctx.lang),
@@ -146,7 +147,7 @@ export function knowsTools(ctx) {
146
147
  description: "The plans teams can be on (Free, Pro, Team): price a month, monthly credits, limits, and the credit packages to buy on top. Live from the account service.",
147
148
  inputSchema: z.object({}),
148
149
  execute: () => attempt(async () => {
149
- ctx.doing(ctx.lang === "de" ? "Sieht die Pläne nach …" : "Looking up the plans …");
150
+ ctx.doing(serverText(ctx.lang, "doingPlans"));
150
151
  const plans = await publicPlans(accountBase());
151
152
  return { ...plans, billingPage: `${accountBase()}/billing` };
152
153
  }),
@@ -156,7 +157,7 @@ export function knowsTools(ctx) {
156
157
  description: "The person, their team, their role, the team's plan, the credit balance and credits that end on a date.",
157
158
  inputSchema: z.object({}),
158
159
  execute: () => attempt(async () => {
159
- ctx.doing(ctx.lang === "de" ? "Sieht das Konto nach …" : "Looking up the account …");
160
+ ctx.doing(serverText(ctx.lang, "doingAccount"));
160
161
  if (managed) {
161
162
  const info = await tenantInfo(user.tenantId, true);
162
163
  return {
@@ -194,7 +195,7 @@ export function knowsTools(ctx) {
194
195
  description: "Credits the team used per month (the last three), and the runs and wizards that cost the most.",
195
196
  inputSchema: z.object({}),
196
197
  execute: () => attempt(async () => {
197
- ctx.doing(ctx.lang === "de" ? "Sieht den Verbrauch nach …" : "Looking up the usage …");
198
+ ctx.doing(serverText(ctx.lang, "doingUsage"));
198
199
  if (!(managed || (await linkedAccount()))) {
199
200
  return { linked: false, note: NOT_LINKED };
200
201
  }
@@ -296,7 +297,7 @@ export function knowsTools(ctx) {
296
297
  description: "What thinks and makes for this install, per model class: an installed AI client, an own key, a local model, credits, or nothing (with the problem). Also which AI clients are installed and signed in.",
297
298
  inputSchema: z.object({}),
298
299
  execute: () => attempt(async () => {
299
- ctx.doing(ctx.lang === "de" ? "Sieht die Modelle nach …" : "Looking up the models …");
300
+ ctx.doing(serverText(ctx.lang, "doingModels"));
300
301
  return {
301
302
  classes: waysIn(await classWays(), "en"),
302
303
  clients: managed
@@ -315,7 +316,7 @@ export function knowsTools(ctx) {
315
316
  description: "Search the person's earlier chats with you (their messages and summaries) for words: what was asked, changed or decided before.",
316
317
  inputSchema: z.object({ query: z.string().min(3).max(200) }),
317
318
  execute: ({ query }) => attempt(async () => {
318
- ctx.doing(ctx.lang === "de" ? "Sucht in früheren Chats …" : "Searching earlier chats …");
319
+ ctx.doing(serverText(ctx.lang, "doingEarlierChats"));
319
320
  const hits = (await searchChats(user, query)).filter((h) => h.chatId !== ctx.chatId);
320
321
  return {
321
322
  hits,
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { and, asc, eq } from "drizzle-orm";
3
3
  import { z } from "zod";
4
4
  import { db, schema } from "../db/client.js";
@@ -275,7 +275,7 @@ export async function importConnector(userId, projectId, input) {
275
275
  await requireWritableProject(projectId);
276
276
  await requireAdmin();
277
277
  if (await load(projectId, input.id)) {
278
- throw new ServiceError("invalid", bilingual(`Den Connector „${input.id}“ gibt es in diesem Space schon.`, `The connector “${input.id}” already exists in this space.`));
278
+ throw new ServiceError("invalid", note("connectorAlreadyExistsSpace", { id: input.id }));
279
279
  }
280
280
  try {
281
281
  const { discover, surface, prepared } = await prepare(input);
@@ -352,7 +352,7 @@ export async function refreshConnector(projectId, id, accessToken) {
352
352
  await requireWritableProject(projectId);
353
353
  const record = await load(projectId, id);
354
354
  if (!record) {
355
- throw new ServiceError("not_found", bilingual("Diesen Connector gibt es nicht.", "This connector doesn’t exist."));
355
+ throw new ServiceError("not_found", note("connectorNotExist"));
356
356
  }
357
357
  try {
358
358
  const { surface } = record.registry_surface_slug
@@ -1,4 +1,5 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { isLang } from "@engenty-wizards/shared/definition";
2
+ import { note } from "@engenty-wizards/shared/run";
2
3
  import { eq } from "drizzle-orm";
3
4
  import { db, schema } from "../db/client.js";
4
5
  import { buildAuthorizationUrl, createOAuth2Pkce, exchangeAuthorizationCode, refreshAccessToken, } from "../engenty/connections-sdk/oauth2.js";
@@ -7,23 +8,21 @@ import { env } from "../env.js";
7
8
  import { NotedError } from "../model-errors.js";
8
9
  import { seal, unseal } from "../secrets/crypto.js";
9
10
  import { deleteSecret, getSecret, putSecret } from "../store/index.js";
11
+ import { serverText } from "../texts.js";
10
12
  import { connectorFetch, MAIL_CONNECTORS, resolveEnv, usable, viaAccount } from "./builtin.js";
11
13
  import { resolveConnector } from "./external.js";
12
14
  import { AccountConnectError, redeemTicket, refreshThroughAccount, startThroughAccount, } from "./via-account.js";
13
15
  /** The connectors a person can pick for each kind of connection. */
14
16
  const CONNECTORS = { mail: MAIL_CONNECTORS };
15
- const HINTS = {
16
- imap: {
17
- de: "Bei Gmail, iCloud, GMX und web.de brauchst du ein App-Passwort (in den Sicherheitseinstellungen deines Kontos), nicht dein normales Passwort.",
18
- en: "Gmail, iCloud, GMX and web.de need an app password (in your account's security settings), not your normal password.",
19
- },
20
- };
21
- /** The built-in connectors' fields in German; their definitions say them in English. */
22
- const GERMAN_FIELDS = {
23
- imap: {
24
- email: { label: "E-Mail-Adresse", placeholder: "name@firma.at" },
25
- password: { label: "Passwort oder App-Passwort" },
26
- host: { label: "IMAP-Server", placeholder: "leer lassen bei Gmail, GMX, iCloud …" },
17
+ const HINTS = { imap: "imapHint" };
18
+ /** The built-in connectors' fields in each language; their definitions say them in English. */
19
+ const FIELD_WORDS = {
20
+ de: {
21
+ imap: {
22
+ email: { label: "E-Mail-Adresse", placeholder: "name@firma.at" },
23
+ password: { label: "Passwort oder App-Passwort" },
24
+ host: { label: "IMAP-Server", placeholder: "leer lassen bei Gmail, GMX, iCloud …" },
25
+ },
27
26
  },
28
27
  };
29
28
  /**
@@ -67,10 +66,11 @@ export async function connectorOptions(connection, projectId, lang = "en") {
67
66
  id: c.id,
68
67
  name: c.name,
69
68
  auth: "api_key",
70
- fields: lang === "de"
71
- ? c.auth.apiKey.fields.map((f) => ({ ...f, ...GERMAN_FIELDS[c.id]?.[f.key] }))
72
- : c.auth.apiKey.fields,
73
- hint: HINTS[c.id]?.[lang],
69
+ fields: c.auth.apiKey.fields.map((f) => ({
70
+ ...f,
71
+ ...(lang === "en" ? undefined : FIELD_WORDS[lang]?.[c.id]?.[f.key]),
72
+ })),
73
+ hint: HINTS[c.id] && serverText(lang, HINTS[c.id]),
74
74
  }
75
75
  : { id: c.id, name: c.name, auth: "oauth2" });
76
76
  }
@@ -157,7 +157,7 @@ export async function startOAuth(scope, projectId, runId, connection, connectorI
157
157
  /** The language a sign-in was started in, a wizard's or a plugin's; null where it names none. */
158
158
  export function oauthStateLang(rawState) {
159
159
  const lang = unseal(rawState)?.lang;
160
- return lang === "de" || lang === "en" ? lang : null;
160
+ return isLang(lang) ? lang : null;
161
161
  }
162
162
  /** The run an OAuth callback belongs to; its tenant is looked up before the state is used. */
163
163
  export function oauthStateRun(rawState) {
@@ -214,7 +214,7 @@ export async function connectWithCredentials(scope, projectId, connection, conne
214
214
  for (const field of connector.auth.apiKey.fields) {
215
215
  const value = String(values[field.key] ?? "").trim();
216
216
  if (!value && field.required !== false) {
217
- throw new ConnectError(bilingual(`„${field.label}“ fehlt.`, `“${field.label}” is missing.`));
217
+ throw new ConnectError(note("fieldMissing", { label: field.label }));
218
218
  }
219
219
  if (value) {
220
220
  credentials[field.key] = value.slice(0, 500);
@@ -244,16 +244,16 @@ function connectFailure(err) {
244
244
  const host = e.imapHost ? ` (${e.imapHost})` : "";
245
245
  if (e.authenticationFailed) {
246
246
  const answer = e.responseText?.slice(0, 160);
247
- return bilingual(`Anmeldung abgelehnt${host}. Stimmen Adresse und (App-)Passwort? Gmail und Google Workspace brauchen ein App-Passwort und eingeschaltetes IMAP.${answer ? ` Antwort des Servers: ${answer}` : ""}`, `The sign-in was refused${host}. Are the address and (app) password right? Gmail and Google Workspace need an app password and IMAP turned on.${answer ? ` The server said: ${answer}` : ""}`);
247
+ return note("signInRefused", { host, answer: answer ? note("serverSaid", { answer }) : "" });
248
248
  }
249
249
  if (e.code === "ENOTFOUND") {
250
- return bilingual(`Den Mail-Server${host} gibt es nicht. Trag den IMAP-Server ein, bei Gmail und Google Workspace imap.gmail.com.`, `The mail server${host} doesn’t exist. Enter the IMAP server. For Gmail and Google Workspace, it’s imap.gmail.com.`);
250
+ return note("mailServerNotExist", { host });
251
251
  }
252
252
  if (e.code === "ETIMEDOUT" || e.code === "ETIMEOUT" || e.code === "ECONNREFUSED") {
253
- return bilingual(`Der Mail-Server${host} antwortet nicht. Prüf IMAP-Server und Port.`, `The mail server${host} isn’t responding. Check the IMAP server and port.`);
253
+ return note("mailServerNotResponding", { host });
254
254
  }
255
255
  const detail = String(e.message ?? err).slice(0, 200);
256
- return bilingual(`Verbinden hat nicht geklappt${host}: ${detail}`, `Connecting didn’t work${host}: ${detail}`);
256
+ return note("connectingNotWork", { host, detail });
257
257
  }
258
258
  export function disconnect(scope, connectionId) {
259
259
  return deleteSecret(scope, slot(connectionId));
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { accountToken } from "../auth/account.js";
3
3
  import { env } from "../env.js";
4
4
  import { NotedError } from "../model-errors.js";
@@ -26,7 +26,7 @@ export async function googleThroughAccount() {
26
26
  async function call(path, body) {
27
27
  const token = await accountToken();
28
28
  if (!token) {
29
- throw new AccountConnectError(bilingual("Verbinde zuerst dein engenty-Konto (Einstellungen → Konto). Dann kannst du Google hier verbinden.", "Connect your engenty account first (Settings → Account). Then you can connect Google here."));
29
+ throw new AccountConnectError(note("connectEngentyAccountFirst"));
30
30
  }
31
31
  const res = await fetch(`${base()}${path}`, {
32
32
  method: "POST",
@@ -35,15 +35,15 @@ async function call(path, body) {
35
35
  signal: AbortSignal.timeout(20_000),
36
36
  }).catch(() => null);
37
37
  if (!res) {
38
- throw new AccountConnectError(bilingual("Dein engenty-Konto ist gerade nicht erreichbar. Versuch es später noch einmal.", "Your engenty account can’t be reached right now. Try again later."));
38
+ throw new AccountConnectError(note("engentyAccountNotReached"));
39
39
  }
40
40
  const data = (await res.json().catch(() => ({})));
41
41
  if (!res.ok) {
42
42
  throw new AccountConnectError(data.code === "invalid_grant"
43
- ? bilingual("Google nimmt diese Verbindung nicht mehr an. Verbinde das Konto neu.", "Google no longer accepts this connection. Connect the account again.")
43
+ ? note("googleNoLongerAccepts")
44
44
  : data.code === "not_configured"
45
- ? bilingual("Google lässt sich über dein engenty-Konto gerade nicht verbinden.", "Google can’t be connected through your engenty account right now.")
46
- : bilingual(`Verbinden über dein engenty-Konto hat nicht geklappt (${data.code ?? res.status}).`, `Connecting through your engenty account didn’t work (${data.code ?? res.status}).`));
45
+ ? note("googleNotConnectedThrough")
46
+ : note("viaAccountFailed", { code: data.code ?? res.status }));
47
47
  }
48
48
  return data;
49
49
  }
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync } from "node:fs";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
- import { bilingual } from "@engenty-wizards/shared/run";
5
+ import { note } from "@engenty-wizards/shared/run";
6
6
  import { generateText, Output } from "ai";
7
7
  import PostalMime from "postal-mime";
8
8
  import { z } from "zod";
@@ -170,7 +170,7 @@ async function read(input, opts) {
170
170
  const vision = new VisionProvider(opts.charge, opts.signal, opts.call);
171
171
  if (mime.startsWith("image/")) {
172
172
  if (!vision.canConvert(mime)) {
173
- throw new UnreadableDocument(bilingual(`Bilder vom Typ ${mime} lassen sich nicht lesen (PNG, JPEG und WebP gehen).`, `Images of type ${mime} can’t be read (PNG, JPEG and WebP work).`));
173
+ throw new UnreadableDocument(note("imageTypeNotReadable", { mime }));
174
174
  }
175
175
  const result = await vision.convert(data, name, mime);
176
176
  return { markdown: result.markdown, pages: 1, via: "vision" };
@@ -214,7 +214,7 @@ async function read(input, opts) {
214
214
  if (mime === "application/json" || mime.startsWith("text/")) {
215
215
  return { markdown: text(), pages: null, via: "text" };
216
216
  }
217
- throw new UnreadableDocument(bilingual(`„${name}“ (${mime}) lässt sich nicht lesen.`, `“${name}” (${mime}) can’t be read.`));
217
+ throw new UnreadableDocument(note("fileNotReadable", { name, mime }));
218
218
  }
219
219
  // --- structured extraction -----------------------------------------------------
220
220
  /** The scanner's schemas say nothing about the currency; SaaS invoices come in several. */
@@ -1,5 +1,5 @@
1
- import { isDecidedField, isLocationValue, MAX_FILES, offeredSlot, shownFields, } from "@engenty-wizards/shared/definition";
2
- import { bilingual, saidText } from "@engenty-wizards/shared/run";
1
+ import { isDecidedField, isLang, isLocationValue, MAX_FILES, offeredSlot, shownFields, } from "@engenty-wizards/shared/definition";
2
+ import { note, saidText } from "@engenty-wizards/shared/run";
3
3
  /** The field errors in a language, for whoever reads them. */
4
4
  export function fieldErrorsIn(errors, lang) {
5
5
  return errors.map((e) => ({ field: e.field, message: saidText(e.message, lang) }));
@@ -120,7 +120,7 @@ function readAudio(raw) {
120
120
  if (typeof input.transcript === "string") {
121
121
  out.transcript = input.transcript.slice(0, 20_000);
122
122
  }
123
- if (input.lang === "de" || input.lang === "en") {
123
+ if (isLang(input.lang)) {
124
124
  out.lang = input.lang;
125
125
  }
126
126
  return out;
@@ -142,21 +142,21 @@ export function readPageInput(step, input, context = {}) {
142
142
  continue;
143
143
  }
144
144
  if (field.required && empty(v) && field.kind !== "toggle") {
145
- errors.push({ field: field.id, message: bilingual("Pflichtfeld", "Required") });
145
+ errors.push({ field: field.id, message: note("fieldRequired") });
146
146
  continue;
147
147
  }
148
148
  if (Array.isArray(v) && (field.kind === "image" || field.kind === "file")) {
149
149
  if (field.min && v.length < field.min) {
150
150
  errors.push({
151
151
  field: field.id,
152
- message: bilingual(`Wähl mindestens ${field.min} aus`, `Choose at least ${field.min}`),
152
+ message: note("chooseAtLeast", { min: field.min }),
153
153
  });
154
154
  continue;
155
155
  }
156
156
  if (field.max && v.length > field.max) {
157
157
  errors.push({
158
158
  field: field.id,
159
- message: bilingual(`Höchstens ${field.max} – entferne einige`, `At most ${field.max} – remove some`),
159
+ message: note("chooseAtMost", { max: field.max }),
160
160
  });
161
161
  continue;
162
162
  }
@@ -164,25 +164,25 @@ export function readPageInput(step, input, context = {}) {
164
164
  if (field.kind === "number" && Number.isNaN(v)) {
165
165
  errors.push({
166
166
  field: field.id,
167
- message: bilingual("Gib eine Zahl ein", "Enter a number"),
167
+ message: note("enterNumber"),
168
168
  });
169
169
  continue;
170
170
  }
171
171
  if (field.kind === "select" && !empty(v) && !field.options?.includes(String(v))) {
172
- errors.push({ field: field.id, message: bilingual("Ungültige Auswahl", "Invalid choice") });
172
+ errors.push({ field: field.id, message: note("invalidChoice") });
173
173
  continue;
174
174
  }
175
175
  if (field.kind === "slot" && !empty(v) && !field.options?.includes(String(v))) {
176
176
  errors.push({
177
177
  field: field.id,
178
- message: bilingual("Dieser Termin wird nicht angeboten", "This time isn’t offered"),
178
+ message: note("timeNotOffered"),
179
179
  });
180
180
  continue;
181
181
  }
182
182
  if (field.kind === "email" && !empty(v) && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(v))) {
183
183
  errors.push({
184
184
  field: field.id,
185
- message: bilingual("Keine gültige E-Mail-Adresse", "Not a valid email address"),
185
+ message: note("notValidEmailAddress"),
186
186
  });
187
187
  continue;
188
188
  }
@@ -193,7 +193,7 @@ export function readPageInput(step, input, context = {}) {
193
193
  catch {
194
194
  errors.push({
195
195
  field: field.id,
196
- message: bilingual("Keine gültige URL", "Not a valid URL"),
196
+ message: note("notValidUrl"),
197
197
  });
198
198
  continue;
199
199
  }
@@ -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 = {