wizards 0.2.16 → 0.2.18

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 (90) hide show
  1. package/apps/runtime/dist/agents/project-assistant.js +20 -2
  2. package/apps/runtime/dist/authoring/guide.js +6 -3
  3. package/apps/runtime/dist/credits/estimate.js +14 -8
  4. package/apps/runtime/dist/db/migrations/tenant/0008_run_event_note.sql +1 -0
  5. package/apps/runtime/dist/db/migrations/tenant/0009_knowledge.sql +104 -0
  6. package/apps/runtime/dist/db/migrations/tenant/meta/0008_snapshot.json +2019 -0
  7. package/apps/runtime/dist/db/migrations/tenant/meta/0009_snapshot.json +2837 -0
  8. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +14 -0
  9. package/apps/runtime/dist/db/schema.js +162 -8
  10. package/apps/runtime/dist/engine/events.js +14 -2
  11. package/apps/runtime/dist/engine/prepare.js +5 -2
  12. package/apps/runtime/dist/engine/requirements.js +108 -0
  13. package/apps/runtime/dist/engine/runner.js +2 -2
  14. package/apps/runtime/dist/engine/steps.js +21 -21
  15. package/apps/runtime/dist/engine/template.js +13 -10
  16. package/apps/runtime/dist/env.js +2 -0
  17. package/apps/runtime/dist/files/storage.js +15 -0
  18. package/apps/runtime/dist/models.js +38 -0
  19. package/apps/runtime/dist/plugins/loader.js +2 -0
  20. package/apps/runtime/dist/routes/studio.js +149 -8
  21. package/apps/runtime/dist/services/cloud.js +59 -1
  22. package/apps/runtime/dist/services/knowledge-model.js +558 -0
  23. package/apps/runtime/dist/services/knowledge.js +721 -0
  24. package/apps/runtime/dist/services/marketplace.js +2 -2
  25. package/apps/runtime/dist/services/project-files.js +114 -6
  26. package/apps/runtime/dist/services/project-index.js +632 -94
  27. package/apps/runtime/dist/services/relevance.js +90 -0
  28. package/apps/runtime/dist/services/space-categories.js +882 -0
  29. package/apps/runtime/dist/services/space-data.js +466 -38
  30. package/apps/runtime/dist/services/space-origin.js +164 -0
  31. package/apps/runtime/dist/services/spaces.js +155 -1
  32. package/apps/runtime/dist/tools/browser.js +12 -6
  33. package/apps/runtime/dist/tools/index.js +12 -6
  34. package/apps/runtime/dist/tools/mail.js +5 -2
  35. package/apps/runtime/dist/tools/pages.js +28 -23
  36. package/apps/runtime/dist/tools/project.js +132 -39
  37. package/apps/runtime/dist/tools/store.js +4 -4
  38. package/apps/runtime/dist/widgets/step.js +10 -4
  39. package/apps/web/dist/assets/{Account-BE_W101C.js → Account-Cp1cgqjV.js} +1 -1
  40. package/apps/web/dist/assets/{ChangelogDialog-DjKQJAos.js → ChangelogDialog-Chke1qUe.js} +1 -1
  41. package/apps/web/dist/assets/ChatPanel-DWrwewr6.js +1 -0
  42. package/apps/web/dist/assets/CreditsDialog-Ckema8Ss.js +1 -0
  43. package/apps/web/dist/assets/EditorPage-XX6pfHk5.js +2 -0
  44. package/apps/web/dist/assets/{FlowPreview-DK4RZqTW.js → FlowPreview-DS545n78.js} +1 -1
  45. package/apps/web/dist/assets/{Harness-DcI94f1U.js → Harness-H3yXP-2L.js} +2 -2
  46. package/apps/web/dist/assets/{HomePage-BBNFgryu.js → HomePage-Bds84ShK.js} +1 -1
  47. package/apps/web/dist/assets/{ImportWizard-BoYqovdl.js → ImportWizard-0_NkA8aS.js} +1 -1
  48. package/apps/web/dist/assets/LocalRuntime-DnABzTOe.js +1 -0
  49. package/apps/web/dist/assets/{NewWizardPage-tKV2S4eK.js → NewWizardPage-BqP75V9K.js} +2 -2
  50. package/apps/web/dist/assets/ProjectPage-DbOvwOdZ.js +5 -0
  51. package/apps/web/dist/assets/PublicRunner-CtZ7vxl_.js +1 -0
  52. package/apps/web/dist/assets/RunnerView-xV5bWclu.js +70 -0
  53. package/apps/web/dist/assets/SettingsPage-DKt55zFh.js +1 -0
  54. package/apps/web/dist/assets/{SetupPage-qxHvlf87.js → SetupPage-c6mMD9zN.js} +1 -1
  55. package/apps/web/dist/assets/SharePage-DTKIbMj-.js +1 -0
  56. package/apps/web/dist/assets/{api-t9_w1XNe.js → api-CVG-xDEn.js} +2 -2
  57. package/apps/web/dist/assets/changelog-CXXT0zsp.js +1 -0
  58. package/apps/web/dist/assets/{esm-D0YVMTdo.js → esm-DLM3jdbm.js} +1 -1
  59. package/apps/web/dist/assets/index-1gmaxDSk.js +10 -0
  60. package/apps/web/dist/assets/index-Bwyehu0k.css +1 -0
  61. package/apps/web/dist/assets/{meta-BJHYbMh7.js → meta-m947x6uG.js} +1 -1
  62. package/apps/web/dist/assets/{settings-menu-BqqUAvIY.js → settings-menu-DUY6__C3.js} +1 -1
  63. package/apps/web/dist/assets/ui-BPY2pIh0.js +1 -0
  64. package/apps/web/dist/index.html +4 -4
  65. package/apps/web/dist/mcp/flow.html +54 -54
  66. package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +164 -0
  67. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +10 -0
  68. package/node_modules/@engenty-wizards/shared/dist/definition.js +25 -0
  69. package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +199 -0
  70. package/node_modules/@engenty-wizards/shared/dist/knowledge.js +68 -0
  71. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +1 -16
  72. package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
  73. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +149 -0
  74. package/node_modules/@engenty-wizards/shared/dist/run.js +77 -1
  75. package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +20 -0
  76. package/node_modules/@engenty-wizards/shared/dist/space-data.js +2 -1
  77. package/package.json +1 -1
  78. package/apps/web/dist/assets/ChatPanel-BWrcDs4s.js +0 -1
  79. package/apps/web/dist/assets/CreditsDialog-DUjR5xkl.js +0 -1
  80. package/apps/web/dist/assets/EditorPage-CrOqikP2.js +0 -2
  81. package/apps/web/dist/assets/LocalRuntime-BD8zGN8w.js +0 -1
  82. package/apps/web/dist/assets/ProjectPage-voR0hvzy.js +0 -4
  83. package/apps/web/dist/assets/PublicRunner-CYZE_hSi.js +0 -1
  84. package/apps/web/dist/assets/RunnerView-D_C7hn81.js +0 -70
  85. package/apps/web/dist/assets/SettingsPage-DguoPF_3.js +0 -1
  86. package/apps/web/dist/assets/SharePage-Cf3i7KB-.js +0 -1
  87. package/apps/web/dist/assets/changelog-kz0DmHY4.js +0 -1
  88. package/apps/web/dist/assets/index-BPpKz65x.css +0 -1
  89. package/apps/web/dist/assets/index-D5w3fW1p.js +0 -10
  90. package/apps/web/dist/assets/ui-BHXO_e0e.js +0 -1
@@ -0,0 +1,164 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import { db, schema } from "../db/client.js";
3
+ import { UnreadableDocument } from "../documents/parse.js";
4
+ import { ServiceError } from "./errors.js";
5
+ import { filePaths, pageTree, tablePaths } from "./knowledge.js";
6
+ import { convertDocument, fillCategories, summarizeValues } from "./knowledge-model.js";
7
+ import { putOriginFile } from "./project-files.js";
8
+ import { queueIndex } from "./project-index.js";
9
+ import { putCategory, setItemCategories } from "./space-categories.js";
10
+ import { listOrigin, putOriginPage, putOriginTable, removeOrigin } from "./space-data.js";
11
+ /**
12
+ * `server.spaceData` of a plugin: Wissen it writes under keys of its own, with Kategorien. The
13
+ * plugin's id is the origin of everything it writes; a person who changes an item keeps it.
14
+ */
15
+ export function originData(origin) {
16
+ return {
17
+ async putCategories({ space, categories, proposed }) {
18
+ for (const c of categories) {
19
+ let tableId = null;
20
+ if (c.column) {
21
+ const table = await db.query.spaceTable.findFirst({
22
+ where: and(eq(schema.spaceTable.projectId, space), eq(schema.spaceTable.origin, origin), eq(schema.spaceTable.originKey, c.column.table)),
23
+ columns: { id: true },
24
+ });
25
+ if (!table) {
26
+ throw new ServiceError("invalid", `There is no table with the key "${c.column.table}".`);
27
+ }
28
+ tableId = table.id;
29
+ }
30
+ await putCategory(space, {
31
+ name: c.name,
32
+ type: c.type,
33
+ unit: c.unit,
34
+ multiple: c.multiple,
35
+ ordered: c.ordered,
36
+ hint: c.hint,
37
+ values: c.values,
38
+ tableId,
39
+ columnId: c.column?.column ?? null,
40
+ }, { origin, proposed });
41
+ }
42
+ },
43
+ async putPage(input) {
44
+ const written = await putOriginPage(origin, {
45
+ ...input,
46
+ categories: input.categories,
47
+ });
48
+ if (input.fill && written.state === "written") {
49
+ const changed = await fillCategories(input.space, { pageId: written.id }, input.markdown, input.title).catch((err) => {
50
+ console.error(`[plugin ${origin}] Kategorien`, err);
51
+ return false;
52
+ });
53
+ if (changed) {
54
+ queueIndex(`P:${written.id}`);
55
+ }
56
+ }
57
+ return { ...written, path: (await pageTree(input.space)).get(written.id)?.path ?? "" };
58
+ },
59
+ async putTable(input) {
60
+ const written = await putOriginTable(origin, {
61
+ ...input,
62
+ categories: input.categories,
63
+ });
64
+ return { ...written, path: (await tablePaths(input.space)).get(written.id) ?? "" };
65
+ },
66
+ async putFile(input) {
67
+ const id = await putOriginFile(origin, {
68
+ ...input,
69
+ categories: input.categories,
70
+ });
71
+ return { id, path: (await filePaths(input.space)).get(id) ?? "" };
72
+ },
73
+ async putDocument(input) {
74
+ const id = await putOriginFile(origin, { ...input, categories: undefined });
75
+ const file = await db.query.projectFile.findFirst({ where: eq(schema.projectFile.id, id) });
76
+ if (!file) {
77
+ throw new ServiceError("not_found", "The document went while it was read.");
78
+ }
79
+ const path = (await filePaths(input.space)).get(id) ?? "";
80
+ const kept = (await db.query.spacePage.findFirst({
81
+ where: and(eq(schema.spacePage.fileId, id), eq(schema.spacePage.kept, true)),
82
+ columns: { id: true },
83
+ })) ??
84
+ (await db.query.spaceTable.findFirst({
85
+ where: and(eq(schema.spaceTable.fileId, id), eq(schema.spaceTable.kept, true)),
86
+ columns: { id: true },
87
+ }));
88
+ if (kept) {
89
+ return { id, path, state: "kept", items: [], review: null };
90
+ }
91
+ let converted;
92
+ try {
93
+ converted = await convertDocument(file, input.data);
94
+ }
95
+ catch (err) {
96
+ if (err instanceof UnreadableDocument) {
97
+ queueIndex(`f:${id}`);
98
+ return { id, path, state: "written", items: [], review: err.message };
99
+ }
100
+ throw err;
101
+ }
102
+ await db
103
+ .update(schema.projectFile)
104
+ .set({
105
+ textHash: converted.textHash,
106
+ pages: converted.pages,
107
+ chars: converted.chars,
108
+ updatedAt: new Date(),
109
+ })
110
+ .where(eq(schema.projectFile.id, id));
111
+ const [tree, tables] = await Promise.all([pageTree(input.space), tablePaths(input.space)]);
112
+ const items = await Promise.all(converted.items.map(async (key) => {
113
+ const itemId = key.slice(2);
114
+ if (key[0] === "p") {
115
+ const node = tree.get(itemId);
116
+ return {
117
+ kind: "page",
118
+ id: itemId,
119
+ path: node?.path ?? "",
120
+ title: node?.title ?? "",
121
+ };
122
+ }
123
+ const table = await db.query.spaceTable.findFirst({
124
+ where: eq(schema.spaceTable.id, itemId),
125
+ columns: { title: true },
126
+ });
127
+ return {
128
+ kind: "table",
129
+ id: itemId,
130
+ path: tables.get(itemId) ?? "",
131
+ title: table?.title ?? "",
132
+ };
133
+ }));
134
+ const first = items[0];
135
+ const ref = first
136
+ ? first.kind === "page"
137
+ ? { pageId: first.id }
138
+ : { tableId: first.id }
139
+ : { fileId: id };
140
+ if (input.categories) {
141
+ await setItemCategories(input.space, ref, input.categories, "origin");
142
+ }
143
+ if (input.fill) {
144
+ await fillCategories(input.space, ref, converted.text, input.name).catch((err) => {
145
+ console.error(`[plugin ${origin}] Kategorien`, err);
146
+ return false;
147
+ });
148
+ }
149
+ if (input.categories || input.fill) {
150
+ queueIndex(first ? `${first.kind === "page" ? "P" : "t"}:${first.id}` : `f:${id}`);
151
+ }
152
+ const review = first?.kind === "page"
153
+ ? ((await db.query.spacePage.findFirst({
154
+ where: eq(schema.spacePage.id, first.id),
155
+ columns: { review: true },
156
+ }))?.review ?? null)
157
+ : null;
158
+ return { id, path, state: "written", items, review };
159
+ },
160
+ list: (space) => listOrigin(origin, space),
161
+ remove: (space, key) => removeOrigin(origin, space, key),
162
+ summarize: (space, category, value) => summarizeValues(space, category, value),
163
+ };
164
+ }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { tableColumnsSchema } from "@engenty-wizards/shared/engenty/data-tables";
3
+ import { CATEGORY_TYPES } from "@engenty-wizards/shared/knowledge";
3
4
  import { PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
4
5
  import { SPACE_DATA_LIMITS } from "@engenty-wizards/shared/space-data";
5
6
  import { WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
@@ -17,6 +18,7 @@ import { emitDraftChanged } from "./draft-events.js";
17
18
  import { notFound, ServiceError } from "./errors.js";
18
19
  import { keepFiles, replaceFiles } from "./files.js";
19
20
  import { addProjectFile, projectFiles, removeProjectFile } from "./project-files.js";
21
+ import { reindexSpace } from "./project-index.js";
20
22
  import { brandColorSchema, projectFactSchema, projectLimit, removeProject, } from "./projects.js";
21
23
  import { deleteWizard, draftIssues, parseDraft, rotateShareLink, shareUrl } from "./wizards.js";
22
24
  /**
@@ -411,11 +413,15 @@ export async function removeSyncedSpace(rawSpaceId) {
411
413
  // --- the space's own tables and pages --------------------------------------------
412
414
  // What the admin made under Space → Daten on the install, not what a wizard keeps: those stay
413
415
  // where their runs write them. Sent as a whole, it replaces what came before.
416
+ // Since Wissen: sub-pages, where an item came from, Kategorien and what has them. An install of
417
+ // before sends none of it.
414
418
  export const syncDataSchema = z.object({
415
419
  tables: z
416
420
  .array(z.object({
417
421
  id: syncedId,
418
422
  title: z.string().min(1).max(120),
423
+ format: z.enum(["faq"]).nullish(),
424
+ originLabel: z.string().max(120).nullish(),
419
425
  columns: tableColumnsSchema,
420
426
  rows: z
421
427
  .array(z.object({ id: syncedId, cells: z.record(z.string(), z.unknown()) }))
@@ -427,8 +433,48 @@ export const syncDataSchema = z.object({
427
433
  id: syncedId,
428
434
  title: z.string().min(1).max(200),
429
435
  markdown: z.string().max(SPACE_DATA_LIMITS.pageChars),
436
+ parentId: syncedId.nullish(),
437
+ position: z.number().int().min(0).optional(),
438
+ originLabel: z.string().max(120).nullish(),
430
439
  }))
431
440
  .max(SPACE_DATA_LIMITS.pages),
441
+ categories: z
442
+ .array(z.object({
443
+ id: syncedId,
444
+ name: z.string().min(1).max(80),
445
+ type: z.enum(CATEGORY_TYPES),
446
+ unit: z.string().max(20).nullish(),
447
+ multiple: z.boolean(),
448
+ ordered: z.boolean(),
449
+ hint: z.string().max(120).nullish(),
450
+ tableId: syncedId.nullish(),
451
+ columnId: z.string().max(64).nullish(),
452
+ position: z.number().int().min(0),
453
+ values: z
454
+ .array(z.object({
455
+ id: syncedId,
456
+ value: z.string().min(1).max(200),
457
+ position: z.number().int().min(0),
458
+ summary: z.string().max(20_000).nullish(),
459
+ }))
460
+ .max(2000),
461
+ }))
462
+ .max(60)
463
+ .optional(),
464
+ /** `p:<page>`, `t:<table>`, `r:<row>`: files stay with the install. */
465
+ assignments: z
466
+ .array(z.object({
467
+ categoryId: syncedId,
468
+ item: z.string().regex(/^[ptr]:[A-Za-z0-9_-]{8,40}$/),
469
+ valueId: syncedId.nullish(),
470
+ text: z.string().max(300).nullish(),
471
+ num: z.number().nullish(),
472
+ at: z.number().int().nullish(),
473
+ bool: z.boolean().nullish(),
474
+ by: z.enum(["person", "origin", "model"]),
475
+ }))
476
+ .max(200_000)
477
+ .optional(),
432
478
  });
433
479
  /**
434
480
  * Takes the space's own tables and pages a local install sent: those it no longer has go, the
@@ -491,12 +537,120 @@ export async function syncSpaceData(rawSpaceId, input) {
491
537
  }
492
538
  }
493
539
  for (const page of input.pages) {
494
- const values = { title: page.title, markdown: page.markdown, updatedAt: now };
540
+ const values = {
541
+ title: page.title,
542
+ markdown: page.markdown,
543
+ position: page.position ?? 0,
544
+ originLabel: page.originLabel ?? null,
545
+ // Its place comes after every page is there.
546
+ parentId: null,
547
+ slug: null,
548
+ updatedAt: now,
549
+ };
495
550
  await db
496
551
  .insert(schema.spacePage)
497
552
  .values({ id: page.id, tenantId: currentTenant(), projectId: spaceId, ...values })
498
553
  .onConflictDoUpdate({ target: schema.spacePage.id, set: values });
499
554
  }
555
+ const pagesThere = new Set(pageIds);
556
+ for (const page of input.pages) {
557
+ if (page.parentId && pagesThere.has(page.parentId) && page.parentId !== page.id) {
558
+ await db
559
+ .update(schema.spacePage)
560
+ .set({ parentId: page.parentId })
561
+ .where(eq(schema.spacePage.id, page.id));
562
+ }
563
+ }
564
+ for (const table of input.tables) {
565
+ await db
566
+ .update(schema.spaceTable)
567
+ .set({ format: table.format ?? null, originLabel: table.originLabel ?? null, slug: null })
568
+ .where(eq(schema.spaceTable.id, table.id));
569
+ }
570
+ if (input.categories) {
571
+ await syncCategories(spaceId, input.categories, input.assignments ?? []);
572
+ }
500
573
  });
574
+ await reindexSpace(spaceId);
501
575
  return { tables: input.tables.length, pages: input.pages.length };
502
576
  }
577
+ /** The Kategorien a local install sent, their values, and what has them: they replace these here. */
578
+ async function syncCategories(spaceId, categories, assignments) {
579
+ const ids = categories.map((c) => c.id);
580
+ const taken = ids.length
581
+ ? await db.query.spaceCategory.findMany({
582
+ where: inArray(schema.spaceCategory.id, ids),
583
+ columns: { id: true, projectId: true },
584
+ })
585
+ : [];
586
+ if (taken.some((c) => c.projectId !== spaceId)) {
587
+ throw new ServiceError("refused", "Eine Kennung ist hier schon vergeben.", {
588
+ reason: "id_taken",
589
+ });
590
+ }
591
+ await db
592
+ .delete(schema.spaceCategory)
593
+ .where(and(eq(schema.spaceCategory.projectId, spaceId), ids.length ? notInArray(schema.spaceCategory.id, ids) : undefined));
594
+ await db.delete(schema.spaceItemCategory).where(eq(schema.spaceItemCategory.projectId, spaceId));
595
+ const tables = new Set((await db.query.spaceTable.findMany({
596
+ where: eq(schema.spaceTable.projectId, spaceId),
597
+ columns: { id: true },
598
+ })).map((t) => t.id));
599
+ const values = new Set();
600
+ for (const c of categories) {
601
+ const row = {
602
+ name: c.name,
603
+ type: c.type,
604
+ unit: c.unit ?? null,
605
+ multiple: c.multiple,
606
+ ordered: c.ordered,
607
+ hint: c.hint ?? null,
608
+ proposed: false,
609
+ tableId: c.tableId && tables.has(c.tableId) ? c.tableId : null,
610
+ columnId: c.tableId && tables.has(c.tableId) ? (c.columnId ?? null) : null,
611
+ position: c.position,
612
+ updatedAt: new Date(),
613
+ };
614
+ await db
615
+ .insert(schema.spaceCategory)
616
+ .values({ id: c.id, tenantId: currentTenant(), projectId: spaceId, ...row })
617
+ .onConflictDoUpdate({ target: schema.spaceCategory.id, set: row });
618
+ const valueIds = c.values.map((v) => v.id);
619
+ await db
620
+ .delete(schema.spaceCategoryValue)
621
+ .where(and(eq(schema.spaceCategoryValue.categoryId, c.id), valueIds.length ? notInArray(schema.spaceCategoryValue.id, valueIds) : undefined));
622
+ for (const v of c.values) {
623
+ const set = {
624
+ value: v.value,
625
+ position: v.position,
626
+ summary: v.summary ?? null,
627
+ summaryAt: v.summary ? new Date() : null,
628
+ };
629
+ await db
630
+ .insert(schema.spaceCategoryValue)
631
+ .values({ id: v.id, categoryId: c.id, ...set })
632
+ .onConflictDoUpdate({ target: schema.spaceCategoryValue.id, set });
633
+ values.add(v.id);
634
+ }
635
+ }
636
+ const known = new Set(ids);
637
+ const rows = assignments.filter((a) => known.has(a.categoryId) && (!a.valueId || values.has(a.valueId)));
638
+ for (let i = 0; i < rows.length; i += 200) {
639
+ await db.insert(schema.spaceItemCategory).values(rows.slice(i, i + 200).map((a) => {
640
+ const id = a.item.slice(2);
641
+ return {
642
+ projectId: spaceId,
643
+ categoryId: a.categoryId,
644
+ pageId: a.item[0] === "p" ? id : null,
645
+ tableId: a.item[0] === "t" ? id : null,
646
+ rowId: a.item[0] === "r" ? id : null,
647
+ valueId: a.valueId ?? null,
648
+ text: a.text ?? null,
649
+ num: a.num ?? null,
650
+ at: a.at ?? null,
651
+ bool: a.bool ?? null,
652
+ by: a.by,
653
+ };
654
+ }));
655
+ }
656
+ }
@@ -141,7 +141,7 @@ export function browserTools(ctx, assets, files) {
141
141
  inputSchema: z.object({ url: z.string() }),
142
142
  execute: ({ url }) => inTurn(async () => {
143
143
  const safe = await assertPublicUrl(url);
144
- await ctx.emit("tool", `Öffnet ${safe.hostname}`);
144
+ await ctx.emit("tool", { code: "opens", params: { name: safe.hostname } });
145
145
  const p = await page();
146
146
  await p.goto(safe.toString(), { waitUntil: "domcontentloaded", timeout: 30_000 });
147
147
  await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => undefined);
@@ -154,7 +154,7 @@ export function browserTools(ctx, assets, files) {
154
154
  inputSchema: z.object({ ref: z.number() }),
155
155
  execute: ({ ref: n }) => inTurn(async () => {
156
156
  const p = await page();
157
- await ctx.emit("tool", "Klickt im Browser");
157
+ await ctx.emit("tool", { code: "clicks" });
158
158
  const popup = p
159
159
  .context()
160
160
  .waitForEvent("page", { timeout: 3000 })
@@ -176,7 +176,7 @@ export function browserTools(ctx, assets, files) {
176
176
  inputSchema: z.object({ ref: z.number(), text: z.string(), submit: z.boolean().optional() }),
177
177
  execute: ({ ref: n, text, submit }) => inTurn(async () => {
178
178
  const p = await page();
179
- await ctx.emit("tool", "Tippt im Browser");
179
+ await ctx.emit("tool", { code: "types" });
180
180
  const el = p.locator(ref(n)).first();
181
181
  await el.fill(text, { timeout: 10_000 });
182
182
  if (submit) {
@@ -192,7 +192,7 @@ export function browserTools(ctx, assets, files) {
192
192
  inputSchema: z.object({ save: z.boolean().optional(), name: z.string().optional() }),
193
193
  execute: ({ save, name }) => inTurn(async () => {
194
194
  const p = await page();
195
- await ctx.emit("tool", "Sieht sich die Seite an");
195
+ await ctx.emit("tool", { code: "looksAtPage" });
196
196
  const png = await p.screenshot({ type: "png" });
197
197
  let saved;
198
198
  if (save) {
@@ -235,7 +235,10 @@ export function browserTools(ctx, assets, files) {
235
235
  };
236
236
  }
237
237
  }
238
- await ctx.emit("tool", `Wartet auf deine Anmeldung bei ${new URL(origin).hostname}`);
238
+ await ctx.emit("tool", {
239
+ code: "waitsForSignIn",
240
+ params: { name: new URL(origin).hostname },
241
+ });
239
242
  const answer = await ctx.ask({
240
243
  kind: "login",
241
244
  reason: input.reason,
@@ -321,7 +324,10 @@ export function browserTools(ctx, assets, files) {
321
324
  }),
322
325
  execute: ({ ref: n, url, path, source }) => inTurn(async () => {
323
326
  const p = await page();
324
- await ctx.emit("tool", `Lädt ${path.split("/").pop()} herunter`);
327
+ await ctx.emit("tool", {
328
+ code: "downloads",
329
+ params: { name: path.split("/").pop() ?? "" },
330
+ });
325
331
  let data;
326
332
  let mime;
327
333
  if (url) {
@@ -49,7 +49,7 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
49
49
  inputSchema: z.object({ url: z.string().describe("Absolute http(s) URL") }),
50
50
  execute: async ({ url }) => {
51
51
  const safe = await assertPublicUrl(url);
52
- await ctx.emit("tool", `Liest ${safe.hostname}`);
52
+ await ctx.emit("tool", { code: "reads", params: { name: safe.hostname } });
53
53
  const res = await safeFetch(safe.toString(), {
54
54
  signal: AbortSignal.any([ctx.signal, AbortSignal.timeout(20_000)]),
55
55
  headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
@@ -115,7 +115,7 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
115
115
  description: shellToolDescription(sandbox),
116
116
  inputSchema: z.object({ command: z.string(), timeoutSeconds: z.number().optional() }),
117
117
  execute: async ({ command, timeoutSeconds }) => {
118
- await ctx.emit("tool", `Führt aus: ${command.slice(0, 80)}`);
118
+ await ctx.emit("tool", { code: "runs", params: { command: command.slice(0, 80) } });
119
119
  return report(await ctx.resources
120
120
  .sandboxHandle()
121
121
  .exec(command, { timeoutMs: timeoutMs(timeoutSeconds) }));
@@ -131,7 +131,7 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
131
131
  timeoutSeconds: z.number().optional(),
132
132
  }),
133
133
  execute: async ({ code, packages, timeoutSeconds }) => {
134
- await ctx.emit("tool", "Führt Python aus");
134
+ await ctx.emit("tool", { code: "python" });
135
135
  return report(await ctx.resources.sandboxHandle().runPython(code, {
136
136
  packages,
137
137
  timeoutMs: timeoutMs(timeoutSeconds),
@@ -174,7 +174,7 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
174
174
  aspectRatio: z.enum(["1:1", "16:9", "9:16", "4:5", "3:2", "2:3"]).optional(),
175
175
  }),
176
176
  execute: async ({ prompt, aspectRatio }) => {
177
- await ctx.emit("tool", "Zeichnet ein Bild");
177
+ await ctx.emit("tool", { code: "drawing" });
178
178
  const media = await generateImageMedia({
179
179
  call: ctx.call,
180
180
  prompt,
@@ -225,10 +225,16 @@ export async function buildStepTools(step, ctx, resolved, uploads) {
225
225
  });
226
226
  try {
227
227
  Object.assign(tools, await mcp.listTools());
228
- await ctx.emit("tool", `Verbunden mit ${servers.map((s) => s.name).join(", ")}`);
228
+ await ctx.emit("tool", {
229
+ code: "mcpConnected",
230
+ params: { names: servers.map((s) => s.name).join(", ") },
231
+ });
229
232
  }
230
233
  catch (err) {
231
- await ctx.emit("info", `MCP-Server nicht erreichbar: ${err.message}`);
234
+ await ctx.emit("info", {
235
+ code: "mcpUnreachable",
236
+ params: { detail: err.message },
237
+ });
232
238
  }
233
239
  }
234
240
  return {
@@ -29,7 +29,7 @@ export function mailTools(step, ctx, files) {
29
29
  inputSchema: mailSearchInput,
30
30
  execute: (input) => attempt(async () => {
31
31
  const { connector, ctx: actionCtx, label } = await open();
32
- await ctx.emit("tool", `Durchsucht ${label}`);
32
+ await ctx.emit("tool", { code: "searchesMail", params: { label } });
33
33
  const { mails } = await mailAction(connector, "search", input, actionCtx);
34
34
  return {
35
35
  mailbox: label,
@@ -64,7 +64,10 @@ export function mailTools(step, ctx, files) {
64
64
  }),
65
65
  execute: ({ items }) => attempt(async () => {
66
66
  const { connector, ctx: actionCtx, label } = await open();
67
- await ctx.emit("tool", `Speichert ${items.length} Belege aus ${label}`);
67
+ await ctx.emit("tool", {
68
+ code: "savesReceipts",
69
+ params: { count: items.length, label },
70
+ });
68
71
  const saved = [];
69
72
  for (const item of items) {
70
73
  try {
@@ -1,11 +1,12 @@
1
1
  import { createTool } from "@mastra/core/tools";
2
2
  import { z } from "zod";
3
3
  import { runPages, writeRunPage } from "../services/space-data.js";
4
+ import { pageReadInput, readKnowledgePage } from "./project.js";
4
5
  import { attempt, clip } from "./shared.js";
5
6
  /**
6
- * Pages of the space for a step that lists "pages": Markdown its wizard keeps for every run —
7
- * notes, a log, a summary that grows — and the space's own pages to read. They stand in the
8
- * studio under Space → Daten.
7
+ * Pages for a step that lists "pages": Markdown its wizard keeps for every run — notes, a log, a
8
+ * summary that grows — shown in the studio under Space → Daten. Its page_read also reads the
9
+ * pages of Wissen, by path.
9
10
  */
10
11
  export function pageTools(ctx) {
11
12
  const projectId = ctx.project.id;
@@ -13,29 +14,33 @@ export function pageTools(ctx) {
13
14
  return {
14
15
  page_read: createTool({
15
16
  id: "page_read",
16
- description: "Read a page of the space: one this wizard wrote, or one of the space's own. Without a title it lists the pages there are.",
17
- inputSchema: z.object({ title: z.string().optional() }),
18
- execute: ({ title }) => attempt(async () => {
19
- const pages = await runPages(projectId, wizardId);
20
- const of = (p) => (p.wizardId ? "wizard" : "space");
21
- if (!title?.trim()) {
22
- return {
23
- pages: pages.map((p) => ({ title: p.title, of: of(p), chars: p.markdown.length })),
24
- };
25
- }
26
- const page = pages.find((p) => p.title.toLowerCase() === title.trim().toLowerCase());
27
- if (!page) {
28
- return {
29
- error: `No page "${title}".`,
30
- pages: pages.map((p) => p.title),
31
- };
32
- }
33
- return { title: page.title, of: of(page), markdown: clip(page.markdown, 40_000) };
17
+ description: "Read a page: one this wizard wrote, by its title, or a page of Wissen, by its path (pages/…). Without either it lists the wizard's pages.",
18
+ inputSchema: z.object({
19
+ title: z.string().optional().describe("A page this wizard wrote"),
20
+ path: pageReadInput.path.optional(),
21
+ section: pageReadInput.section,
22
+ from: pageReadInput.from,
34
23
  }),
24
+ execute: ({ title, path, section, from }) => {
25
+ if (path?.trim()) {
26
+ return readKnowledgePage(ctx, { path, section, from });
27
+ }
28
+ return attempt(async () => {
29
+ const pages = await runPages(projectId, wizardId);
30
+ if (!title?.trim()) {
31
+ return { pages: pages.map((p) => ({ title: p.title, chars: p.markdown.length })) };
32
+ }
33
+ const page = pages.find((p) => p.title.toLowerCase() === title.trim().toLowerCase());
34
+ if (!page) {
35
+ return { error: `No page "${title}".`, pages: pages.map((p) => p.title) };
36
+ }
37
+ return { title: page.title, markdown: clip(page.markdown, 40_000) };
38
+ });
39
+ },
35
40
  }),
36
41
  page_write: createTool({
37
42
  id: "page_write",
38
- description: "Write a page of this wizard in Markdown, kept for every later run and shown to the admin in the studio. A page with the same title is replaced — or added to with append: true (a log, a list that grows). The space's own pages are read-only.",
43
+ description: "Write a page of this wizard in Markdown, kept for every later run and shown to the admin in the studio. A page with the same title is replaced — or added to with append: true (a log, a list that grows). Pages of Wissen are read-only.",
39
44
  inputSchema: z.object({
40
45
  title: z.string().min(1).max(200),
41
46
  markdown: z.string().max(200_000),
@@ -43,7 +48,7 @@ export function pageTools(ctx) {
43
48
  }),
44
49
  execute: (input) => attempt(async () => {
45
50
  const page = await writeRunPage(projectId, wizardId, input);
46
- await ctx.emit("tool", `Schreibt die Seite „${page.title}“`);
51
+ await ctx.emit("tool", { code: "writesPage", params: { title: page.title } });
47
52
  return page;
48
53
  }),
49
54
  }),