mindoodb-app-sdk 0.0.71 → 0.0.72

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.
@@ -66,6 +66,296 @@ function assertValidMindooDBCreateIds(input, methodName = "createDocument") {
66
66
  }
67
67
  }
68
68
 
69
+ // src/inspection.ts
70
+ var MINDOODB_INSPECT_DOCUMENT = "mindoo.inspect.document@1";
71
+ var MINDOODB_INSPECT_SPREADSHEET = "mindoo.inspect.spreadsheet@1";
72
+ var MINDOODB_INSPECT_SLIDES = "mindoo.inspect.slides@1";
73
+ var MINDOODB_APP_INSPECTION_FIELD = "inspection";
74
+ var MINDOODB_APP_INSPECTION_SUMMARY_MAX_BYTES = 8 * 1024;
75
+ var MINDOODB_APP_INSPECTION_MAX_BYTES = 256 * 1024;
76
+ var MINDOODB_APP_ISSUES_MAX = 100;
77
+ var MindooDBAppEmbedValidationError = class extends Error {
78
+ constructor(issues) {
79
+ const first = issues.find((issue) => issue.severity === "error") ?? issues[0];
80
+ super(first ? first.message : "The document was not saved.");
81
+ this.issues = issues;
82
+ this.name = "MindooDBAppEmbedValidationError";
83
+ }
84
+ issues;
85
+ };
86
+ var SUPPORTS_CAPABILITY = /^[a-z][a-z0-9-]{0,31}$/;
87
+ function mindooDBAppComponentSupports(supports, requirement) {
88
+ const wanted = requirement.trim();
89
+ return (supports ?? []).some((entry) => entry === wanted || !wanted.includes(":") && entry.startsWith(`${wanted}:`));
90
+ }
91
+ function mindooDBAppInspectContracts(supports) {
92
+ return (supports ?? []).filter((entry) => entry.startsWith("inspect:")).map((entry) => entry.slice("inspect:".length));
93
+ }
94
+ function isPlainObject(value) {
95
+ return typeof value === "object" && value !== null && !Array.isArray(value);
96
+ }
97
+ function normalizeMindooDBAppInspectAnchor(raw) {
98
+ if (!isPlainObject(raw)) {
99
+ return void 0;
100
+ }
101
+ if (raw.kind === "heading" && typeof raw.id === "string" && raw.id && raw.id.length <= 200) {
102
+ return { kind: "heading", id: raw.id };
103
+ }
104
+ if (raw.kind === "cell" && typeof raw.ref === "string" && raw.ref && raw.ref.length <= 200) {
105
+ return { kind: "cell", ref: raw.ref };
106
+ }
107
+ if (raw.kind === "slide" && Number.isInteger(raw.index) && raw.index >= 0) {
108
+ return { kind: "slide", index: raw.index };
109
+ }
110
+ return void 0;
111
+ }
112
+ function normalizeMindooDBAppIssues(raw) {
113
+ if (!Array.isArray(raw)) {
114
+ return [];
115
+ }
116
+ const issues = [];
117
+ for (const entry of raw) {
118
+ if (issues.length >= MINDOODB_APP_ISSUES_MAX) {
119
+ break;
120
+ }
121
+ if (!isPlainObject(entry) || typeof entry.message !== "string" || !entry.message.trim()) {
122
+ continue;
123
+ }
124
+ const severity = entry.severity === "warning" || entry.severity === "info" ? entry.severity : entry.severity === "error" ? "error" : null;
125
+ if (!severity) {
126
+ continue;
127
+ }
128
+ const anchor = normalizeMindooDBAppInspectAnchor(entry.anchor);
129
+ const code = typeof entry.code === "string" && entry.code.trim() ? entry.code.trim().slice(0, 64) : void 0;
130
+ issues.push({
131
+ severity,
132
+ message: entry.message.trim().slice(0, 500),
133
+ ...anchor ? { anchor } : {},
134
+ ...code ? { code } : {}
135
+ });
136
+ }
137
+ return issues;
138
+ }
139
+ function normalizeMindooDBAppInspectRequest(raw) {
140
+ const input = isPlainObject(raw) ? raw : {};
141
+ const strings = (value, max) => Array.isArray(value) ? [...new Set(value.filter((entry) => typeof entry === "string" && !!entry && entry.length <= 200))].slice(0, max) : [];
142
+ const include = strings(input.include, 16);
143
+ const cells = strings(input.cells, 200);
144
+ const maxTextLength = Number(input.maxTextLength);
145
+ return {
146
+ ...typeof input.contract === "string" && input.contract ? { contract: input.contract.slice(0, 200) } : {},
147
+ ...include.length ? { include } : {},
148
+ ...cells.length ? { cells } : {},
149
+ ...Number.isFinite(maxTextLength) && maxTextLength > 0 ? { maxTextLength: Math.min(Math.floor(maxTextLength), 5e4) } : {}
150
+ };
151
+ }
152
+ function normalizeMindooDBAppInspection(raw) {
153
+ let json;
154
+ try {
155
+ json = JSON.stringify(raw);
156
+ } catch {
157
+ throw new Error("An inspection must be plain JSON.");
158
+ }
159
+ if (!json || json.length > MINDOODB_APP_INSPECTION_MAX_BYTES) {
160
+ throw new Error(`An inspection must not be larger than ${MINDOODB_APP_INSPECTION_MAX_BYTES / 1024} KiB.`);
161
+ }
162
+ const value = JSON.parse(json);
163
+ if (!isPlainObject(value) || typeof value.contract !== "string" || !value.contract) {
164
+ throw new Error("An inspection must be an object with a contract.");
165
+ }
166
+ return value;
167
+ }
168
+ var SUMMARY_TEXT_STEPS = [500, 200, 0];
169
+ var SUMMARY_LIST_STEPS = [50, 20, 5, 0];
170
+ function shorten(value, textMax, listMax, marks) {
171
+ if (typeof value === "string") {
172
+ if (value.length <= textMax) return value;
173
+ marks.truncated = true;
174
+ return value.slice(0, textMax);
175
+ }
176
+ if (Array.isArray(value)) {
177
+ if (value.length > listMax) marks.truncated = true;
178
+ return value.slice(0, listMax).map((entry) => shorten(entry, Math.min(textMax || 80, 80), listMax, marks));
179
+ }
180
+ if (isPlainObject(value)) {
181
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, shorten(entry, textMax, listMax, marks)]));
182
+ }
183
+ return value;
184
+ }
185
+ function summarizeMindooDBAppInspection(inspection, maxBytes = MINDOODB_APP_INSPECTION_SUMMARY_MAX_BYTES) {
186
+ const { cells: _cells, ...rest } = inspection;
187
+ const base = JSON.parse(JSON.stringify(rest));
188
+ for (const textMax of SUMMARY_TEXT_STEPS) {
189
+ for (const listMax of SUMMARY_LIST_STEPS) {
190
+ const marks = { truncated: false };
191
+ const next = shorten(base, textMax, listMax, marks);
192
+ if (textMax === 0 && typeof next.text === "string") delete next.text;
193
+ next.contract = inspection.contract;
194
+ if (typeof inspection.subject === "string") next.subject = inspection.subject.slice(0, 200);
195
+ if (marks.truncated || inspection.truncated) next.truncated = true;
196
+ if (JSON.stringify(next).length <= maxBytes) {
197
+ return next;
198
+ }
199
+ }
200
+ }
201
+ return { contract: inspection.contract, truncated: true };
202
+ }
203
+ function mindooDBAppInspectionSummaryValue(inspection) {
204
+ return MindooDBAppValue.atomic(JSON.stringify(summarizeMindooDBAppInspection(inspection)));
205
+ }
206
+ function readMindooDBAppInspectionSummary(root) {
207
+ let value = root?.[MINDOODB_APP_INSPECTION_FIELD];
208
+ if (typeof value === "string") {
209
+ try {
210
+ value = JSON.parse(value);
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ return isPlainObject(value) && typeof value.contract === "string" ? value : null;
216
+ }
217
+ function outcomeIssues(rule, outcome) {
218
+ if (!outcome) return [];
219
+ if (typeof outcome === "string") {
220
+ return [{ severity: rule.severity ?? "error", message: outcome, code: rule.code }];
221
+ }
222
+ return (Array.isArray(outcome) ? outcome : [outcome]).map((issue) => ({ code: rule.code, ...issue }));
223
+ }
224
+ function defineDocumentRules(definition) {
225
+ const request = normalizeMindooDBAppInspectRequest(definition);
226
+ function check(inspection) {
227
+ if (!inspection || definition.contract && inspection.contract !== definition.contract) {
228
+ return [];
229
+ }
230
+ const issues = definition.rules.flatMap((rule) => {
231
+ try {
232
+ return outcomeIssues(rule, rule.check(inspection));
233
+ } catch (error) {
234
+ return [
235
+ {
236
+ severity: "warning",
237
+ code: rule.code,
238
+ message: `Check "${rule.code}" failed: ${error instanceof Error ? error.message : String(error)}`
239
+ }
240
+ ];
241
+ }
242
+ });
243
+ return normalizeMindooDBAppIssues(issues);
244
+ }
245
+ const rules = {
246
+ request,
247
+ check,
248
+ checkRoot: (root) => check(readMindooDBAppInspectionSummary(root)),
249
+ beforeSave: ({ inspection }) => ({ issues: check(inspection) }),
250
+ attach: (embed) => embed.onBeforeSave(rules.beforeSave, { inspect: request })
251
+ };
252
+ return rules;
253
+ }
254
+ function plural(count, one, many) {
255
+ return `${count} ${count === 1 ? one : many}`;
256
+ }
257
+ var mindooDBAppInspectRules = {
258
+ /** Documents: no placeholder or MERGEFIELD left open. */
259
+ noOpenPlaceholders(severity = "error", message) {
260
+ return {
261
+ code: "open-placeholders",
262
+ severity,
263
+ check: (inspection) => {
264
+ const names = (inspection.placeholders ?? []).map((entry) => entry.name);
265
+ if (!names.length) return null;
266
+ return message?.(names) ?? `${plural(names.length, "placeholder is", "placeholders are")} not filled in: ${names.slice(0, 5).join(", ")}${names.length > 5 ? ", \u2026" : ""}`;
267
+ }
268
+ };
269
+ },
270
+ /** Documents: every comment resolved. */
271
+ noOpenComments(severity = "warning", message) {
272
+ return {
273
+ code: "open-comments",
274
+ severity,
275
+ check: (inspection) => inspection.openComments > 0 ? message?.(inspection.openComments) ?? `${plural(inspection.openComments, "comment is", "comments are")} not resolved.` : null
276
+ };
277
+ },
278
+ /** Documents: every tracked change accepted or rejected. */
279
+ noOpenTrackedChanges(severity = "warning", message) {
280
+ return {
281
+ code: "open-tracked-changes",
282
+ severity,
283
+ check: (inspection) => inspection.openTrackedChanges > 0 ? message?.(inspection.openTrackedChanges) ?? `${plural(inspection.openTrackedChanges, "tracked change is", "tracked changes are")} not accepted or rejected.` : null
284
+ };
285
+ },
286
+ /** Spreadsheets: no formula evaluates to an error; one issue per cell. */
287
+ noFormulaErrors(severity = "error") {
288
+ return {
289
+ code: "formula-errors",
290
+ severity,
291
+ check: (inspection) => (inspection.formulaErrors ?? []).map((entry) => ({
292
+ severity,
293
+ message: `${entry.ref}: ${entry.error}`,
294
+ anchor: { kind: "cell", ref: entry.ref }
295
+ }))
296
+ };
297
+ },
298
+ /** Presentations: no layout placeholder left empty; one issue per slide. */
299
+ noEmptySlidePlaceholders(severity = "warning") {
300
+ return {
301
+ code: "empty-placeholders",
302
+ severity,
303
+ check: (inspection) => {
304
+ const bySlide = /* @__PURE__ */ new Map();
305
+ for (const entry of inspection.emptyPlaceholders ?? []) {
306
+ bySlide.set(entry.slide, [...bySlide.get(entry.slide) ?? [], entry.placeholder]);
307
+ }
308
+ return [...bySlide.entries()].map(([slide, names]) => ({
309
+ severity,
310
+ message: `Slide ${slide + 1}: empty ${names.join(", ")}`,
311
+ anchor: { kind: "slide", index: slide }
312
+ }));
313
+ }
314
+ };
315
+ }
316
+ };
317
+ function isMindooDBAppSupportsEntry(entry, contractPattern) {
318
+ const [capability, contract, ...rest] = entry.split(":");
319
+ if (rest.length || !capability || !SUPPORTS_CAPABILITY.test(capability)) {
320
+ return false;
321
+ }
322
+ return contract === void 0 || contractPattern.test(contract);
323
+ }
324
+ async function performMindooDBAppEmbeddingSave(steps) {
325
+ const validation = await steps.validate();
326
+ if (!validation.ok) {
327
+ if (validation.issues.length && steps.showIssues) {
328
+ try {
329
+ await steps.showIssues(validation.issues);
330
+ } catch (error) {
331
+ console.warn("[mindoodb-app-sdk] Showing issues failed.", error);
332
+ }
333
+ }
334
+ throw new MindooDBAppEmbedValidationError(validation.issues);
335
+ }
336
+ let inspection = null;
337
+ if (steps.provider) {
338
+ try {
339
+ inspection = mindooDBAppInspectionSummaryValue(normalizeMindooDBAppInspection(await steps.provider({})));
340
+ } catch (error) {
341
+ console.warn("[mindoodb-app-sdk] Could not summarize the document.", error);
342
+ }
343
+ }
344
+ await steps.flush({ inspection, issues: validation.issues });
345
+ await steps.saved();
346
+ return validation;
347
+ }
348
+ async function resolveMindooDBAppEmbedValidation(issues, interactive, confirm) {
349
+ if (issues.some((issue) => issue.severity === "error")) {
350
+ return { ok: false, issues };
351
+ }
352
+ const warnings = issues.filter((issue) => issue.severity === "warning");
353
+ if (!warnings.length || !interactive) {
354
+ return { ok: true, issues };
355
+ }
356
+ return { ok: await confirm(warnings) === true, issues };
357
+ }
358
+
69
359
  // src/appDefinition.ts
70
360
  var MINDOODB_APP_DEFINITION_FORMAT = "mindoodb.haven.app";
71
361
  var MINDOODB_APP_DEFINITION_VERSION = 1;
@@ -96,7 +386,7 @@ var MINDOODB_APP_AGENT_LIMIT_MAX = 300;
96
386
  var MINDOODB_APP_AGENT_GUIDE_MAX_BYTES = 8 * 1024;
97
387
  var MINDOODB_APP_AGENT_TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{0,47}$/;
98
388
  var LOGICAL_DATABASE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
99
- function isPlainObject(value) {
389
+ function isPlainObject2(value) {
100
390
  return typeof value === "object" && value !== null && !Array.isArray(value);
101
391
  }
102
392
  function readOptionalString(raw, key, errors) {
@@ -127,7 +417,7 @@ function readStringRecord(raw, key, errors) {
127
417
  if (value === void 0 || value === null) {
128
418
  return void 0;
129
419
  }
130
- if (!isPlainObject(value)) {
420
+ if (!isPlainObject2(value)) {
131
421
  errors.push(`App definition ${key} must be an object of string values.`);
132
422
  return void 0;
133
423
  }
@@ -193,7 +483,7 @@ function readDatabases(value, errors) {
193
483
  const result = [];
194
484
  const seen = /* @__PURE__ */ new Set();
195
485
  value.forEach((entry, index) => {
196
- if (!isPlainObject(entry)) {
486
+ if (!isPlainObject2(entry)) {
197
487
  errors.push(`App definition database #${index} must be an object.`);
198
488
  return;
199
489
  }
@@ -246,7 +536,7 @@ function readLocalizedText(value, label, maxLength, errors) {
246
536
  }
247
537
  return trimmed || void 0;
248
538
  }
249
- if (!isPlainObject(value)) {
539
+ if (!isPlainObject2(value)) {
250
540
  errors.push(`${label} must be a string or an object of per-locale strings.`);
251
541
  return void 0;
252
542
  }
@@ -293,7 +583,7 @@ function readAccepts(value, errors) {
293
583
  const ids = /* @__PURE__ */ new Set();
294
584
  value.forEach((entry, index) => {
295
585
  const label = `App definition accepts[${index}]`;
296
- if (!isPlainObject(entry)) {
586
+ if (!isPlainObject2(entry)) {
297
587
  errors.push(`${label} must be an object.`);
298
588
  return;
299
589
  }
@@ -354,7 +644,7 @@ var COMPONENT_FIELD_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
354
644
  var COMPONENT_INTENTS = ["create", "edit", "view"];
355
645
  var COMPONENT_CREATE_MAX_BYTES = 16 * 1024;
356
646
  function readComponentFieldMatch(value, label, errors) {
357
- if (!isPlainObject(value)) {
647
+ if (!isPlainObject2(value)) {
358
648
  errors.push(`${label} must be an object of field values.`);
359
649
  return void 0;
360
650
  }
@@ -377,6 +667,28 @@ var MINDOODB_APP_COMPONENT_CONTRACT_PATTERN = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]
377
667
  var COMPONENT_CONTRACTS_MAX = 8;
378
668
  var COMPONENT_REFERENCES_MAX = 8;
379
669
  var COMPONENT_REFERENCE_SHOW_MAX = 8;
670
+ var COMPONENT_SUPPORTS_MAX = 16;
671
+ function readComponentSupports(value, label, errors) {
672
+ if (value === void 0) {
673
+ return void 0;
674
+ }
675
+ if (!Array.isArray(value) || value.length > COMPONENT_SUPPORTS_MAX) {
676
+ errors.push(`${label} must be an array of at most ${COMPONENT_SUPPORTS_MAX} entries.`);
677
+ return void 0;
678
+ }
679
+ const result = [];
680
+ for (const entry of value) {
681
+ const text = typeof entry === "string" ? entry.trim() : "";
682
+ if (!isMindooDBAppSupportsEntry(text, MINDOODB_APP_COMPONENT_CONTRACT_PATTERN)) {
683
+ errors.push(`${label} entries must look like "validate" or "inspect:mindoo.inspect.document@1", received ${JSON.stringify(entry)}.`);
684
+ return void 0;
685
+ }
686
+ if (!result.includes(text)) {
687
+ result.push(text);
688
+ }
689
+ }
690
+ return result.length ? result : void 0;
691
+ }
380
692
  function readComponentContracts(value, label, errors) {
381
693
  if (value === void 0) {
382
694
  return void 0;
@@ -387,14 +699,14 @@ function readComponentContracts(value, label, errors) {
387
699
  }
388
700
  const result = [];
389
701
  for (const [index, entry] of value.entries()) {
390
- const contract = isPlainObject(entry) && typeof entry.contract === "string" ? entry.contract.trim() : "";
702
+ const contract = isPlainObject2(entry) && typeof entry.contract === "string" ? entry.contract.trim() : "";
391
703
  if (!MINDOODB_APP_COMPONENT_CONTRACT_PATTERN.test(contract)) {
392
704
  errors.push(`${label}[${index}].contract must look like "mindoo.contacts.company@1".`);
393
705
  return void 0;
394
706
  }
395
707
  let fields;
396
- if (isPlainObject(entry) && entry.fields !== void 0) {
397
- if (!isPlainObject(entry.fields)) {
708
+ if (isPlainObject2(entry) && entry.fields !== void 0) {
709
+ if (!isPlainObject2(entry.fields)) {
398
710
  errors.push(`${label}[${index}].fields must map contract fields to document fields.`);
399
711
  return void 0;
400
712
  }
@@ -423,7 +735,7 @@ function readComponentReferences(value, label, rootMatch, errors) {
423
735
  const fieldsSeen = /* @__PURE__ */ new Set();
424
736
  for (const [index, entry] of value.entries()) {
425
737
  const at = `${label}[${index}]`;
426
- if (!isPlainObject(entry)) {
738
+ if (!isPlainObject2(entry)) {
427
739
  errors.push(`${at} must be an object.`);
428
740
  return void 0;
429
741
  }
@@ -472,7 +784,7 @@ function readComponents(value, errors) {
472
784
  value.forEach((entry, index) => {
473
785
  const label = `App definition components[${index}]`;
474
786
  const errorCount = errors.length;
475
- if (!isPlainObject(entry)) {
787
+ if (!isPlainObject2(entry)) {
476
788
  errors.push(`${label} must be an object.`);
477
789
  return;
478
790
  }
@@ -519,7 +831,7 @@ function readComponents(value, errors) {
519
831
  }
520
832
  let create;
521
833
  if (entry.create !== void 0) {
522
- if (!isPlainObject(entry.create)) {
834
+ if (!isPlainObject2(entry.create)) {
523
835
  errors.push(`${label}.create must be an object of initial field values.`);
524
836
  return;
525
837
  }
@@ -548,7 +860,7 @@ function readComponents(value, errors) {
548
860
  }
549
861
  let children;
550
862
  if (entry.children !== void 0) {
551
- if (!isPlainObject(entry.children)) {
863
+ if (!isPlainObject2(entry.children)) {
552
864
  errors.push(`${label}.children must be an object.`);
553
865
  return;
554
866
  }
@@ -577,6 +889,7 @@ function readComponents(value, errors) {
577
889
  }
578
890
  const provides = readComponentContracts(entry.provides, `${label}.provides`, errors);
579
891
  const references = readComponentReferences(entry.references, `${label}.references`, match, errors);
892
+ const supports = readComponentSupports(entry.supports, `${label}.supports`, errors);
580
893
  if (errors.length !== errorCount) {
581
894
  return;
582
895
  }
@@ -591,7 +904,8 @@ function readComponents(value, errors) {
591
904
  ...children ? { children } : {},
592
905
  ...category ? { category } : {},
593
906
  ...provides ? { provides } : {},
594
- ...references ? { references } : {}
907
+ ...references ? { references } : {},
908
+ ...supports ? { supports } : {}
595
909
  });
596
910
  });
597
911
  return result.length ? result : void 0;
@@ -631,7 +945,7 @@ function readListing(value, errors) {
631
945
  if (value === void 0 || value === null) {
632
946
  return void 0;
633
947
  }
634
- if (!isPlainObject(value)) {
948
+ if (!isPlainObject2(value)) {
635
949
  errors.push("App definition listing must be an object.");
636
950
  return void 0;
637
951
  }
@@ -671,7 +985,7 @@ function readListing(value, errors) {
671
985
  value.screenshots.forEach((entry, index) => {
672
986
  const label = `App definition listing.screenshots[${index}]`;
673
987
  const raw = typeof entry === "string" ? { file: entry } : entry;
674
- if (!isPlainObject(raw)) {
988
+ if (!isPlainObject2(raw)) {
675
989
  errors.push(`${label} must be a path or an object with a file.`);
676
990
  return;
677
991
  }
@@ -686,7 +1000,7 @@ function readListing(value, errors) {
686
1000
  }
687
1001
  }
688
1002
  if (value.publisher !== void 0 && value.publisher !== null) {
689
- const publisher = isPlainObject(value.publisher) ? value.publisher : {};
1003
+ const publisher = isPlainObject2(value.publisher) ? value.publisher : {};
690
1004
  const name = typeof publisher.name === "string" ? publisher.name.trim() : "";
691
1005
  if (!name) {
692
1006
  errors.push("App definition listing.publisher must be an object with a non-empty name.");
@@ -705,7 +1019,7 @@ var AGENT_CAPABILITY_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,47}$/;
705
1019
  var AGENT_LOCALE_PATTERN = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
706
1020
  var AGENT_GUIDE_PATH_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_.\/-]{0,199}\.md$/;
707
1021
  function readAgentLocaleText(value, label, maxLength, errors) {
708
- if (!isPlainObject(value)) {
1022
+ if (!isPlainObject2(value)) {
709
1023
  errors.push(`${label} must be an object of per-locale strings with an "en" entry.`);
710
1024
  return void 0;
711
1025
  }
@@ -736,7 +1050,7 @@ function readAgentLocaleList(value, label, maxLength, errors) {
736
1050
  if (value === void 0) {
737
1051
  return void 0;
738
1052
  }
739
- if (!isPlainObject(value)) {
1053
+ if (!isPlainObject2(value)) {
740
1054
  errors.push(`${label} must be an object of per-locale string arrays with an "en" entry.`);
741
1055
  return void 0;
742
1056
  }
@@ -777,7 +1091,7 @@ function readAgentCapabilities(value, errors) {
777
1091
  const ids = /* @__PURE__ */ new Set();
778
1092
  for (const [index, entry] of value.entries()) {
779
1093
  const at = `${label}[${index}]`;
780
- if (!isPlainObject(entry)) {
1094
+ if (!isPlainObject2(entry)) {
781
1095
  errors.push(`${at} must be an object.`);
782
1096
  return void 0;
783
1097
  }
@@ -820,7 +1134,7 @@ function readAgentProfile(value, errors) {
820
1134
  if (value === void 0 || value === null) {
821
1135
  return void 0;
822
1136
  }
823
- if (!isPlainObject(value)) {
1137
+ if (!isPlainObject2(value)) {
824
1138
  errors.push("App definition agent must be an object.");
825
1139
  return void 0;
826
1140
  }
@@ -883,7 +1197,7 @@ function resolveMindooDBAppListingAssetUrl(path, originUrl) {
883
1197
  }
884
1198
  function validateMindooDBAppDefinition(raw) {
885
1199
  const errors = [];
886
- if (!isPlainObject(raw)) {
1200
+ if (!isPlainObject2(raw)) {
887
1201
  return { definition: null, errors: ["App definition must be a JSON object."] };
888
1202
  }
889
1203
  if (raw.format !== MINDOODB_APP_DEFINITION_FORMAT) {
@@ -1397,29 +1711,29 @@ function bindDragSource(element, options, start, follow) {
1397
1711
  }
1398
1712
 
1399
1713
  // src/client/agentInput.ts
1400
- function isPlainObject2(value) {
1714
+ function isPlainObject3(value) {
1401
1715
  return typeof value === "object" && value !== null && !Array.isArray(value);
1402
1716
  }
1403
1717
  function isPlaceholder(value) {
1404
1718
  if (value === null || value === void 0) return true;
1405
1719
  if (typeof value === "string") return value.trim() === "";
1406
1720
  if (Array.isArray(value)) return value.length === 0;
1407
- if (isPlainObject2(value)) return Object.keys(value).length === 0;
1721
+ if (isPlainObject3(value)) return Object.keys(value).length === 0;
1408
1722
  return false;
1409
1723
  }
1410
1724
  function normalizeValue(value, schema) {
1411
- if (!isPlainObject2(schema)) return value;
1725
+ if (!isPlainObject3(schema)) return value;
1412
1726
  if (Array.isArray(value)) {
1413
1727
  const items = schema.items;
1414
- return isPlainObject2(items) ? value.map((item) => normalizeValue(item, items)) : value;
1728
+ return isPlainObject3(items) ? value.map((item) => normalizeValue(item, items)) : value;
1415
1729
  }
1416
- if (isPlainObject2(value) && isPlainObject2(schema.properties)) {
1730
+ if (isPlainObject3(value) && isPlainObject3(schema.properties)) {
1417
1731
  return normalizeObject(value, schema);
1418
1732
  }
1419
1733
  return value;
1420
1734
  }
1421
1735
  function normalizeObject(input, schema) {
1422
- const properties = isPlainObject2(schema.properties) ? schema.properties : {};
1736
+ const properties = isPlainObject3(schema.properties) ? schema.properties : {};
1423
1737
  const required = new Set(Array.isArray(schema.required) ? schema.required.filter((key) => typeof key === "string") : []);
1424
1738
  const result = {};
1425
1739
  for (const [key, raw] of Object.entries(input)) {
@@ -1434,7 +1748,7 @@ function normalizeObject(input, schema) {
1434
1748
  return result;
1435
1749
  }
1436
1750
  function normalizeAgentToolInput(input, schema) {
1437
- return isPlainObject2(input) ? normalizeObject(input, schema) : input;
1751
+ return isPlainObject3(input) ? normalizeObject(input, schema) : input;
1438
1752
  }
1439
1753
 
1440
1754
  // src/client/agentTools.ts
@@ -1651,6 +1965,15 @@ import { parseMindooDBFormulaBooleanExpression } from "mindoodb-view-language";
1651
1965
  function isEmbedEventMessage(message) {
1652
1966
  return message.kind === "embed-event";
1653
1967
  }
1968
+ function isEmbedRequestMessage(message) {
1969
+ return message.kind === "embed-request";
1970
+ }
1971
+ function isMethodNotFound(error) {
1972
+ return error instanceof Error && error.name === "method-not-found";
1973
+ }
1974
+ function errorMessage(error) {
1975
+ return error instanceof Error ? error.message : String(error);
1976
+ }
1654
1977
  function isEmbeddingRequestMessage(message) {
1655
1978
  return message.kind === "embedding-request";
1656
1979
  }
@@ -1717,13 +2040,14 @@ function createComponentFrame(container, url, allow) {
1717
2040
  return frame;
1718
2041
  }
1719
2042
  var MindooDBAppEmbedImpl = class {
1720
- constructor(rpc, embedId, componentKey, databaseId, docId, frame, container) {
2043
+ constructor(rpc, embedId, componentKey, databaseId, docId, frame, container, supports = []) {
1721
2044
  this.rpc = rpc;
1722
2045
  this.embedId = embedId;
1723
2046
  this.componentKey = componentKey;
1724
2047
  this.databaseId = databaseId;
1725
2048
  this.docId = docId;
1726
2049
  this.frame = frame;
2050
+ this.supports = supports;
1727
2051
  this.closed = new Promise((resolve) => {
1728
2052
  this.resolveClosed = resolve;
1729
2053
  });
@@ -1740,6 +2064,7 @@ var MindooDBAppEmbedImpl = class {
1740
2064
  databaseId;
1741
2065
  docId;
1742
2066
  frame;
2067
+ supports;
1743
2068
  closed;
1744
2069
  resolveClosed;
1745
2070
  closedEvent = null;
@@ -1749,6 +2074,8 @@ var MindooDBAppEmbedImpl = class {
1749
2074
  tracker = null;
1750
2075
  toolsState = null;
1751
2076
  toolsListeners = /* @__PURE__ */ new Set();
2077
+ beforeSaveHandler = null;
2078
+ savedListeners = /* @__PURE__ */ new Set();
1752
2079
  get placement() {
1753
2080
  return this.frame ? "frame" : "overlay";
1754
2081
  }
@@ -1831,6 +2158,88 @@ var MindooDBAppEmbedImpl = class {
1831
2158
  const stopClosed = this.onClosed(() => finish(null));
1832
2159
  });
1833
2160
  }
2161
+ async inspect(request) {
2162
+ const result = await this.rpc.call("embeds.inspect", {
2163
+ embedId: this.embedId,
2164
+ request: normalizeMindooDBAppInspectRequest(request)
2165
+ });
2166
+ return normalizeMindooDBAppInspection(result);
2167
+ }
2168
+ onBeforeSave(handler, options) {
2169
+ this.beforeSaveHandler = handler;
2170
+ this.announceBeforeSave(true, options?.inspect);
2171
+ return () => {
2172
+ if (this.beforeSaveHandler === handler) {
2173
+ this.beforeSaveHandler = null;
2174
+ this.announceBeforeSave(false);
2175
+ }
2176
+ };
2177
+ }
2178
+ /** Haven only asks hosts that said they check, and fetches the inspection they want. */
2179
+ announceBeforeSave(enabled, inspect) {
2180
+ if (this.closedEvent) {
2181
+ return;
2182
+ }
2183
+ void this.rpc.call("embeds.setBeforeSave", {
2184
+ embedId: this.embedId,
2185
+ enabled,
2186
+ ...enabled ? { inspect: normalizeMindooDBAppInspectRequest(inspect) } : {}
2187
+ }).catch((error) => {
2188
+ if (!isMethodNotFound(error)) {
2189
+ console.warn("[mindoodb-app-sdk] Could not register the save check.", error);
2190
+ }
2191
+ });
2192
+ }
2193
+ /** Haven asks before the component saves: run the handler, never longer than Haven waits. */
2194
+ async answerBeforeSave(params) {
2195
+ const handler = this.beforeSaveHandler;
2196
+ if (!handler || this.closedEvent) {
2197
+ return { issues: [] };
2198
+ }
2199
+ const result = await handler({
2200
+ embedId: this.embedId,
2201
+ inspection: params?.inspection ?? null,
2202
+ root: params?.root ?? null
2203
+ });
2204
+ return { issues: normalizeMindooDBAppIssues(result?.issues) };
2205
+ }
2206
+ onAfterSave(listener) {
2207
+ this.savedListeners.add(listener);
2208
+ return () => {
2209
+ this.savedListeners.delete(listener);
2210
+ };
2211
+ }
2212
+ /** The component reported that it saved. */
2213
+ notifySaved() {
2214
+ if (this.closedEvent) {
2215
+ return;
2216
+ }
2217
+ const event = { embedId: this.embedId };
2218
+ this.savedListeners.forEach((listener) => {
2219
+ try {
2220
+ listener(event);
2221
+ } catch (error) {
2222
+ console.error("MindooDB embed saved listener failed.", error);
2223
+ }
2224
+ });
2225
+ }
2226
+ async showIssues(issues) {
2227
+ await this.rpc.call("embeds.showIssues", { embedId: this.embedId, issues: normalizeMindooDBAppIssues(issues) });
2228
+ }
2229
+ async reveal(anchor) {
2230
+ const normalized = normalizeMindooDBAppInspectAnchor(anchor);
2231
+ if (!normalized) {
2232
+ throw new Error("Invalid anchor.");
2233
+ }
2234
+ await this.rpc.call("embeds.reveal", { embedId: this.embedId, anchor: normalized });
2235
+ }
2236
+ async callTool(name, input) {
2237
+ return await this.rpc.call("embeds.callTool", {
2238
+ embedId: this.embedId,
2239
+ name: String(name),
2240
+ input: input === void 0 ? {} : JSON.parse(JSON.stringify(input))
2241
+ });
2242
+ }
1834
2243
  async setRect(rect) {
1835
2244
  if (this.closedEvent) {
1836
2245
  return;
@@ -1885,6 +2294,8 @@ var MindooDBAppEmbedImpl = class {
1885
2294
  });
1886
2295
  this.listeners.clear();
1887
2296
  this.dirtyListeners.clear();
2297
+ this.savedListeners.clear();
2298
+ this.beforeSaveHandler = null;
1888
2299
  this.toolsState = null;
1889
2300
  this.toolsListeners.forEach((listener) => {
1890
2301
  try {
@@ -1904,6 +2315,10 @@ var MindooDBAppEmbedsClient = class {
1904
2315
  void this.answerRequest(message);
1905
2316
  return;
1906
2317
  }
2318
+ if (isEmbedRequestMessage(message)) {
2319
+ void this.answerEmbedRequest(message);
2320
+ return;
2321
+ }
1907
2322
  if (!isEmbedEventMessage(message)) {
1908
2323
  return;
1909
2324
  }
@@ -1927,6 +2342,10 @@ var MindooDBAppEmbedsClient = class {
1927
2342
  }
1928
2343
  return;
1929
2344
  }
2345
+ if (message.event?.type === "saved") {
2346
+ this.open.get(message.event.embedId)?.notifySaved();
2347
+ return;
2348
+ }
1930
2349
  if (message.event?.type !== "closed") {
1931
2350
  return;
1932
2351
  }
@@ -1964,7 +2383,8 @@ var MindooDBAppEmbedsClient = class {
1964
2383
  input.databaseId,
1965
2384
  input.docId,
1966
2385
  frame,
1967
- container
2386
+ container,
2387
+ Array.isArray(response.supports) ? response.supports.filter((entry) => typeof entry === "string") : []
1968
2388
  );
1969
2389
  const earlyDirty = this.earlyDirty.get(embed.embedId);
1970
2390
  if (earlyDirty !== void 0) {
@@ -2040,7 +2460,45 @@ var MindooDBAppEmbedsClient = class {
2040
2460
  this.saveHandler = null;
2041
2461
  }
2042
2462
  };
2043
- }
2463
+ },
2464
+ provideInspection: (provider) => {
2465
+ this.inspectionProvider = provider;
2466
+ return () => {
2467
+ if (this.inspectionProvider === provider) {
2468
+ this.inspectionProvider = null;
2469
+ }
2470
+ };
2471
+ },
2472
+ onShowIssues: (handler) => {
2473
+ this.showIssuesHandler = handler;
2474
+ return () => {
2475
+ if (this.showIssuesHandler === handler) {
2476
+ this.showIssuesHandler = null;
2477
+ }
2478
+ };
2479
+ },
2480
+ onReveal: (handler) => {
2481
+ this.revealHandler = handler;
2482
+ return () => {
2483
+ if (this.revealHandler === handler) {
2484
+ this.revealHandler = null;
2485
+ }
2486
+ };
2487
+ },
2488
+ validate: async (options) => await this.validate(options),
2489
+ save: async (flush, options) => await performMindooDBAppEmbeddingSave({
2490
+ validate: () => this.validate(options),
2491
+ provider: this.inspectionProvider,
2492
+ showIssues: this.showIssuesHandler,
2493
+ flush,
2494
+ // Only news for the host: a lost message must not fail a save that happened.
2495
+ saved: () => this.rpc.call("embedding.saved", {}).then(
2496
+ () => {
2497
+ },
2498
+ () => {
2499
+ }
2500
+ )
2501
+ })
2044
2502
  };
2045
2503
  }
2046
2504
  rpc;
@@ -2055,18 +2513,53 @@ var MindooDBAppEmbedsClient = class {
2055
2513
  stopListening;
2056
2514
  /** This app as a component: what to do when its host asks it to save. */
2057
2515
  saveHandler = null;
2058
- /** Runs the save handler and tells Haven how it went; Haven answers the host. */
2516
+ inspectionProvider = null;
2517
+ showIssuesHandler = null;
2518
+ revealHandler = null;
2519
+ /** `embedding.validate`; ok when Haven cannot check (not embedded, older Haven). */
2520
+ async validate(options) {
2521
+ try {
2522
+ const result = await this.rpc.call("embedding.validate", {
2523
+ interactive: options?.interactive !== false
2524
+ });
2525
+ return {
2526
+ ok: result?.ok !== false,
2527
+ issues: normalizeMindooDBAppIssues(result?.issues),
2528
+ ...result?.timedOut ? { timedOut: true } : {}
2529
+ };
2530
+ } catch (error) {
2531
+ if (isMethodNotFound(error) || error instanceof Error && error.name === "unsupported") {
2532
+ return { ok: true, issues: [] };
2533
+ }
2534
+ throw error;
2535
+ }
2536
+ }
2537
+ /** Runs the matching handler and tells Haven how it went; Haven answers the host. */
2059
2538
  async answerRequest(message) {
2060
2539
  let response;
2061
- if (message.request !== "save" || !this.saveHandler) {
2062
- response = { ok: false, error: "unsupported" };
2063
- } else {
2064
- try {
2540
+ try {
2541
+ const params = message.params ?? {};
2542
+ if (message.request === "save" && this.saveHandler) {
2065
2543
  await this.saveHandler();
2066
2544
  response = { ok: true };
2067
- } catch (error) {
2068
- response = { ok: false, error: error instanceof Error ? error.message : String(error) };
2545
+ } else if (message.request === "inspect" && this.inspectionProvider) {
2546
+ const inspection = await this.inspectionProvider(normalizeMindooDBAppInspectRequest(params.request));
2547
+ response = { ok: true, result: normalizeMindooDBAppInspection(inspection) };
2548
+ } else if (message.request === "showIssues" && this.showIssuesHandler) {
2549
+ await this.showIssuesHandler(normalizeMindooDBAppIssues(params.issues));
2550
+ response = { ok: true };
2551
+ } else if (message.request === "reveal" && this.revealHandler) {
2552
+ const anchor = normalizeMindooDBAppInspectAnchor(params.anchor);
2553
+ if (!anchor) {
2554
+ throw new Error("Invalid anchor.");
2555
+ }
2556
+ await this.revealHandler(anchor);
2557
+ response = { ok: true };
2558
+ } else {
2559
+ response = { ok: false, error: "unsupported" };
2069
2560
  }
2561
+ } catch (error) {
2562
+ response = { ok: false, error: errorMessage(error) };
2070
2563
  }
2071
2564
  try {
2072
2565
  await this.rpc.call("embedding.respond", { requestId: message.requestId, ...response });
@@ -2074,6 +2567,24 @@ var MindooDBAppEmbedsClient = class {
2074
2567
  console.warn("[mindoodb-app-sdk] Could not answer the host's request.", error);
2075
2568
  }
2076
2569
  }
2570
+ /** This app as a host: Haven asks before one of its components saves. */
2571
+ async answerEmbedRequest(message) {
2572
+ let response;
2573
+ const embed = this.open.get(message.embedId);
2574
+ try {
2575
+ response = message.request === "beforeSave" ? { ok: true, result: embed ? await embed.answerBeforeSave(message.params) : { issues: [] } } : { ok: false, error: "unsupported" };
2576
+ } catch (error) {
2577
+ response = {
2578
+ ok: true,
2579
+ result: { issues: [{ severity: "warning", message: `The check failed: ${errorMessage(error)}`, code: "check-failed" }] }
2580
+ };
2581
+ }
2582
+ try {
2583
+ await this.rpc.call("embeds.respond", { requestId: message.requestId, ...response });
2584
+ } catch (error) {
2585
+ console.warn("[mindoodb-app-sdk] Could not answer Haven's request.", error);
2586
+ }
2587
+ }
2077
2588
  dispose() {
2078
2589
  this.stopListening();
2079
2590
  }
@@ -3907,6 +4418,27 @@ export {
3907
4418
  isValidMindooDBDocumentId,
3908
4419
  mindooDBDocumentId,
3909
4420
  assertValidMindooDBCreateIds,
4421
+ MINDOODB_INSPECT_DOCUMENT,
4422
+ MINDOODB_INSPECT_SPREADSHEET,
4423
+ MINDOODB_INSPECT_SLIDES,
4424
+ MINDOODB_APP_INSPECTION_FIELD,
4425
+ MINDOODB_APP_INSPECTION_SUMMARY_MAX_BYTES,
4426
+ MINDOODB_APP_INSPECTION_MAX_BYTES,
4427
+ MINDOODB_APP_ISSUES_MAX,
4428
+ MindooDBAppEmbedValidationError,
4429
+ mindooDBAppComponentSupports,
4430
+ mindooDBAppInspectContracts,
4431
+ normalizeMindooDBAppInspectAnchor,
4432
+ normalizeMindooDBAppIssues,
4433
+ normalizeMindooDBAppInspectRequest,
4434
+ normalizeMindooDBAppInspection,
4435
+ summarizeMindooDBAppInspection,
4436
+ mindooDBAppInspectionSummaryValue,
4437
+ readMindooDBAppInspectionSummary,
4438
+ defineDocumentRules,
4439
+ mindooDBAppInspectRules,
4440
+ performMindooDBAppEmbeddingSave,
4441
+ resolveMindooDBAppEmbedValidation,
3910
4442
  MINDOODB_APP_DEFINITION_FORMAT,
3911
4443
  MINDOODB_APP_DEFINITION_VERSION,
3912
4444
  MINDOODB_APP_DEFINITION_FILE_NAME,