wizards 0.2.17 → 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 (65) 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/db/migrations/tenant/0009_knowledge.sql +104 -0
  4. package/apps/runtime/dist/db/migrations/tenant/meta/0009_snapshot.json +2837 -0
  5. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +7 -0
  6. package/apps/runtime/dist/db/schema.js +160 -8
  7. package/apps/runtime/dist/engine/steps.js +3 -11
  8. package/apps/runtime/dist/env.js +2 -0
  9. package/apps/runtime/dist/files/storage.js +15 -0
  10. package/apps/runtime/dist/models.js +38 -0
  11. package/apps/runtime/dist/plugins/loader.js +2 -0
  12. package/apps/runtime/dist/routes/studio.js +147 -6
  13. package/apps/runtime/dist/services/cloud.js +59 -1
  14. package/apps/runtime/dist/services/knowledge-model.js +558 -0
  15. package/apps/runtime/dist/services/knowledge.js +721 -0
  16. package/apps/runtime/dist/services/project-files.js +114 -6
  17. package/apps/runtime/dist/services/project-index.js +632 -94
  18. package/apps/runtime/dist/services/relevance.js +90 -0
  19. package/apps/runtime/dist/services/space-categories.js +882 -0
  20. package/apps/runtime/dist/services/space-data.js +466 -38
  21. package/apps/runtime/dist/services/space-origin.js +164 -0
  22. package/apps/runtime/dist/services/spaces.js +155 -1
  23. package/apps/runtime/dist/tools/pages.js +27 -22
  24. package/apps/runtime/dist/tools/project.js +130 -40
  25. package/apps/web/dist/assets/{Account-LVXhagaS.js → Account-Cp1cgqjV.js} +1 -1
  26. package/apps/web/dist/assets/{ChangelogDialog-DM0KAoy6.js → ChangelogDialog-Chke1qUe.js} +1 -1
  27. package/apps/web/dist/assets/{ChatPanel-CxOQmtv-.js → ChatPanel-DWrwewr6.js} +1 -1
  28. package/apps/web/dist/assets/{CreditsDialog-DkW3YT44.js → CreditsDialog-Ckema8Ss.js} +1 -1
  29. package/apps/web/dist/assets/{EditorPage-hbisE1Wf.js → EditorPage-XX6pfHk5.js} +1 -1
  30. package/apps/web/dist/assets/{FlowPreview-v7or29ku.js → FlowPreview-DS545n78.js} +1 -1
  31. package/apps/web/dist/assets/{Harness-RZF-bBmb.js → Harness-H3yXP-2L.js} +1 -1
  32. package/apps/web/dist/assets/{HomePage-mxG-a_KU.js → HomePage-Bds84ShK.js} +1 -1
  33. package/apps/web/dist/assets/{ImportWizard-CiPyUt3E.js → ImportWizard-0_NkA8aS.js} +1 -1
  34. package/apps/web/dist/assets/{LocalRuntime-Dqp9wkN3.js → LocalRuntime-DnABzTOe.js} +1 -1
  35. package/apps/web/dist/assets/{NewWizardPage-DHwRsi0_.js → NewWizardPage-BqP75V9K.js} +2 -2
  36. package/apps/web/dist/assets/ProjectPage-DbOvwOdZ.js +5 -0
  37. package/apps/web/dist/assets/{PublicRunner-TGkOwbAP.js → PublicRunner-CtZ7vxl_.js} +1 -1
  38. package/apps/web/dist/assets/{RunnerView-DzHGshZj.js → RunnerView-xV5bWclu.js} +1 -1
  39. package/apps/web/dist/assets/{SettingsPage-CG3Xtfej.js → SettingsPage-DKt55zFh.js} +1 -1
  40. package/apps/web/dist/assets/{SetupPage-O1GfPlqX.js → SetupPage-c6mMD9zN.js} +1 -1
  41. package/apps/web/dist/assets/{SharePage-CCNUhtry.js → SharePage-DTKIbMj-.js} +1 -1
  42. package/apps/web/dist/assets/{api-DB22_B7G.js → api-CVG-xDEn.js} +1 -1
  43. package/apps/web/dist/assets/changelog-CXXT0zsp.js +1 -0
  44. package/apps/web/dist/assets/{esm-3TT9i_rv.js → esm-DLM3jdbm.js} +1 -1
  45. package/apps/web/dist/assets/{index-CLe9Wrpu.js → index-1gmaxDSk.js} +3 -3
  46. package/apps/web/dist/assets/index-Bwyehu0k.css +1 -0
  47. package/apps/web/dist/assets/{meta-CEsBCPfA.js → meta-m947x6uG.js} +1 -1
  48. package/apps/web/dist/assets/{settings-menu-pOcjznGJ.js → settings-menu-DUY6__C3.js} +1 -1
  49. package/apps/web/dist/assets/ui-BPY2pIh0.js +1 -0
  50. package/apps/web/dist/index.html +4 -4
  51. package/apps/web/dist/mcp/flow.html +2 -2
  52. package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +164 -0
  53. package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +199 -0
  54. package/node_modules/@engenty-wizards/shared/dist/knowledge.js +68 -0
  55. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +1 -16
  56. package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
  57. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +8 -0
  58. package/node_modules/@engenty-wizards/shared/dist/run.js +5 -0
  59. package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +20 -0
  60. package/node_modules/@engenty-wizards/shared/dist/space-data.js +2 -1
  61. package/package.json +1 -1
  62. package/apps/web/dist/assets/ProjectPage-Bb7jN_6n.js +0 -4
  63. package/apps/web/dist/assets/changelog-BkIJdZ7M.js +0 -1
  64. package/apps/web/dist/assets/index-BPpKz65x.css +0 -1
  65. package/apps/web/dist/assets/ui-I7QYMX0F.js +0 -1
@@ -64,6 +64,13 @@
64
64
  "when": 1791307814904,
65
65
  "tag": "0008_run_event_note",
66
66
  "breakpoints": true
67
+ },
68
+ {
69
+ "idx": 9,
70
+ "version": "6",
71
+ "when": 1791311715390,
72
+ "tag": "0009_knowledge",
73
+ "breakpoints": true
67
74
  }
68
75
  ]
69
76
  }
@@ -1,5 +1,5 @@
1
1
  import { sql } from "drizzle-orm";
2
- import { blob, index, integer, primaryKey, sqliteTable, text, uniqueIndex, } from "drizzle-orm/sqlite-core";
2
+ import { blob, index, integer, primaryKey, real, sqliteTable, text, uniqueIndex, } from "drizzle-orm/sqlite-core";
3
3
  const now = sql `(unixepoch() * 1000)`;
4
4
  const createdAt = () => integer("created_at", { mode: "timestamp_ms" }).notNull().default(now);
5
5
  const updatedAt = () => integer("updated_at", { mode: "timestamp_ms" }).notNull().default(now);
@@ -196,14 +196,23 @@ export const projectFile = sqliteTable("project_file", {
196
196
  chars: integer("chars"),
197
197
  /** What the index holds of a document; `embeddings` includes the keywords. */
198
198
  indexed: text("indexed", { enum: ["embeddings", "keywords"] }),
199
+ /** A plugin's file (`server.spaceData.putFile`): the plugin, its key and what it calls it. */
200
+ origin: text("origin"),
201
+ originKey: text("origin_key"),
202
+ originLabel: text("origin_label"),
199
203
  createdAt: createdAt(),
200
204
  updatedAt: updatedAt(),
201
- }, (t) => [index("project_file_project").on(t.projectId, t.kind, t.position)]);
205
+ }, (t) => [
206
+ index("project_file_project").on(t.projectId, t.kind, t.position),
207
+ uniqueIndex("project_file_origin").on(t.projectId, t.origin, t.originKey),
208
+ ]);
202
209
  /**
203
- * The document index: passages of a project's documents, and of the texts plugins put in
204
- * (`server.index`). Keywords live in the FTS5 table `project_chunk_fts`, which triggers keep
205
- * in step (see the migrations); `embedding` is the passage's vector as float32 bytes, compared
206
- * with libSQL's `vector_distance_cos`.
210
+ * The index of Wissen: one unit per section of a page, row of a table, file, summary of a
211
+ * Kategorie's value, passage of a document read before pages, and passage of a plugin's text
212
+ * (`server.index`). Each unit's text starts with where it stands and its Kategorien. Keywords
213
+ * live in two FTS5 tables, words (`project_chunk_fts`) and trigrams (`project_chunk_tri`), which
214
+ * triggers keep in step (see the migrations); `embedding` is the unit's vector as float32
215
+ * bytes, compared with libSQL's `vector_distance_cos`.
207
216
  */
208
217
  export const projectChunk = sqliteTable("project_chunk", {
209
218
  id: integer("id").primaryKey({ autoIncrement: true }),
@@ -212,6 +221,23 @@ export const projectChunk = sqliteTable("project_chunk", {
212
221
  .references(() => project.id, { onDelete: "cascade" }),
213
222
  /** The document the passage is of; null for a plugin's text. */
214
223
  fileId: text("file_id").references(() => projectFile.id, { onDelete: "cascade" }),
224
+ /** What the unit is of: a page, a row (with its table), a table (FAQ format: a row), a value. */
225
+ pageId: text("page_id").references(() => spacePage.id, {
226
+ onDelete: "cascade",
227
+ }),
228
+ rowId: text("row_id").references(() => spaceTableRow.id, {
229
+ onDelete: "cascade",
230
+ }),
231
+ tableId: text("table_id").references(() => spaceTable.id, {
232
+ onDelete: "cascade",
233
+ }),
234
+ valueId: text("value_id").references(() => spaceCategoryValue.id, {
235
+ onDelete: "cascade",
236
+ }),
237
+ /** A section's headings, outermost first, joined by " › ". */
238
+ heading: text("heading"),
239
+ /** The page of the original a section starts on (`<!-- S. 3 -->`). */
240
+ sheet: integer("sheet"),
215
241
  /** A plugin's text: the plugin, its key for the text, its title and where the studio shows it. */
216
242
  plugin: text("plugin"),
217
243
  ref: text("ref"),
@@ -226,6 +252,10 @@ export const projectChunk = sqliteTable("project_chunk", {
226
252
  index("project_chunk_project").on(t.projectId, t.model),
227
253
  index("project_chunk_file").on(t.fileId),
228
254
  index("project_chunk_ref").on(t.projectId, t.plugin, t.ref),
255
+ index("project_chunk_page").on(t.pageId),
256
+ index("project_chunk_row").on(t.rowId),
257
+ index("project_chunk_table").on(t.tableId),
258
+ index("project_chunk_value").on(t.valueId),
229
259
  ]);
230
260
  // --- The space's data -------------------------------------------------------
231
261
  // Tables and pages a space keeps, its own or one of its wizards'. Shared by everyone who works
@@ -247,12 +277,30 @@ export const spaceTable = sqliteTable("space_table", {
247
277
  /** The column a row is matched on, as the list says. */
248
278
  keyColumn: text("key_column"),
249
279
  title: text("title").notNull(),
280
+ /** Its name in a path (`tables/<slug>`), unique among the space's tables. */
281
+ slug: text("slug"),
282
+ /** `faq`: a question and its answer per row. */
283
+ format: text("format", { enum: ["faq"] }),
250
284
  columns: text("columns", { mode: "json" }).$type().notNull(),
285
+ /**
286
+ * Written by a plugin (`server.spaceData`): the plugin, its key for the item and what it
287
+ * calls where it came from ("Tagesberichte"). Null: made in the studio or uploaded.
288
+ */
289
+ origin: text("origin"),
290
+ originKey: text("origin_key"),
291
+ originLabel: text("origin_label"),
292
+ /** A person changed what its origin wrote: the origin no longer writes it. */
293
+ kept: integer("kept", { mode: "boolean" }).notNull().default(false),
294
+ /** Why a person should look at it ("hardly readable"); null when nothing is in doubt. */
295
+ review: text("review"),
296
+ /** The file it was read from, kept as the evidence. */
297
+ fileId: text("file_id").references(() => projectFile.id, { onDelete: "set null" }),
251
298
  createdAt: createdAt(),
252
299
  updatedAt: updatedAt(),
253
300
  }, (t) => [
254
301
  index("space_table_project").on(t.projectId, t.wizardId),
255
302
  uniqueIndex("space_table_list").on(t.wizardId, t.list),
303
+ uniqueIndex("space_table_origin").on(t.projectId, t.origin, t.originKey),
256
304
  ]);
257
305
  /** A row of a table: cells by column id. Rows stand in the order they were added. */
258
306
  export const spaceTableRow = sqliteTable("space_table_row", {
@@ -269,7 +317,10 @@ export const spaceTableRow = sqliteTable("space_table_row", {
269
317
  index("space_table_row_table").on(t.tableId, t.createdAt),
270
318
  index("space_table_row_key").on(t.tableId, t.key),
271
319
  ]);
272
- /** A page: a title and markdown, what the editor shows and an agent reads and writes. */
320
+ /**
321
+ * A page: a title and markdown, what the editor shows and an agent reads and writes. A page of
322
+ * the space's own may have sub-pages: a long document split along its own headings.
323
+ */
273
324
  export const spacePage = sqliteTable("space_page", {
274
325
  id: text("id").primaryKey(),
275
326
  tenantId: text("tenant_id").notNull(),
@@ -277,11 +328,112 @@ export const spacePage = sqliteTable("space_page", {
277
328
  .notNull()
278
329
  .references(() => project.id, { onDelete: "cascade" }),
279
330
  wizardId: text("wizard_id").references(() => wizard.id, { onDelete: "cascade" }),
331
+ parentId: text("parent_id").references(() => spacePage.id, {
332
+ onDelete: "cascade",
333
+ }),
334
+ /** Its place among its parent's sub-pages. */
335
+ position: integer("position").notNull().default(0),
280
336
  title: text("title").notNull(),
337
+ /** Its name in a path (`pages/<parent>/<slug>`), unique among its siblings. */
338
+ slug: text("slug"),
281
339
  markdown: text("markdown").notNull().default(""),
340
+ /**
341
+ * Written by a plugin (`server.spaceData`): the plugin, its key for the item and what it
342
+ * calls where it came from ("Tagesberichte"). Null: made in the studio or uploaded.
343
+ */
344
+ origin: text("origin"),
345
+ originKey: text("origin_key"),
346
+ originLabel: text("origin_label"),
347
+ /** A person changed what its origin wrote: the origin no longer writes it. */
348
+ kept: integer("kept", { mode: "boolean" }).notNull().default(false),
349
+ /** Why a person should look at it ("hardly readable"); null when nothing is in doubt. */
350
+ review: text("review"),
351
+ /** The file it was read from, kept as the evidence. */
352
+ fileId: text("file_id").references(() => projectFile.id, { onDelete: "set null" }),
282
353
  createdAt: createdAt(),
283
354
  updatedAt: updatedAt(),
284
- }, (t) => [index("space_page_project").on(t.projectId, t.wizardId)]);
355
+ }, (t) => [
356
+ index("space_page_project").on(t.projectId, t.wizardId),
357
+ index("space_page_parent").on(t.parentId, t.position),
358
+ uniqueIndex("space_page_origin").on(t.projectId, t.origin, t.originKey),
359
+ ]);
360
+ // --- Kategorien -----------------------------------------------------------------
361
+ // Typed properties that run across Wissen: set on pages, rows, tables and files, any number of
362
+ // each, so a step filters before it searches.
363
+ /** A Kategorie of the space: a name, a type and, for a choice, the values it has met. */
364
+ export const spaceCategory = sqliteTable("space_category", {
365
+ id: text("id").primaryKey(),
366
+ tenantId: text("tenant_id").notNull(),
367
+ projectId: text("project_id")
368
+ .notNull()
369
+ .references(() => project.id, { onDelete: "cascade" }),
370
+ name: text("name").notNull(),
371
+ type: text("type", { enum: ["choice", "date", "number", "text", "boolean"] }).notNull(),
372
+ /** A number's unit: `h`, `€`, `kg`. */
373
+ unit: text("unit"),
374
+ /** A choice: several values per item. */
375
+ multiple: integer("multiple", { mode: "boolean" }).notNull().default(false),
376
+ /** A choice whose values are ranked (niedrig, mittel, hoch): their position is the rank. */
377
+ ordered: integer("ordered", { mode: "boolean" }).notNull().default(false),
378
+ /** Where its values come from, said in a few words ("Kopfzeile", "Spalte WLL"). */
379
+ hint: text("hint"),
380
+ /** The plugin that brought it; null: made in the studio. */
381
+ origin: text("origin"),
382
+ /** Proposed, not yet taken: shown to be confirmed, never filled. */
383
+ proposed: integer("proposed", { mode: "boolean" }).notNull().default(false),
384
+ /** A column of a table whose cells are its values for the table's rows. */
385
+ tableId: text("table_id").references(() => spaceTable.id, { onDelete: "set null" }),
386
+ columnId: text("column_id"),
387
+ position: integer("position").notNull().default(0),
388
+ createdAt: createdAt(),
389
+ updatedAt: updatedAt(),
390
+ }, (t) => [uniqueIndex("space_category_name").on(t.projectId, t.name)]);
391
+ /** A value of a choice: grows as items arrive; its summary is the Übersicht of its items. */
392
+ export const spaceCategoryValue = sqliteTable("space_category_value", {
393
+ id: text("id").primaryKey(),
394
+ categoryId: text("category_id")
395
+ .notNull()
396
+ .references(() => spaceCategory.id, { onDelete: "cascade" }),
397
+ value: text("value").notNull(),
398
+ position: integer("position").notNull().default(0),
399
+ /** The Übersicht: Markdown a model wrote of the value's items, when it last did. */
400
+ summary: text("summary"),
401
+ summaryAt: integer("summary_at", { mode: "timestamp_ms" }),
402
+ createdAt: createdAt(),
403
+ }, (t) => [uniqueIndex("space_category_value_value").on(t.categoryId, t.value)]);
404
+ /**
405
+ * A Kategorie set on an item: exactly one of page, row, table and file. A choice points to its
406
+ * value; the other types keep theirs typed: a date as milliseconds, a number, a short text, yes
407
+ * or no. A choice with several values has a row per value.
408
+ */
409
+ export const spaceItemCategory = sqliteTable("space_item_category", {
410
+ id: integer("id").primaryKey({ autoIncrement: true }),
411
+ projectId: text("project_id")
412
+ .notNull()
413
+ .references(() => project.id, { onDelete: "cascade" }),
414
+ categoryId: text("category_id")
415
+ .notNull()
416
+ .references(() => spaceCategory.id, { onDelete: "cascade" }),
417
+ pageId: text("page_id").references(() => spacePage.id, { onDelete: "cascade" }),
418
+ rowId: text("row_id").references(() => spaceTableRow.id, { onDelete: "cascade" }),
419
+ tableId: text("table_id").references(() => spaceTable.id, { onDelete: "cascade" }),
420
+ fileId: text("file_id").references(() => projectFile.id, { onDelete: "cascade" }),
421
+ valueId: text("value_id").references(() => spaceCategoryValue.id, { onDelete: "cascade" }),
422
+ text: text("text"),
423
+ num: real("num"),
424
+ at: integer("at"),
425
+ bool: integer("bool", { mode: "boolean" }),
426
+ /** Who set it: a person, the item's origin (a source's structure), or a model reading it. */
427
+ by: text("by", { enum: ["person", "origin", "model"] })
428
+ .notNull()
429
+ .default("person"),
430
+ }, (t) => [
431
+ index("space_item_category_category").on(t.categoryId, t.valueId),
432
+ index("space_item_category_page").on(t.pageId),
433
+ index("space_item_category_row").on(t.rowId),
434
+ index("space_item_category_table").on(t.tableId),
435
+ index("space_item_category_file").on(t.fileId),
436
+ ]);
285
437
  // --- The wizard's store ------------------------------------------------------
286
438
  // What a wizard keeps between runs for one person. `holder` is that person: "u:<userId>" for a
287
439
  // signed-in admin, "v:<visitorId>" for an end user on a shared link.
@@ -6,6 +6,7 @@ import { extFor, loadAsset, loadAssetText } from "../files/storage.js";
6
6
  import { generateImageMedia, generateSpeechMedia, generateVideoMedia, } from "../media/generate.js";
7
7
  import { markMedia } from "../media/marking.js";
8
8
  import { attachTools, costOf, isHarnessVendor, textModel } from "../models.js";
9
+ import { knowledgeBlock } from "../services/knowledge.js";
9
10
  import { buildStepTools } from "../tools/index.js";
10
11
  import { spaceContextOf } from "../tools/plugin.js";
11
12
  import { personUploads } from "../tools/store.js";
@@ -31,16 +32,6 @@ function brandBlock(ctx) {
31
32
  ].filter(Boolean);
32
33
  return parts.length ? `# WHO THIS WIZARD BELONGS TO\n${parts.join("\n")}` : "";
33
34
  }
34
- /** The project's documents, for a step that has the tools to search and read them. */
35
- function documentsBlock(ctx) {
36
- const docs = ctx.projectFiles.filter((f) => f.kind === "document").slice(0, 40);
37
- if (!docs.length) {
38
- return "";
39
- }
40
- return `# THE PROJECT'S DOCUMENTS\nKnowledge the wizard's owner gave for all wizards. Find passages with project_search, read a whole document with project_document. Use them when the task touches what they cover.\n${docs
41
- .map((d) => `- ${d.name}${d.description ? ` — ${d.description}` : ""}`)
42
- .join("\n")}`;
43
- }
44
35
  /** Previous output of a step, for a revision. */
45
36
  async function previousResult(ctx, stepId) {
46
37
  const out = ctx.state.outputs[stepId];
@@ -173,7 +164,8 @@ export async function runAgentStep(step, ctx) {
173
164
  GROUND_RULES,
174
165
  `Today is ${today(ctx)}.`,
175
166
  brandBlock(ctx),
176
- documentsBlock(ctx),
167
+ // Wissen: what the space holds for its wizards, and how to find and read it.
168
+ await knowledgeBlock(ctx.project.id),
177
169
  // What the space's plugins hold for its wizards: a wiki, questions and answers.
178
170
  ...(await spaceContextOf(ctx)).blocks,
179
171
  formatHint,
@@ -158,6 +158,8 @@ export const env = {
158
158
  falKey: str("FAL_KEY"),
159
159
  elevenlabsKey: str("ELEVENLABS_API_KEY"),
160
160
  replicateKey: str("REPLICATE_API_TOKEN"),
161
+ /** TypeSafe's own API, for the classifier that checks what a search found (Jev). */
162
+ typesafeKey: str("TYPESAFE_API_KEY"),
161
163
  ollamaUrl: str("OLLAMA_URL", "http://127.0.0.1:11434/v1"),
162
164
  /** What each model class runs on when this runtime resolves classes itself (own keys, Ollama). */
163
165
  models: {
@@ -77,6 +77,21 @@ export async function inlineAssetRefs(html) {
77
77
  }
78
78
  return out;
79
79
  }
80
+ /** Gives an asset new content under the id it has: what points to it keeps pointing to it. */
81
+ export async function replaceAsset(id, input) {
82
+ const row = await db.query.asset.findFirst({ where: eq(schema.asset.id, id) });
83
+ if (!row) {
84
+ return;
85
+ }
86
+ const bytes = Buffer.from(input.data);
87
+ const file = `${id}-${nanoid(6)}.${extFor(input.mime)}`;
88
+ await objects.put(assetKey(file), bytes);
89
+ await db
90
+ .update(schema.asset)
91
+ .set({ mime: input.mime, name: input.name, path: file, size: bytes.byteLength })
92
+ .where(eq(schema.asset.id, id));
93
+ await objects.remove(assetKey(row.path));
94
+ }
80
95
  /** Deletes one asset: its file and its row. */
81
96
  export async function removeAsset(id) {
82
97
  const row = await db.query.asset.findFirst({ where: eq(schema.asset.id, id) });
@@ -600,6 +600,44 @@ export async function embeddingModel() {
600
600
  ? { model: gatewayClient(access, {}).embeddingModel("wizards/embedding"), ref: bound.model }
601
601
  : null;
602
602
  }
603
+ /**
604
+ * The way to TypeSafe's Jev, a classifier that answers typed questions with probabilities and
605
+ * writes no text: the credits on a Manage-App's runtime; alone, an AI Gateway key, a TypeSafe
606
+ * key, or the linked account's credits. Null when there is none.
607
+ */
608
+ export async function systemOneAccess() {
609
+ const cfg = await config();
610
+ const viaCredits = (access) => ({
611
+ url: `${access.baseUrl}/v4/ai/systemone`,
612
+ headers: {
613
+ authorization: `Bearer ${access.token}`,
614
+ // Booked under the classifier class; its evaluation model answers.
615
+ "ai-model-id": "wizards/classifier",
616
+ ...(access.tenant ? { "x-wizards-tenant": access.tenant } : {}),
617
+ },
618
+ model: null,
619
+ });
620
+ if (managed) {
621
+ const access = await creditAccess(cfg, true).catch(() => null);
622
+ return access ? viaCredits(access) : null;
623
+ }
624
+ if (cfg.keys.gateway) {
625
+ return {
626
+ url: "https://ai-gateway.vercel.sh/typesafe/v1/systemone",
627
+ headers: { authorization: `Bearer ${cfg.keys.gateway}` },
628
+ model: "typesafe-ai/jev",
629
+ };
630
+ }
631
+ if (env.typesafeKey) {
632
+ return {
633
+ url: "https://api.typesafe.ai/v1/systemone",
634
+ headers: { authorization: `Bearer ${env.typesafeKey}` },
635
+ model: "jev-latest",
636
+ };
637
+ }
638
+ const access = await creditAccess(cfg, false).catch(() => null);
639
+ return access ? viaCredits(access) : null;
640
+ }
603
641
  /** Search tools an AI Gateway runs itself; they travel in the request, so any gateway client names them. */
604
642
  export const gatewayTools = createGateway({ apiKey: "unused" }).tools;
605
643
  /**
@@ -11,6 +11,7 @@ import { parseDocument } from "../documents/parse.js";
11
11
  import { env } from "../env.js";
12
12
  import { ModelUnavailableError, textModel } from "../models.js";
13
13
  import { putIndexEntry, removeIndexEntries } from "../services/project-index.js";
14
+ import { originData } from "../services/space-origin.js";
14
15
  import { safeFetch } from "../tools/net-guard.js";
15
16
  import { discoverPlugins } from "./discovery.js";
16
17
  import { migrateOpenTenants, migratePlugins } from "./migrations.js";
@@ -112,6 +113,7 @@ function apiFor(record) {
112
113
  put: (entry) => putIndexEntry(source.id, entry),
113
114
  remove: (space, key) => removeIndexEntries(source.id, space, key),
114
115
  },
116
+ spaceData: originData(source.id),
115
117
  web: {
116
118
  fetch: (url, init) => safeFetch(url, init),
117
119
  read: (url, options) => readPage(url, options?.signal),
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { MODEL_CLASSES, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
4
+ import { CATEGORY_TYPES, whereSchema } from "@engenty-wizards/shared/knowledge";
4
5
  import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
5
6
  import { isCreditsRef } from "@engenty-wizards/shared/providers";
6
7
  import { generateText } from "ai";
@@ -30,15 +31,18 @@ import { HTML_RESPONSE_CSP } from "../render/guard.js";
30
31
  import { mayBuild, projectWritable, requireWritable } from "../services/access.js";
31
32
  import { architectTurn } from "../services/architect.js";
32
33
  import { checkForCloud, cloudLinked, cloudSpaces, cloudState, forgetCloud, publishAndSync, pushSharing, removeFromCloud, replaceCloudSpaces, rotateInCloud, syncToCloud, } from "../services/cloud.js";
33
- import { ServiceError } from "../services/errors.js";
34
+ import { notFound, ServiceError } from "../services/errors.js";
34
35
  import { deleteFile, listFiles, readFile, writeFile } from "../services/files.js";
36
+ import { categoryContents, listKnowledge, resolvePath, valueContents, } from "../services/knowledge.js";
37
+ import { writeSummary } from "../services/knowledge-model.js";
35
38
  import { exportWizard, importWizard, PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "../services/package.js";
36
39
  import { profileSchema, saveProfile, userProfile } from "../services/profile.js";
37
40
  import { addProjectFile, fileView, orderProjectFiles, projectFile, projectFileContent, projectFiles, reindexProjectFile, removeProjectFile, updateProjectFile, } from "../services/project-files.js";
38
- import { documentText, searchProject } from "../services/project-index.js";
41
+ import { documentText, queueIndex, searchKnowledge } from "../services/project-index.js";
39
42
  import { createProject, deleteProject, listProjects, maskedServers, ownedProject, projectLimit, projectPatchSchema, updateProject, } from "../services/projects.js";
40
43
  import { listResults, listRuns, startTestRun } from "../services/runs.js";
41
- import { addTableRow, createPage, createTable, deletePage, deleteTable, deleteTableRows, getPage, getTable, listSpaceData, updatePage, updateTable, updateTableRow, } from "../services/space-data.js";
44
+ import { addCategoryValue, categoryRow, categoryValue, deleteCategory, deleteCategoryValue, mergeCategoryValues, orderCategoryValues, putCategory, renameCategoryValue, setItemCategory, updateCategory, } from "../services/space-categories.js";
45
+ import { addTableRow, createPage, createTable, deletePage, deleteTable, deleteTableRows, getPage, getTable, listSpaceData, orderSubPages, updatePage, updateTable, updateTableRow, } from "../services/space-data.js";
42
46
  import { draftWidgetPreview } from "../services/widgets.js";
43
47
  import { createWizard, deleteWizard, duplicateWizard, listWizards, ownedWizard, rotateShareLink, updateWizardSettings, wizardMessages, wizardState, writableWizard, writeDraft, } from "../services/wizards.js";
44
48
  import { readSetting, writeSetting } from "../settings.js";
@@ -344,11 +348,13 @@ export const studio = new Hono()
344
348
  const text = await documentText(row);
345
349
  return c.json({ text: text.slice(0, limit), chars: text.length });
346
350
  })
347
- // What the document index finds for a question: the passages an agent step would get.
351
+ // What a search of Wissen finds for a question: the items an agent step would get.
348
352
  .post("/projects/:id/search", async (c) => {
349
353
  const project = await ownedProject(c.get("user").id, c.req.param("id"));
350
- const { query } = z.object({ query: z.string().min(1).max(400) }).parse(await c.req.json());
351
- return c.json({ hits: await searchProject(project.id, query, 6) });
354
+ const body = z
355
+ .object({ query: z.string().min(1).max(400), where: whereSchema.optional() })
356
+ .parse(await c.req.json());
357
+ return c.json(await searchKnowledge(project.id, body.query, { where: body.where }));
352
358
  })
353
359
  // The assistant fills the project in: from a description, a website, the files given.
354
360
  .post("/projects/:id/assist", async (c) => {
@@ -362,6 +368,7 @@ export const studio = new Hono()
362
368
  .array(z.object({ role: z.enum(["user", "assistant"]), content: z.string().max(8000) }))
363
369
  .max(24)
364
370
  .default([]),
371
+ part: z.enum(["info", "knowledge"]).default("info"),
365
372
  })
366
373
  .parse(await c.req.json());
367
374
  if (!(await canSpend())) {
@@ -376,6 +383,7 @@ export const studio = new Hono()
376
383
  projectId: project.id,
377
384
  message: body.message,
378
385
  history: body.history,
386
+ part: body.part,
379
387
  signal: abort.signal,
380
388
  onText: (delta) => {
381
389
  void stream.writeSSE({ event: "text", data: JSON.stringify(delta) });
@@ -466,6 +474,128 @@ export const studio = new Hono()
466
474
  mode: query.mode,
467
475
  days: query.days,
468
476
  }));
477
+ })
478
+ // --- Wissen: its items and Kategorien ---------------------------------------
479
+ .get("/projects/:id/knowledge", async (c) => c.json(await listKnowledge(c.get("user").id, c.req.param("id"))))
480
+ // What a link in a page names (`pages/bgb/p-281`): the studio opens it.
481
+ .get("/projects/:id/resolve", async (c) => {
482
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
483
+ const found = await resolvePath(project.id, z.string().max(600).parse(c.req.query("path")));
484
+ if (!found) {
485
+ throw notFound();
486
+ }
487
+ return c.json(found);
488
+ })
489
+ .post("/projects/:id/categories", async (c) => {
490
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
491
+ await requireWritable(project);
492
+ const body = z
493
+ .object({
494
+ name: z.string().min(1).max(80),
495
+ type: z.enum(CATEGORY_TYPES),
496
+ unit: z.string().max(20).nullish(),
497
+ multiple: z.boolean().optional(),
498
+ ordered: z.boolean().optional(),
499
+ hint: z.string().max(120).nullish(),
500
+ values: z.array(z.string().max(200)).max(200).optional(),
501
+ tableId: z.string().nullish(),
502
+ columnId: z.string().nullish(),
503
+ })
504
+ .parse(await c.req.json());
505
+ const row = await putCategory(project.id, body);
506
+ return c.json({ id: row.id });
507
+ })
508
+ .patch("/categories/:id", async (c) => {
509
+ const row = await categoryRow(c.req.param("id"));
510
+ await ownedProject(c.get("user").id, row.projectId);
511
+ const body = z
512
+ .object({
513
+ name: z.string().min(1).max(80).optional(),
514
+ unit: z.string().max(20).nullish(),
515
+ multiple: z.boolean().optional(),
516
+ ordered: z.boolean().optional(),
517
+ hint: z.string().max(120).nullish(),
518
+ proposed: z.literal(false).optional(),
519
+ position: z.number().int().min(0).optional(),
520
+ })
521
+ .parse(await c.req.json());
522
+ await updateCategory(row.id, body);
523
+ return c.json({ ok: true });
524
+ })
525
+ .delete("/categories/:id", async (c) => {
526
+ const row = await categoryRow(c.req.param("id"));
527
+ await ownedProject(c.get("user").id, row.projectId);
528
+ await deleteCategory(row.id);
529
+ return c.json({ ok: true });
530
+ })
531
+ .post("/categories/:id/values", async (c) => {
532
+ const row = await categoryRow(c.req.param("id"));
533
+ await ownedProject(c.get("user").id, row.projectId);
534
+ const { value } = z.object({ value: z.string().min(1).max(200) }).parse(await c.req.json());
535
+ return c.json(await addCategoryValue(row.id, value));
536
+ })
537
+ .put("/categories/:id/values/order", async (c) => {
538
+ const row = await categoryRow(c.req.param("id"));
539
+ await ownedProject(c.get("user").id, row.projectId);
540
+ const { ids } = z.object({ ids: z.array(z.string()).max(500) }).parse(await c.req.json());
541
+ await orderCategoryValues(row.id, ids);
542
+ return c.json({ ok: true });
543
+ })
544
+ .get("/values/:id", async (c) => c.json(await valueContents(c.get("user").id, c.req.param("id"))))
545
+ // Everything that has a Kategorie: the entries its page lists.
546
+ .get("/categories/:id/items", async (c) => c.json(await categoryContents(c.get("user").id, c.req.param("id"))))
547
+ .patch("/values/:id", async (c) => {
548
+ const { category } = await categoryValue(c.req.param("id"));
549
+ await ownedProject(c.get("user").id, category.projectId);
550
+ const { value } = z.object({ value: z.string().min(1).max(200) }).parse(await c.req.json());
551
+ await renameCategoryValue(c.req.param("id"), value);
552
+ return c.json({ ok: true });
553
+ })
554
+ .post("/values/:id/merge", async (c) => {
555
+ const { category } = await categoryValue(c.req.param("id"));
556
+ await ownedProject(c.get("user").id, category.projectId);
557
+ const { into } = z.object({ into: z.string() }).parse(await c.req.json());
558
+ await mergeCategoryValues(c.req.param("id"), into);
559
+ return c.json({ ok: true });
560
+ })
561
+ .delete("/values/:id", async (c) => {
562
+ const { category } = await categoryValue(c.req.param("id"));
563
+ await ownedProject(c.get("user").id, category.projectId);
564
+ await deleteCategoryValue(c.req.param("id"));
565
+ return c.json({ ok: true });
566
+ })
567
+ // The Übersicht of a value: a model writes it from the value's items.
568
+ .post("/values/:id/summary", async (c) => {
569
+ const { category } = await categoryValue(c.req.param("id"));
570
+ const project = await ownedProject(c.get("user").id, category.projectId);
571
+ await requireWritable(project);
572
+ return c.json({ summary: await writeSummary(c.req.param("id")) });
573
+ })
574
+ // A Kategorie a person sets on an item: `p:<page>`, `t:<table>`, `r:<row>`, `f:<file>`.
575
+ .put("/projects/:id/items/:key/categories/:categoryId", async (c) => {
576
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
577
+ await requireWritable(project);
578
+ const key = c.req.param("key");
579
+ const id = key.slice(2);
580
+ const ref = key[0] === "p"
581
+ ? { pageId: id }
582
+ : key[0] === "t"
583
+ ? { tableId: id }
584
+ : key[0] === "r"
585
+ ? { rowId: id }
586
+ : key[0] === "f"
587
+ ? { fileId: id }
588
+ : null;
589
+ if (!ref || key[1] !== ":") {
590
+ throw new ServiceError("invalid", "Unbekannter Eintrag.");
591
+ }
592
+ const { values } = z
593
+ .object({ values: z.array(z.union([z.string().max(300), z.number(), z.boolean()])).max(50) })
594
+ .parse(await c.req.json());
595
+ if (await setItemCategory(project.id, ref, c.req.param("categoryId"), values)) {
596
+ queueIndex(key[0] === "p" ? `P:${id}` : key);
597
+ }
598
+ return c.json({ ok: true });
469
599
  })
470
600
  // --- the space's tables and pages --------------------------------------------
471
601
  .get("/projects/:id/data", async (c) => c.json(await listSpaceData(c.get("user").id, c.req.param("id"))))
@@ -475,6 +605,7 @@ export const studio = new Hono()
475
605
  title: z.string().max(120),
476
606
  wizardId: z.string().nullish(),
477
607
  columns: z.unknown().optional(),
608
+ format: z.enum(["faq"]).nullish(),
478
609
  })
479
610
  .parse(await c.req.json());
480
611
  return c.json(await createTable(c.get("user").id, c.req.param("id"), body));
@@ -486,6 +617,8 @@ export const studio = new Hono()
486
617
  title: z.string().max(120).optional(),
487
618
  wizardId: z.string().nullish(),
488
619
  columns: z.unknown().optional(),
620
+ format: z.enum(["faq"]).nullish(),
621
+ review: z.null().optional(),
489
622
  })
490
623
  .parse(await c.req.json());
491
624
  await updateTable(c.get("user").id, c.req.param("id"), body);
@@ -515,6 +648,7 @@ export const studio = new Hono()
515
648
  .object({
516
649
  title: z.string().max(200),
517
650
  wizardId: z.string().nullish(),
651
+ parentId: z.string().nullish(),
518
652
  markdown: z.string().optional(),
519
653
  })
520
654
  .parse(await c.req.json());
@@ -526,11 +660,18 @@ export const studio = new Hono()
526
660
  .object({
527
661
  title: z.string().max(200).optional(),
528
662
  wizardId: z.string().nullish(),
663
+ parentId: z.string().nullish(),
529
664
  markdown: z.string().optional(),
665
+ review: z.null().optional(),
530
666
  })
531
667
  .parse(await c.req.json());
532
668
  await updatePage(c.get("user").id, c.req.param("id"), body);
533
669
  return c.json({ ok: true });
670
+ })
671
+ .put("/pages/:id/order", async (c) => {
672
+ const { ids } = z.object({ ids: z.array(z.string()).max(5000) }).parse(await c.req.json());
673
+ await orderSubPages(c.get("user").id, c.req.param("id"), ids);
674
+ return c.json({ ok: true });
534
675
  })
535
676
  .delete("/pages/:id", async (c) => {
536
677
  await deletePage(c.get("user").id, c.req.param("id"));