wizards 0.2.26 → 0.2.28

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/project-assistant.js +10 -1
  2. package/apps/runtime/dist/authoring/guide.js +7 -3
  3. package/apps/runtime/dist/connectors/plugin-connections.js +248 -0
  4. package/apps/runtime/dist/db/migrations/tenant/0010_plugin_connection.sql +15 -0
  5. package/apps/runtime/dist/db/migrations/tenant/meta/0010_snapshot.json +2942 -0
  6. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +7 -0
  7. package/apps/runtime/dist/db/schema.js +21 -0
  8. package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +25 -3
  9. package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +29 -1
  10. package/apps/runtime/dist/engine/input.js +10 -1
  11. package/apps/runtime/dist/mcp/tools.js +5 -2
  12. package/apps/runtime/dist/plugins/loader.js +22 -1
  13. package/apps/runtime/dist/plugins/registry.js +10 -0
  14. package/apps/runtime/dist/routes/marketplace.js +2 -1
  15. package/apps/runtime/dist/routes/runs.js +11 -3
  16. package/apps/runtime/dist/routes/studio.js +3 -1
  17. package/apps/runtime/dist/services/marketplace.js +167 -5
  18. package/apps/runtime/dist/services/runs.js +43 -15
  19. package/apps/runtime/dist/services/wizards.js +4 -2
  20. package/apps/runtime/dist/tools/plugin.js +29 -1
  21. package/apps/web/dist/assets/Account-CVMSZTCv.js +1 -0
  22. package/apps/web/dist/assets/{ChangelogDialog-CdtQWq0m.js → ChangelogDialog-3N1CgtWP.js} +1 -1
  23. package/apps/web/dist/assets/CreditsDialog-BW-RETl-.js +1 -0
  24. package/apps/web/dist/assets/EditorPage-DnuHM3nW.js +3 -0
  25. package/apps/web/dist/assets/{FlowPreview-IZcNrBJi.js → FlowPreview-Ds6ul7-N.js} +1 -1
  26. package/apps/web/dist/assets/{Harness-Cj-k02TA.js → Harness-D-QoXcSe.js} +2 -2
  27. package/apps/web/dist/assets/HomePage-Ch0HlwVX.js +1 -0
  28. package/apps/web/dist/assets/ImportWizard-DVcYcB6l.js +1 -0
  29. package/apps/web/dist/assets/LocalRuntime-D7bzT40S.js +1 -0
  30. package/apps/web/dist/assets/NewWizardPage-DvA6AX7D.js +2 -0
  31. package/apps/web/dist/assets/ProjectPage-BWAISSfX.js +2 -0
  32. package/apps/web/dist/assets/PublicRunner-D2bd95TR.js +1 -0
  33. package/apps/web/dist/assets/RunDrawer-DkOCgXAR.js +4 -0
  34. package/apps/web/dist/assets/RunnerView-DF5uW_95.js +2 -0
  35. package/apps/web/dist/assets/SettingsPage-CMTGFXit.js +1 -0
  36. package/apps/web/dist/assets/SetupPage-BaT-VqIc.js +1 -0
  37. package/apps/web/dist/assets/{SharePage-70niRKaO.js → SharePage-COKOsuvD.js} +1 -1
  38. package/apps/web/dist/assets/arrow-right-hy38IBRU.js +1 -0
  39. package/apps/web/dist/assets/changelog-C8wFW63z.js +1 -0
  40. package/apps/web/dist/assets/circle-alert-BG9P3ewL.js +1 -0
  41. package/apps/web/dist/assets/definition-BiUSuZN5.js +1 -0
  42. package/apps/web/dist/assets/esm-0rdZWuyu.js +7 -0
  43. package/apps/web/dist/assets/external-link-BwCtn1nW.js +1 -0
  44. package/apps/web/dist/assets/image-DG4q2P1H.js +1 -0
  45. package/apps/web/dist/assets/index-BOp2fz-2.css +1 -0
  46. package/apps/web/dist/assets/index-DHM9S48l.js +78 -0
  47. package/apps/web/dist/assets/lock-DH_BntP_.js +1 -0
  48. package/apps/web/dist/assets/meta-DI0ahJ_z.js +1 -0
  49. package/apps/web/dist/assets/pen-line-B1dsy3Sr.js +1 -0
  50. package/apps/web/dist/assets/refresh-cw-B9M1g3SH.js +1 -0
  51. package/apps/web/dist/assets/settings-menu-DOjgaq6J.js +1 -0
  52. package/apps/web/dist/assets/star-axQV_3EC.js +1 -0
  53. package/apps/web/dist/assets/surface-BE-3Z1DP.js +56 -0
  54. package/apps/web/dist/assets/trash-3S1_XpVa.js +1 -0
  55. package/apps/web/dist/assets/ui-BZ01zr3i.js +9 -0
  56. package/apps/web/dist/assets/upload-U0i8fAW3.js +1 -0
  57. package/apps/web/dist/assets/video-Db4o719f.js +1 -0
  58. package/apps/web/dist/assets/volume-2-6f7qN_LM.js +1 -0
  59. package/apps/web/dist/assets/wand-sparkles-MelvBtiK.js +1 -0
  60. package/apps/web/dist/index.html +4 -5
  61. package/apps/web/dist/mcp/flow.html +57 -57
  62. package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +85 -0
  63. package/node_modules/@engenty-wizards/plugin-sdk/dist/studio.d.ts +23 -0
  64. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +28 -1
  65. package/node_modules/@engenty-wizards/shared/dist/definition.js +85 -5
  66. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +4 -1
  67. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +43 -1
  68. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +27 -0
  69. package/package.json +1 -1
  70. package/apps/web/dist/assets/Account-Cnksw5Ea.js +0 -1
  71. package/apps/web/dist/assets/CreditsDialog-Ti43boCN.js +0 -1
  72. package/apps/web/dist/assets/EditorPage-N7EzlRSj.js +0 -3
  73. package/apps/web/dist/assets/HomePage-bjdKkt84.js +0 -1
  74. package/apps/web/dist/assets/ImportWizard-DagFXYPM.js +0 -1
  75. package/apps/web/dist/assets/LocalRuntime-Boi_zqQJ.js +0 -1
  76. package/apps/web/dist/assets/NewWizardPage-DO5J8OpZ.js +0 -2
  77. package/apps/web/dist/assets/ProjectPage-DdG9wYsd.js +0 -2
  78. package/apps/web/dist/assets/PublicRunner-B4ts9rT6.js +0 -1
  79. package/apps/web/dist/assets/RunDrawer-LnVfCiYO.js +0 -4
  80. package/apps/web/dist/assets/RunnerView-DwffK7AA.js +0 -70
  81. package/apps/web/dist/assets/SettingsPage-Cyx_jTlp.js +0 -1
  82. package/apps/web/dist/assets/SetupPage-CBDD4rpC.js +0 -1
  83. package/apps/web/dist/assets/api-C4M6cbpW.js +0 -10
  84. package/apps/web/dist/assets/arrow-right-6FyN3e7D.js +0 -1
  85. package/apps/web/dist/assets/changelog-COyL1B5X.js +0 -1
  86. package/apps/web/dist/assets/check-CUpby_J9.js +0 -1
  87. package/apps/web/dist/assets/chevron-down-DymW-2BP.js +0 -1
  88. package/apps/web/dist/assets/circle-alert-D2SCwFT-.js +0 -1
  89. package/apps/web/dist/assets/createLucideIcon-q3MZMmnf.js +0 -1
  90. package/apps/web/dist/assets/definition-DoCLtZZL.js +0 -55
  91. package/apps/web/dist/assets/esm-CfW8Rmtq.js +0 -7
  92. package/apps/web/dist/assets/external-link-MAL7vJYj.js +0 -1
  93. package/apps/web/dist/assets/image-CC_2mJd6.js +0 -1
  94. package/apps/web/dist/assets/index-CJ0YJ5HC.css +0 -1
  95. package/apps/web/dist/assets/index-DMKXnSf-.js +0 -10
  96. package/apps/web/dist/assets/lock-DZL2N67v.js +0 -1
  97. package/apps/web/dist/assets/meta-6jZygxJq.js +0 -1
  98. package/apps/web/dist/assets/pen-line-NK46C_nG.js +0 -1
  99. package/apps/web/dist/assets/plus-Du-Jx8aI.js +0 -1
  100. package/apps/web/dist/assets/refresh-cw-BNtHis1x.js +0 -1
  101. package/apps/web/dist/assets/settings-menu-Dph4yXbO.js +0 -1
  102. package/apps/web/dist/assets/star-UHmZXpjA.js +0 -1
  103. package/apps/web/dist/assets/trash-0IafyhZB.js +0 -1
  104. package/apps/web/dist/assets/ui-BS348L4M.js +0 -1
  105. package/apps/web/dist/assets/upload-DNRENWzQ.js +0 -1
  106. package/apps/web/dist/assets/video-9fDPRPCv.js +0 -1
  107. package/apps/web/dist/assets/volume-2-DE67_rue.js +0 -1
  108. package/apps/web/dist/assets/wand-sparkles-IEd4jkyn.js +0 -1
@@ -71,6 +71,13 @@
71
71
  "when": 1791311715390,
72
72
  "tag": "0009_knowledge",
73
73
  "breakpoints": true
74
+ },
75
+ {
76
+ "idx": 10,
77
+ "version": "6",
78
+ "when": 1791469862548,
79
+ "tag": "0010_plugin_connection",
80
+ "breakpoints": true
74
81
  }
75
82
  ]
76
83
  }
@@ -497,3 +497,24 @@ export const marketplaceStar = sqliteTable("marketplace_star", {
497
497
  entryId: text("entry_id").notNull(),
498
498
  createdAt: createdAt(),
499
499
  }, (t) => [primaryKey({ columns: [t.tenantId, t.entryId] })]);
500
+ /**
501
+ * An account a plugin connected for a space (`server.connections`): a calendar, a mailbox. Not
502
+ * a wizard's: it outlives runs and people. `data` is encrypted.
503
+ */
504
+ export const pluginConnection = sqliteTable("plugin_connection", {
505
+ id: text("id").primaryKey(),
506
+ plugin: text("plugin").notNull(),
507
+ projectId: text("project_id")
508
+ .notNull()
509
+ .references(() => project.id, { onDelete: "cascade" }),
510
+ /** The built-in connector: `google-calendar`, `microsoft-outlook`. */
511
+ connector: text("connector").notNull(),
512
+ /** Shown to the person: the account's address. */
513
+ label: text("label").notNull(),
514
+ /** The connector's actions it was connected for. */
515
+ actions: text("actions", { mode: "json" }).$type().notNull(),
516
+ data: text("data").notNull(),
517
+ createdBy: text("created_by"),
518
+ createdAt: createdAt(),
519
+ updatedAt: updatedAt(),
520
+ }, (t) => [index("plugin_connection_space").on(t.plugin, t.projectId)]);
@@ -59,6 +59,12 @@ const attendeesField = z
59
59
  .array(z.string().describe("Attendee email address."))
60
60
  .optional()
61
61
  .describe("Optional list of attendee email addresses.");
62
+ const sendUpdatesField = z
63
+ .enum(["all", "externalOnly", "none"])
64
+ .optional()
65
+ .describe('Whom Google e-mails about the change: "all" attendees, "externalOnly" (outside the organisation) or "none" (default).');
66
+ /** `?sendUpdates=` for a write, when the caller asked Google to tell the attendees. */
67
+ const updatesQuery = (value) => value ? `?sendUpdates=${encodeURIComponent(value)}` : "";
62
68
  export const calendarConnector = defineConnector({
63
69
  actions: [
64
70
  connectorAction({
@@ -237,12 +243,13 @@ export const calendarConnector = defineConnector({
237
243
  group: "write",
238
244
  handler: async (input, ctx) => {
239
245
  const calendarId = input.calendar_id ?? "primary";
240
- const created = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events`, {
246
+ const created = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events${updatesQuery(input.send_updates)}`, {
241
247
  body: JSON.stringify({
242
248
  end: toEventDateTime(input.end, input.time_zone),
243
249
  start: toEventDateTime(input.start, input.time_zone),
244
250
  summary: input.summary,
245
251
  ...(input.description ? { description: input.description } : {}),
252
+ ...(input.location ? { location: input.location } : {}),
246
253
  ...(input.attendees?.length
247
254
  ? { attendees: input.attendees.map((email) => ({ email })) }
248
255
  : {}),
@@ -264,7 +271,12 @@ export const calendarConnector = defineConnector({
264
271
  .optional()
265
272
  .describe("Optional event description/notes."),
266
273
  end: endField,
274
+ location: z
275
+ .string()
276
+ .optional()
277
+ .describe("Where the event takes place: an address, a phone number or a meeting link."),
267
278
  private_properties: privatePropertiesField,
279
+ send_updates: sendUpdatesField,
268
280
  start: startField,
269
281
  summary: z.string().describe("Event title."),
270
282
  time_zone: timeZoneField,
@@ -284,6 +296,9 @@ export const calendarConnector = defineConnector({
284
296
  if (input.description !== undefined) {
285
297
  patch.description = input.description;
286
298
  }
299
+ if (input.location !== undefined) {
300
+ patch.location = input.location;
301
+ }
287
302
  if (input.start !== undefined) {
288
303
  patch.start = toEventDateTime(input.start, input.time_zone);
289
304
  }
@@ -296,7 +311,7 @@ export const calendarConnector = defineConnector({
296
311
  if (input.private_properties !== undefined) {
297
312
  patch.extendedProperties = { private: input.private_properties };
298
313
  }
299
- const updated = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`, {
314
+ const updated = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}${updatesQuery(input.send_updates)}`, {
300
315
  body: JSON.stringify(patch),
301
316
  headers: { "Content-Type": "application/json" },
302
317
  method: "PATCH",
@@ -314,7 +329,12 @@ export const calendarConnector = defineConnector({
314
329
  .describe("New event description (replaces the existing one)."),
315
330
  end: endField.optional(),
316
331
  event_id: z.string().describe("Calendar event id to update."),
332
+ location: z
333
+ .string()
334
+ .optional()
335
+ .describe("New location (replaces the existing one)."),
317
336
  private_properties: privatePropertiesField,
337
+ send_updates: sendUpdatesField,
318
338
  start: startField.optional(),
319
339
  summary: z.string().optional().describe("New event title."),
320
340
  time_zone: timeZoneField,
@@ -322,6 +342,7 @@ export const calendarConnector = defineConnector({
322
342
  .refine((v) => v.attendees !== undefined ||
323
343
  v.description !== undefined ||
324
344
  v.end !== undefined ||
345
+ v.location !== undefined ||
325
346
  v.private_properties !== undefined ||
326
347
  v.start !== undefined ||
327
348
  v.summary !== undefined, { message: "Provide at least one field to update." }),
@@ -333,7 +354,7 @@ export const calendarConnector = defineConnector({
333
354
  group: "destructive",
334
355
  handler: async (input, ctx) => {
335
356
  const calendarId = input.calendar_id ?? "primary";
336
- await googleFetch(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`, { method: "DELETE" });
357
+ await googleFetch(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}${updatesQuery(input.send_updates)}`, { method: "DELETE" });
337
358
  return {
338
359
  calendar_id: calendarId,
339
360
  deleted: true,
@@ -344,6 +365,7 @@ export const calendarConnector = defineConnector({
344
365
  inputSchema: z.object({
345
366
  calendar_id: calendarIdField,
346
367
  event_id: z.string().describe("Calendar event id to delete."),
368
+ send_updates: sendUpdatesField,
347
369
  }),
348
370
  providerScopes: [SCOPE_EVENTS],
349
371
  summary: "Delete a calendar event",
@@ -25,6 +25,7 @@ function mapEventSummary(e) {
25
25
  isAllDay: e.isAllDay ?? false,
26
26
  location: e.location?.displayName ?? null,
27
27
  organizer: mapRecipient(e.organizer),
28
+ show_as: e.showAs ?? null,
28
29
  start: e.start ?? null,
29
30
  subject: e.subject ?? null,
30
31
  };
@@ -93,7 +94,7 @@ function draftMessagePayload(input) {
93
94
  toRecipients: toRecipients(input.to),
94
95
  };
95
96
  }
96
- const EVENT_SELECT = "id,subject,start,end,location,organizer,isAllDay";
97
+ const EVENT_SELECT = "id,subject,start,end,location,organizer,isAllDay,showAs";
97
98
  async function fetchEventDetail(ctx, eventId) {
98
99
  const e = await graphJson(ctx, `/me/events/${encodeURIComponent(eventId)}?$select=${EVENT_SELECT},attendees,body,webLink`);
99
100
  return mapEventDetail(e);
@@ -232,6 +233,10 @@ export const microsoftOutlookConnector = defineConnector({
232
233
  .optional()
233
234
  .describe("Window start (ISO 8601). Provide both start and end to query a window."),
234
235
  top: topField,
236
+ all: z
237
+ .boolean()
238
+ .optional()
239
+ .describe("With start and end: every event of the window (up to 1000), not only the first `top`. For free/busy."),
235
240
  }),
236
241
  providerScopes: ["Calendars.Read"],
237
242
  run: async (input, ctx) => {
@@ -244,6 +249,20 @@ export const microsoftOutlookConnector = defineConnector({
244
249
  if (input.start && input.end) {
245
250
  params.set("startDateTime", input.start);
246
251
  params.set("endDateTime", input.end);
252
+ if (input.all) {
253
+ // Page through the window, 100 at a time.
254
+ const events = [];
255
+ params.set("$top", "100");
256
+ for (let skip = 0; skip < 1000; skip += 100) {
257
+ params.set("$skip", String(skip));
258
+ const page = await graphJson(ctx, `${calendarViewPath(input.calendar_id)}?${params.toString()}`);
259
+ events.push(...(page.value ?? []));
260
+ if ((page.value ?? []).length < 100) {
261
+ break;
262
+ }
263
+ }
264
+ return { events: events.map(mapEventSummary) };
265
+ }
247
266
  path = `${calendarViewPath(input.calendar_id)}?${params.toString()}`;
248
267
  }
249
268
  const data = await graphJson(ctx, path);
@@ -320,6 +339,10 @@ export const microsoftOutlookConnector = defineConnector({
320
339
  .optional()
321
340
  .describe("Plain-text event description."),
322
341
  calendar_id: calendarIdField,
342
+ location: z
343
+ .string()
344
+ .optional()
345
+ .describe("Where the event takes place: an address, a phone number or a meeting link."),
323
346
  end: z
324
347
  .string()
325
348
  .describe("Event end, ISO 8601 date-time (e.g. 2026-07-04T15:00:00)."),
@@ -343,6 +366,7 @@ export const microsoftOutlookConnector = defineConnector({
343
366
  ? { body: { content: input.body_text, contentType: "Text" } }
344
367
  : {}),
345
368
  end: { dateTime: input.end, timeZone: input.time_zone },
369
+ ...(input.location ? { location: { displayName: input.location } } : {}),
346
370
  start: { dateTime: input.start, timeZone: input.time_zone },
347
371
  subject: input.subject,
348
372
  },
@@ -367,6 +391,7 @@ export const microsoftOutlookConnector = defineConnector({
367
391
  .describe("New plain-text event description."),
368
392
  end: z.string().optional().describe("New end, ISO 8601 date-time."),
369
393
  event_id: z.string().describe("Graph event id to update."),
394
+ location: z.string().optional().describe("New location (replaces the existing one)."),
370
395
  start: z.string().optional().describe("New start, ISO 8601 date-time."),
371
396
  subject: z.string().optional().describe("New event subject/title."),
372
397
  time_zone: z
@@ -392,6 +417,9 @@ export const microsoftOutlookConnector = defineConnector({
392
417
  if (input.attendees !== undefined) {
393
418
  patch.attendees = toAttendees(input.attendees);
394
419
  }
420
+ if (input.location !== undefined) {
421
+ patch.location = { displayName: input.location };
422
+ }
395
423
  if (Object.keys(patch).length === 0) {
396
424
  throw new Error("update_event: no fields to update");
397
425
  }
@@ -1,4 +1,4 @@
1
- import { isDecidedField, isLocationValue, MAX_FILES, shownFields, } from "@engenty-wizards/shared/definition";
1
+ import { isDecidedField, isLocationValue, MAX_FILES, offeredSlot, shownFields, } from "@engenty-wizards/shared/definition";
2
2
  function empty(v) {
3
3
  return v === undefined || v === null || v === "" || (Array.isArray(v) && v.length === 0);
4
4
  }
@@ -20,6 +20,11 @@ function coerce(field, raw) {
20
20
  return (Array.isArray(raw) ? raw : [raw])
21
21
  .map(String)
22
22
  .filter((v) => field.options?.includes(v));
23
+ // The offered time as written, also when the answer names the same moment in another offset.
24
+ case "slot": {
25
+ const v = String(raw).trim();
26
+ return v ? (offeredSlot(v, field.options ?? []) ?? v) : undefined;
27
+ }
23
28
  case "image":
24
29
  case "file": {
25
30
  const ids = (Array.isArray(raw) ? raw : [raw])
@@ -153,6 +158,10 @@ export function readPageInput(step, input, context = {}) {
153
158
  errors.push({ field: field.id, message: "Ungültige Auswahl" });
154
159
  continue;
155
160
  }
161
+ if (field.kind === "slot" && !empty(v) && !field.options?.includes(String(v))) {
162
+ errors.push({ field: field.id, message: "Dieser Termin wird nicht angeboten" });
163
+ continue;
164
+ }
156
165
  if (field.kind === "email" && !empty(v) && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(v))) {
157
166
  errors.push({ field: field.id, message: "Keine gültige E-Mail-Adresse" });
158
167
  continue;
@@ -143,7 +143,7 @@ export function registerTools(server, who) {
143
143
  });
144
144
  tool("list_starters", "wizards:read", {
145
145
  title: "List starters",
146
- description: "The marketplace's ready-made wizards (tweet with image, video ad, research briefing, dashboard, invoice, offer …), with what each makes and needs. Give a query to search them; without one you get the starters.",
146
+ description: "Ready-made wizards to start from: first those the space's plugins bring (marked with the plugin), then the marketplace's (tweet with image, video ad, research briefing, dashboard, invoice, offer …), with what each makes and needs. Give a query to search them; without one you get the starters.",
147
147
  input: z.object({ query: z.string().max(200).optional() }),
148
148
  readOnly: true,
149
149
  }, async ({ query }) => (await searchMarketplace({ q: query ?? "", lang: "de", limit: 30 })).entries
@@ -155,6 +155,9 @@ export function registerTools(server, who) {
155
155
  formats: s.formats,
156
156
  useCases: s.useCases,
157
157
  capabilities: s.capabilities,
158
+ ...(s.plugin ? { plugin: s.plugin.name } : {}),
159
+ // Not startable here: its plugins come with the Pro plan.
160
+ ...(s.needs?.length ? { needsPro: s.needs } : {}),
158
161
  })));
159
162
  tool("get_starter", "wizards:read", {
160
163
  title: "Get starter",
@@ -401,7 +404,7 @@ While status is running, get_run with waitSeconds. Hosts with MCP Apps also show
401
404
  }, async ({ runId, waitSeconds }) => runReport(who.userId, runId, waitSeconds));
402
405
  tool("answer_page", "runs:test", {
403
406
  title: "Answer page",
404
- description: "Fill the page a run waits for: field id → value (items: an array of row objects; toggle: true/false; multiselect: an array). Returns the run, after waitSeconds.",
407
+ description: "Fill the page a run waits for: field id → value (items: an array of row objects; toggle: true/false; multiselect: an array; slot: one of the offered times, as given). Returns the run, after waitSeconds.",
405
408
  input: z.object({
406
409
  runId: z.string(),
407
410
  stepId: z.string().describe("waitingFor.page"),
@@ -1,11 +1,12 @@
1
1
  import { existsSync, mkdirSync, watch } from "node:fs";
2
2
  import { join, resolve, sep } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { PLUGIN_TOOL_NAME } from "@engenty-wizards/shared/definition";
4
+ import { PLUGIN_ID, PLUGIN_TOOL_NAME, wizardSchema } from "@engenty-wizards/shared/definition";
5
5
  import { generateText, Output } from "ai";
6
6
  import { createJiti } from "jiti";
7
7
  import { readPage } from "../agents/website.js";
8
8
  import { packageRoot } from "../cli/home.js";
9
+ import { pluginConnections } from "../connectors/plugin-connections.js";
9
10
  import { db, onTenantOpen } from "../db/client.js";
10
11
  import { parseDocument } from "../documents/parse.js";
11
12
  import { env } from "../env.js";
@@ -98,6 +99,9 @@ function apiFor(record) {
98
99
  }
99
100
  record.assistantTools.set(tool.name, tool);
100
101
  },
102
+ registerAssistantContext(context) {
103
+ record.assistantPages.push(context);
104
+ },
101
105
  registerPublicRoute(route) {
102
106
  record.publicRoutes.push(compileRoute(route));
103
107
  },
@@ -153,6 +157,21 @@ function apiFor(record) {
153
157
  object: (request.schema ? result.output : undefined),
154
158
  };
155
159
  },
160
+ connections: pluginConnections(source.id),
161
+ registerStarter(starter) {
162
+ if (!PLUGIN_ID.test(starter.id)) {
163
+ throw new Error(`Starter "${starter.id}": an id is lower case, digits and "-".`);
164
+ }
165
+ if (record.starters.has(starter.id)) {
166
+ throw new Error(`Starter "${starter.id}" is registered twice.`);
167
+ }
168
+ const parsed = wizardSchema.safeParse(starter.definition);
169
+ if (!parsed.success) {
170
+ const issue = parsed.error.issues[0];
171
+ throw new Error(`Starter "${starter.id}": ${issue?.path.join(".") || "definition"}: ${issue?.message}`);
172
+ }
173
+ record.starters.set(starter.id, { ...starter, definition: parsed.data });
174
+ },
156
175
  onUnload(dispose) {
157
176
  record.disposers.push(dispose);
158
177
  },
@@ -186,8 +205,10 @@ async function load(source) {
186
205
  record.tools.clear();
187
206
  record.contexts = [];
188
207
  record.assistantTools.clear();
208
+ record.assistantPages = [];
189
209
  record.publicRoutes = [];
190
210
  record.jobs.clear();
211
+ record.starters.clear();
191
212
  record.listeners = {};
192
213
  record.migrations = [];
193
214
  record.error = err?.message || String(err);
@@ -17,8 +17,10 @@ export function emptyRecord(source) {
17
17
  tools: new Map(),
18
18
  contexts: [],
19
19
  assistantTools: new Map(),
20
+ assistantPages: [],
20
21
  publicRoutes: [],
21
22
  jobs: new Map(),
23
+ starters: new Map(),
22
24
  listeners: {},
23
25
  migrations: [],
24
26
  disposers: [],
@@ -83,6 +85,14 @@ export async function pluginToolsOf(tenantId) {
83
85
  description: tool.description,
84
86
  })));
85
87
  }
88
+ /** The starters the tenant's plugins bring, in the order the plugins registered them. */
89
+ export async function pluginStartersOf(tenantId) {
90
+ return (await pluginsOf(tenantId)).flatMap((plugin) => [...plugin.starters.values()].map((starter) => ({
91
+ id: `${plugin.source.id}.${starter.id}`,
92
+ plugin,
93
+ starter,
94
+ })));
95
+ }
86
96
  /** The built studio half as it is on disk now; null when the plugin has none. */
87
97
  export function pluginAsset(record, kind) {
88
98
  const file = record.source[kind];
@@ -1,4 +1,4 @@
1
- import { CAPABILITY_IDS, INDUSTRY_IDS, ITEM_FORMAT_IDS, USE_CASE_IDS, } from "@engenty-wizards/shared/marketplace";
1
+ import { CAPABILITY_IDS, INDUSTRY_IDS, ITEM_FORMAT_IDS, MARKETPLACE_PLAN_IDS, USE_CASE_IDS, } from "@engenty-wizards/shared/marketplace";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
4
  import { asLang, marketplaceDetail, searchMarketplace, starEntry, } from "../services/marketplace.js";
@@ -9,6 +9,7 @@ const searchSchema = z.object({
9
9
  industry: z.enum(INDUSTRY_IDS).optional(),
10
10
  format: z.enum(ITEM_FORMAT_IDS).optional(),
11
11
  capability: z.enum(CAPABILITY_IDS).optional(),
12
+ plan: z.enum(MARKETPLACE_PLAN_IDS).optional(),
12
13
  starred: z
13
14
  .enum(["1", "0"])
14
15
  .optional()
@@ -9,6 +9,7 @@ import { nanoid } from "nanoid";
9
9
  import { z } from "zod";
10
10
  import { principalOf } from "../auth/index.js";
11
11
  import { ConnectError, connectionViews, connectWithCredentials, disconnect, finishOAuth, oauthStateRun, startOAuth, } from "../connectors/index.js";
12
+ import { finishPluginOAuth, pluginStateTenant } from "../connectors/plugin-connections.js";
12
13
  import { db, schema, withTenant } from "../db/client.js";
13
14
  import { answerAsk, pendingAsk } from "../engine/asks.js";
14
15
  import { signalChanged, subscribe } from "../engine/events.js";
@@ -795,12 +796,19 @@ export const connectCallback = new Hono().get("/callback", async (c) => {
795
796
  let message = c.req.query("error") === "exchange_failed"
796
797
  ? "Verbinden hat nicht geklappt. Bitte noch einmal versuchen."
797
798
  : "Verbinden wurde abgebrochen.";
798
- const tenant = state ? await tenantOfRun(oauthStateRun(state) ?? "") : null;
799
+ // A plugin's sign-in (server.connections) names its tenant; a wizard's names its run.
800
+ const pluginTenant = state ? pluginStateTenant(state) : null;
801
+ const tenant = state ? (pluginTenant ?? (await tenantOfRun(oauthStateRun(state) ?? ""))) : null;
799
802
  const answer = ticket ? { ticket } : code ? { code } : null;
800
803
  if (answer && state && tenant) {
801
804
  try {
802
- const done = await withTenant(tenant, () => finishOAuth(answer, state));
803
- signalChanged(done.runId);
805
+ if (pluginTenant) {
806
+ await withTenant(tenant, () => finishPluginOAuth(answer, state));
807
+ }
808
+ else {
809
+ const done = await withTenant(tenant, () => finishOAuth(answer, state));
810
+ signalChanged(done.runId);
811
+ }
804
812
  ok = true;
805
813
  message = "Verbunden. Du kannst dieses Fenster schließen.";
806
814
  }
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { MODEL_CLASSES, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
3
+ import { MODEL_CLASSES, PLUGIN_ID, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
4
4
  import { CATEGORY_TYPES, whereSchema } from "@engenty-wizards/shared/knowledge";
5
5
  import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
6
6
  import { isCreditsRef } from "@engenty-wizards/shared/providers";
@@ -405,6 +405,7 @@ export const studio = new Hono()
405
405
  .max(24)
406
406
  .default([]),
407
407
  part: z.enum(["info", "knowledge"]).default("info"),
408
+ plugin: z.string().regex(PLUGIN_ID).optional(),
408
409
  })
409
410
  .parse(await c.req.json());
410
411
  if (!(await canSpend())) {
@@ -420,6 +421,7 @@ export const studio = new Hono()
420
421
  message: body.message,
421
422
  history: body.history,
422
423
  part: body.part,
424
+ plugin: body.plugin,
423
425
  signal: abort.signal,
424
426
  onText: (delta) => {
425
427
  void stream.writeSSE({ event: "text", data: JSON.stringify(delta) });