mindoodb-app-sdk 0.0.65 → 0.0.66

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.
@@ -4,7 +4,7 @@ import {
4
4
  mindooDBAppComponentFieldsMatch,
5
5
  pageUsers,
6
6
  releaseMindooDBAppBridgeSessions
7
- } from "../chunk-BE7LLFFE.js";
7
+ } from "../chunk-B3K3I2S2.js";
8
8
 
9
9
  // src/testing/index.ts
10
10
  import * as Automerge from "@automerge/automerge";
@@ -464,6 +464,8 @@ function createMockEmbedHost(options, getDatabase) {
464
464
  const handles = /* @__PURE__ */ new Map();
465
465
  const closedListeners = /* @__PURE__ */ new Set();
466
466
  const dirtyListeners = /* @__PURE__ */ new Set();
467
+ const toolListeners = /* @__PURE__ */ new Set();
468
+ const toolNames = /* @__PURE__ */ new Map();
467
469
  let nextSaveError = null;
468
470
  let embeddingDirty;
469
471
  let saveRequestHandler = null;
@@ -504,11 +506,49 @@ function createMockEmbedHost(options, getDatabase) {
504
506
  docId: input.docId,
505
507
  intent,
506
508
  rect: mockRect(input),
507
- visible: input.visible !== false
509
+ visible: input.visible !== false,
510
+ active: false,
511
+ agentTools: null
508
512
  });
509
- notifyChange();
513
+ setActive(embedId);
510
514
  return embedId;
511
515
  }
516
+ function toolPrefix(component) {
517
+ return component.appId.replace(/^mindoodb-app-/, "").replace(/[^a-z0-9_]/g, "_") || "app";
518
+ }
519
+ function publishTools(entry) {
520
+ const declared = toolNames.get(entry.embedId);
521
+ const prefix = toolPrefix(entry.component);
522
+ const next = declared?.names.length ? { prefix, names: declared.names.map((name) => `${prefix}_${name}`), active: entry.active, enabled: declared.enabled } : null;
523
+ if (JSON.stringify(next) === JSON.stringify(entry.agentTools)) {
524
+ return;
525
+ }
526
+ entry.agentTools = next;
527
+ handles.get(entry.embedId)?.setAgentToolsState(next);
528
+ toolListeners.forEach((listener) => listener({ embedId: entry.embedId, agentTools: next }));
529
+ }
530
+ function setActive(embedId) {
531
+ const target = embeds.get(embedId);
532
+ if (!target) {
533
+ return;
534
+ }
535
+ for (const entry of embeds.values()) {
536
+ if (entry.component.appId === target.component.appId) {
537
+ entry.active = entry.embedId === embedId;
538
+ publishTools(entry);
539
+ }
540
+ }
541
+ notifyChange();
542
+ }
543
+ function setEmbedAgentTools(embedId, names, toolOptions = {}) {
544
+ const entry = embeds.get(embedId);
545
+ if (!entry) {
546
+ return;
547
+ }
548
+ toolNames.set(embedId, { names: [...names ?? []], enabled: toolOptions.enabled !== false });
549
+ publishTools(entry);
550
+ notifyChange();
551
+ }
512
552
  function closeEmbed(embedId, reason = "closed", result) {
513
553
  if (!embeds.delete(embedId)) {
514
554
  return;
@@ -520,6 +560,7 @@ function createMockEmbedHost(options, getDatabase) {
520
560
  };
521
561
  handles.get(embedId)?.settle(event);
522
562
  handles.delete(embedId);
563
+ toolNames.delete(embedId);
523
564
  closedListeners.forEach((listener) => listener(event));
524
565
  notifyChange();
525
566
  }
@@ -558,6 +599,39 @@ function createMockEmbedHost(options, getDatabase) {
558
599
  async save() {
559
600
  await saveEmbed(this.embedId);
560
601
  }
602
+ agentTools = null;
603
+ toolsListeners = /* @__PURE__ */ new Set();
604
+ async setActive() {
605
+ setActive(this.embedId);
606
+ }
607
+ onAgentToolsChange(listener) {
608
+ this.toolsListeners.add(listener);
609
+ return () => {
610
+ this.toolsListeners.delete(listener);
611
+ };
612
+ }
613
+ setAgentToolsState(tools) {
614
+ this.agentTools = tools;
615
+ this.toolsListeners.forEach((listener) => listener(tools));
616
+ }
617
+ waitForAgentTools(timeoutMs = 1e4) {
618
+ if (this.agentTools?.names.length) {
619
+ return Promise.resolve(this.agentTools);
620
+ }
621
+ return new Promise((resolve) => {
622
+ const timer = setTimeout(() => {
623
+ stop();
624
+ resolve(null);
625
+ }, timeoutMs);
626
+ const stop = this.onAgentToolsChange((tools) => {
627
+ if (tools?.names.length) {
628
+ clearTimeout(timer);
629
+ stop();
630
+ resolve(tools);
631
+ }
632
+ });
633
+ });
634
+ }
561
635
  resolve;
562
636
  listeners = /* @__PURE__ */ new Set();
563
637
  settled = null;
@@ -652,6 +726,9 @@ function createMockEmbedHost(options, getDatabase) {
652
726
  async setDirty(dirty) {
653
727
  embeddingDirty = dirty === true;
654
728
  },
729
+ async lookup(field2, lookupOptions) {
730
+ return structuredClone(await (options.embeddingLookup?.(field2, { ...lookupOptions }) ?? []));
731
+ },
655
732
  onSaveRequest(handler) {
656
733
  saveRequestHandler = handler;
657
734
  return () => {
@@ -684,6 +761,14 @@ function createMockEmbedHost(options, getDatabase) {
684
761
  };
685
762
  },
686
763
  setEmbedDirty,
764
+ setActive,
765
+ setEmbedAgentTools,
766
+ onAgentTools(listener) {
767
+ toolListeners.add(listener);
768
+ return () => {
769
+ toolListeners.delete(listener);
770
+ };
771
+ },
687
772
  onDirty(listener) {
688
773
  dirtyListeners.add(listener);
689
774
  return () => {
@@ -828,6 +913,44 @@ function guardDatabase(database, currentInfo, enforce) {
828
913
  };
829
914
  }
830
915
 
916
+ // src/testing/configuredViews.ts
917
+ var field = (path) => ({ kind: "field", path });
918
+ var literal = (value) => ({ kind: "literal", value });
919
+ var operation = (op, args) => ({ kind: "operation", op, args });
920
+ function ruleExpression(rule) {
921
+ const target = field(rule.field);
922
+ switch (rule.operator) {
923
+ case "notContains":
924
+ return operation("not", [operation("contains", [target, literal(rule.value ?? "")])]);
925
+ case "exists":
926
+ case "notExists":
927
+ return operation(rule.operator, [target]);
928
+ default:
929
+ return operation(rule.operator, [target, literal(rule.value ?? "")]);
930
+ }
931
+ }
932
+ function configuredViewDefinition(view2) {
933
+ const filter = view2.filter.mode === "formula" ? { mode: "expression", expression: structuredClone(view2.filter.expression) } : view2.filter.rules.length ? {
934
+ mode: "expression",
935
+ expression: view2.filter.rules.length === 1 ? ruleExpression(view2.filter.rules[0]) : operation(view2.filter.match === "all" ? "and" : "or", view2.filter.rules.map(ruleExpression))
936
+ } : void 0;
937
+ return {
938
+ id: view2.id,
939
+ title: view2.description?.trim() || view2.id,
940
+ ...filter ? { filter } : {},
941
+ defaultExpand: "collapsed",
942
+ columns: view2.columns.map((column) => ({
943
+ name: column.name,
944
+ title: column.title,
945
+ role: column.role === "sort" ? "display" : column.role,
946
+ expression: column.expression.mode === "field" ? field(column.expression.field) : structuredClone(column.expression.expression),
947
+ sorting: column.sorting,
948
+ hidden: column.hidden,
949
+ totalMode: column.totalMode
950
+ }))
951
+ };
952
+ }
953
+
831
954
  // src/testing/mockAgentTools.ts
832
955
  var TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{0,47}$/;
833
956
  var PREFIX_PATTERN = /^[a-z][a-z0-9_]{0,15}$/;
@@ -877,11 +1000,15 @@ function validateMockAgentTools(raw) {
877
1000
  throw invalid(`Agent tool "${tool.name}" has invalid annotations.`);
878
1001
  }
879
1002
  }
1003
+ if (tool.scope !== void 0 && tool.scope !== "app" && tool.scope !== "document") {
1004
+ throw invalid(`Agent tool "${tool.name}" has an invalid scope; use "app" or "document".`);
1005
+ }
880
1006
  return {
881
1007
  name: tool.name,
882
1008
  description: tool.description,
883
1009
  inputSchema: schema,
884
- ...tool.annotations ? { annotations: { ...tool.annotations } } : {}
1010
+ ...tool.annotations ? { annotations: { ...tool.annotations } } : {},
1011
+ ...tool.scope ? { scope: tool.scope } : {}
885
1012
  };
886
1013
  });
887
1014
  }
@@ -934,7 +1061,7 @@ function createMockAgentHost(options) {
934
1061
  async function dispatch(method, params) {
935
1062
  const input = params ?? {};
936
1063
  if (method === "agent.registerTools") {
937
- tools = validateMockAgentTools(input.tools);
1064
+ tools = validateMockAgentTools(input.tools).filter((tool) => !options.embedded?.() || tool.scope === "document");
938
1065
  changed();
939
1066
  return { result: registration() };
940
1067
  }
@@ -1078,6 +1205,10 @@ function concat(chunks) {
1078
1205
  }
1079
1206
  return bytes;
1080
1207
  }
1208
+ var MEMORY_ATTACHMENTS = /* @__PURE__ */ Symbol.for("mindoodb-app-sdk.memoryAttachments");
1209
+ function isMemoryAttachments(value) {
1210
+ return !!value && typeof value === "object" && MEMORY_ATTACHMENTS in value;
1211
+ }
1081
1212
  function createMemoryAttachments() {
1082
1213
  const files = /* @__PURE__ */ new Map();
1083
1214
  const forDoc = (docId) => {
@@ -1085,7 +1216,7 @@ function createMemoryAttachments() {
1085
1216
  if (!map) files.set(docId, map = /* @__PURE__ */ new Map());
1086
1217
  return map;
1087
1218
  };
1088
- return {
1219
+ const api = {
1089
1220
  async list(docId) {
1090
1221
  return [...forDoc(docId)].map(
1091
1222
  ([fileName, file]) => ({
@@ -1132,6 +1263,131 @@ function createMemoryAttachments() {
1132
1263
  };
1133
1264
  }
1134
1265
  };
1266
+ return Object.assign(api, { [MEMORY_ATTACHMENTS]: true });
1267
+ }
1268
+
1269
+ // src/testing/twoUserTestHost.ts
1270
+ var DEFAULT_TEST_HOST_USERS = [
1271
+ { name: "Anna", username: "CN=Anna Test/O=Test" },
1272
+ { name: "Ben", username: "CN=Ben Test/O=Test" }
1273
+ ];
1274
+ var STYLE = `
1275
+ .htu{display:grid;grid-template-rows:auto 1fr;height:100vh;margin:0;font:13px/1.45 system-ui,sans-serif;background:#eef1f7;color:#172033}
1276
+ .htu *{box-sizing:border-box}
1277
+ .htu__bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:8px 12px;border-bottom:1px solid #d7dce8;background:#f8f9fc}
1278
+ .htu__badge{padding:2px 8px;border-radius:999px;background:#fde68a;color:#713f12;font-weight:600;font-size:11px;letter-spacing:.04em}
1279
+ .htu button{font:inherit;padding:5px 12px;border-radius:7px;border:1px solid #c9d0e0;background:#fff;cursor:pointer}
1280
+ .htu__status{font-weight:600}
1281
+ .htu .htest__mode{display:flex;gap:8px;align-items:center;padding:0 8px;border-left:1px solid #d7dce8;border-right:1px solid #d7dce8}
1282
+ .htu__panes{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#d7dce8;min-height:0}
1283
+ .htu__pane{display:grid;grid-template-rows:auto 1fr;min-height:0;background:#fff}
1284
+ .htu__pane h2{margin:0;padding:4px 10px;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#5b6478;background:#f8f9fc;border-bottom:1px solid #e4e8f1}
1285
+ .htu iframe{width:100%;height:100%;border:0}
1286
+ @media (max-width:900px){.htu__panes{grid-template-columns:1fr;grid-template-rows:1fr 1fr}}
1287
+ `;
1288
+ function replicaOf(databases) {
1289
+ return databases.map((database) => ({
1290
+ ...database,
1291
+ automerge: true,
1292
+ info: structuredClone(database.info),
1293
+ documents: database.documents ? structuredClone(database.documents) : void 0,
1294
+ ...database.methods ? {
1295
+ methods: {
1296
+ ...database.methods,
1297
+ ...isMemoryAttachments(database.methods.attachments) ? { attachments: createMemoryAttachments() } : {}
1298
+ }
1299
+ } : {}
1300
+ }));
1301
+ }
1302
+ function mountTwoUserTestHost(options, createHost) {
1303
+ const { container = document.body, title = "Haven test host", users = DEFAULT_TEST_HOST_USERS, ...hostOptions } = options;
1304
+ const style = document.createElement("style");
1305
+ style.textContent = STYLE;
1306
+ document.head.append(style);
1307
+ if (container === document.body) document.body.style.margin = "0";
1308
+ const root = document.createElement("div");
1309
+ root.className = "htu";
1310
+ const bar = document.createElement("div");
1311
+ bar.className = "htu__bar";
1312
+ const badge = document.createElement("span");
1313
+ badge.className = "htu__badge";
1314
+ badge.textContent = `TEST HOST \xB7 TWO USERS \xB7 ${title}`;
1315
+ const syncButton = document.createElement("button");
1316
+ syncButton.type = "button";
1317
+ syncButton.textContent = "Sync";
1318
+ syncButton.dataset.testid = "haven-two-users-sync";
1319
+ const auto = document.createElement("label");
1320
+ const autoBox = document.createElement("input");
1321
+ autoBox.type = "checkbox";
1322
+ autoBox.checked = true;
1323
+ autoBox.dataset.testid = "haven-two-users-autosync";
1324
+ auto.append(autoBox, " Auto-sync every 2 s");
1325
+ const status = document.createElement("span");
1326
+ status.className = "htu__status";
1327
+ status.dataset.testid = "haven-two-users-status";
1328
+ bar.append(badge, modeSwitch("two"), syncButton, auto, status);
1329
+ const panes = document.createElement("div");
1330
+ panes.className = "htu__panes";
1331
+ root.append(bar, panes);
1332
+ container.replaceChildren(root);
1333
+ const databasesA = (hostOptions.databases ?? []).map((database) => ({ ...database, automerge: true }));
1334
+ const hosts = users.map((user, index) => {
1335
+ const pane = document.createElement("section");
1336
+ pane.className = "htu__pane";
1337
+ const heading = document.createElement("h2");
1338
+ heading.textContent = `${user.name} \u2014 replica ${index + 1}`;
1339
+ const frame = document.createElement("iframe");
1340
+ frame.title = `${title} as ${user.name}`;
1341
+ frame.dataset.testid = `haven-two-users-frame-${index + 1}`;
1342
+ pane.append(heading, frame);
1343
+ panes.append(pane);
1344
+ const launchContext = {
1345
+ ...hostOptions.launchContext,
1346
+ user: { id: `test-user-${index + 1}`, username: user.username }
1347
+ };
1348
+ return createHost({
1349
+ ...hostOptions,
1350
+ frame,
1351
+ launchContext,
1352
+ databases: index === 0 ? databasesA : replicaOf(databasesA),
1353
+ // distinct Automerge actors: the two replicas write concurrently
1354
+ automergeActor: index === 0 ? "a11ce000000000000000000000000001" : "b0b00000000000000000000000000002"
1355
+ });
1356
+ });
1357
+ let lastSync = null;
1358
+ const describe = () => {
1359
+ const open = mockReplicaDifferences(hosts[0].host, hosts[1].host);
1360
+ status.textContent = `${open ? `\u26A0 ${open} document(s) not synced` : "\u2713 in sync"} \xB7 last sync ${lastSync ?? "never"}`;
1361
+ status.style.color = open ? "#b45309" : "#15803d";
1362
+ };
1363
+ let syncing = null;
1364
+ const sync = () => {
1365
+ syncing ??= syncMockReplicas(hosts[0].host, hosts[1].host).finally(() => {
1366
+ syncing = null;
1367
+ lastSync = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1368
+ describe();
1369
+ });
1370
+ return syncing;
1371
+ };
1372
+ syncButton.addEventListener("click", () => void sync());
1373
+ let timer;
1374
+ const setAutoSync = (on) => {
1375
+ autoBox.checked = on;
1376
+ if (timer) clearInterval(timer);
1377
+ timer = on ? setInterval(() => void sync(), 2e3) : void 0;
1378
+ };
1379
+ autoBox.addEventListener("change", () => setAutoSync(autoBox.checked));
1380
+ setAutoSync(true);
1381
+ setInterval(describe, 500);
1382
+ describe();
1383
+ const handle = {
1384
+ hosts,
1385
+ sync,
1386
+ setAutoSync,
1387
+ differences: () => mockReplicaDifferences(hosts[0].host, hosts[1].host)
1388
+ };
1389
+ window.__havenTwoUsers = handle;
1390
+ return handle;
1135
1391
  }
1136
1392
 
1137
1393
  // src/testing/browserTestHost.ts
@@ -1426,7 +1682,7 @@ var TEST_HOST_LOCALES = [
1426
1682
  { tag: "he-IL", label: "\u05E2\u05D1\u05E8\u05D9\u05EA" }
1427
1683
  ];
1428
1684
  var DIRECTORY_SIZES = [0, 5, 25, 120, 500];
1429
- var STYLE = `
1685
+ var STYLE2 = `
1430
1686
  .htest{display:grid;grid-template-columns:minmax(0,1fr) 320px;height:100vh;margin:0;font:13px/1.45 system-ui,sans-serif;background:#eef1f7;color:#172033}
1431
1687
  .htest *{box-sizing:border-box}
1432
1688
  .htest__frame{width:100%;height:100%;border:0;background:#fff}
@@ -1494,12 +1750,40 @@ function item(text) {
1494
1750
  li.textContent = text;
1495
1751
  return li;
1496
1752
  }
1753
+ function modeSwitch(current) {
1754
+ const row = document.createElement("div");
1755
+ row.className = "htest__row htest__mode";
1756
+ row.dataset.testid = "haven-test-mode";
1757
+ for (const [mode, label] of [
1758
+ ["single", "Single user"],
1759
+ ["two", "Two users"]
1760
+ ]) {
1761
+ const url = new URL(window.location.href);
1762
+ if (mode === "two") url.searchParams.set("twoUsers", "1");
1763
+ else url.searchParams.delete("twoUsers");
1764
+ if (mode === current) {
1765
+ const active = document.createElement("strong");
1766
+ active.textContent = label;
1767
+ active.setAttribute("aria-current", "page");
1768
+ row.append(active);
1769
+ } else {
1770
+ const link = document.createElement("a");
1771
+ link.href = url.toString();
1772
+ link.textContent = label;
1773
+ link.dataset.testid = `haven-test-mode-${mode}`;
1774
+ row.append(link);
1775
+ }
1776
+ }
1777
+ return row;
1778
+ }
1497
1779
  function mountHavenTestHost(options) {
1498
1780
  const {
1499
1781
  container = document.body,
1500
1782
  title = "Haven test host",
1501
1783
  locales = TEST_HOST_LOCALES,
1502
1784
  urlSettings: useUrl = true,
1785
+ twoUsers = false,
1786
+ users,
1503
1787
  ...hostOptions
1504
1788
  } = options;
1505
1789
  const url = useUrl ? readTestHostUrlSettings(window.location.search) : { overrides: {} };
@@ -1512,8 +1796,17 @@ function mountHavenTestHost(options) {
1512
1796
  ...url.locale ? { locale: url.locale } : {},
1513
1797
  ...url.theme ? { theme: { mode: url.theme, preset: "mindoo" } } : {}
1514
1798
  };
1799
+ const twoUsersFromUrl = useUrl && new URLSearchParams(window.location.search).get("twoUsers") === "1";
1800
+ if (twoUsers || twoUsersFromUrl) {
1801
+ const handle = mountTwoUserTestHost(
1802
+ { ...hostOptions, databases, launchContext, container, title, ...users ? { users } : {} },
1803
+ createBrowserTestHost
1804
+ );
1805
+ window.__havenTestHost = handle.hosts[0];
1806
+ return handle.hosts[0];
1807
+ }
1515
1808
  const style = document.createElement("style");
1516
- style.textContent = STYLE;
1809
+ style.textContent = STYLE2;
1517
1810
  document.head.append(style);
1518
1811
  const root = document.createElement("div");
1519
1812
  root.className = "htest";
@@ -1600,8 +1893,8 @@ function mountHavenTestHost(options) {
1600
1893
  if (override.mapped === false) params.append("db", `${id}:none`);
1601
1894
  }
1602
1895
  if (testHost.host.getEnforceCapabilities()) params.set("enforce", "1");
1603
- const users = testHost.host.getDirectoryUsers().length;
1604
- if (users) params.set("users", String(users));
1896
+ const users2 = testHost.host.getDirectoryUsers().length;
1897
+ if (users2) params.set("users", String(users2));
1605
1898
  if (currentLocale) params.set("locale", currentLocale);
1606
1899
  if (initialDark === void 0 || dark !== initialDark) params.set("theme", dark ? "dark" : "light");
1607
1900
  const query = params.toString();
@@ -1731,6 +2024,7 @@ Allow?`),
1731
2024
  }),
1732
2025
  button("Reload app", () => testHost.reloadApp())
1733
2026
  );
2027
+ section("Mode", modeSwitch("single"));
1734
2028
  section("Host", hostRow, focusState);
1735
2029
  const language = document.createElement("select");
1736
2030
  language.dataset.testid = "haven-test-language";
@@ -2083,8 +2377,8 @@ function applyDocumentUpdatePatch(current, patch) {
2083
2377
  applyMockTextPatch(next, textPatch.path, textPatch.edits);
2084
2378
  }
2085
2379
  for (const richTextPatch of patch.richText ?? []) {
2086
- const spans = richTextPatch.spansSequence?.at(-1) ?? richTextPatch.spans ?? [];
2087
- setValueAtPath(next, richTextPatch.path, structuredClone(spans));
2380
+ const spans2 = richTextPatch.spansSequence?.at(-1) ?? richTextPatch.spans ?? [];
2381
+ setValueAtPath(next, richTextPatch.path, structuredClone(spans2));
2088
2382
  }
2089
2383
  for (const richTextStepPatch of patch.richTextSteps ?? []) {
2090
2384
  applyMockRichTextSteps(next, richTextStepPatch.path, richTextStepPatch.steps);
@@ -2095,50 +2389,50 @@ function applyMockJsonPatch(target, patch) {
2095
2389
  if (!patch) {
2096
2390
  return;
2097
2391
  }
2098
- for (const operation of patch.set ?? []) {
2099
- setValueAtPath(target, operation.path, readMockTypedValues(operation.value));
2392
+ for (const operation2 of patch.set ?? []) {
2393
+ setValueAtPath(target, operation2.path, readMockTypedValues(operation2.value));
2100
2394
  }
2101
- for (const operation of patch.unset ?? []) {
2102
- unsetValueAtPath(target, operation.path);
2395
+ for (const operation2 of patch.unset ?? []) {
2396
+ unsetValueAtPath(target, operation2.path);
2103
2397
  }
2104
- for (const operation of patch.listDelete ?? []) {
2105
- const list = readListAtPath(target, operation.path);
2106
- list.splice(operation.index, operation.deleteCount);
2398
+ for (const operation2 of patch.listDelete ?? []) {
2399
+ const list = readListAtPath(target, operation2.path);
2400
+ list.splice(operation2.index, operation2.deleteCount);
2107
2401
  }
2108
- for (const operation of patch.listInsert ?? []) {
2109
- const parent = ensureParentAtPath(target, operation.path);
2110
- const leaf = operation.path[operation.path.length - 1];
2402
+ for (const operation2 of patch.listInsert ?? []) {
2403
+ const parent = ensureParentAtPath(target, operation2.path);
2404
+ const leaf = operation2.path[operation2.path.length - 1];
2111
2405
  if (parent[leaf] == null) {
2112
2406
  parent[leaf] = [];
2113
2407
  }
2114
- const list = readListAtPath(target, operation.path);
2115
- list.splice(operation.index, 0, ...readMockTypedValues(operation.values));
2408
+ const list = readListAtPath(target, operation2.path);
2409
+ list.splice(operation2.index, 0, ...readMockTypedValues(operation2.values));
2116
2410
  }
2117
- for (const operation of patch.textSplice ?? []) {
2118
- applyMockTextPatch(target, operation.path, [{
2119
- index: operation.index,
2120
- deleteCount: operation.deleteCount,
2121
- ...operation.insert !== void 0 ? { insert: operation.insert } : {}
2411
+ for (const operation2 of patch.textSplice ?? []) {
2412
+ applyMockTextPatch(target, operation2.path, [{
2413
+ index: operation2.index,
2414
+ deleteCount: operation2.deleteCount,
2415
+ ...operation2.insert !== void 0 ? { insert: operation2.insert } : {}
2122
2416
  }]);
2123
2417
  }
2124
- for (const operation of patch.textMark ?? []) {
2125
- applyMockTextMarks(target, operation.path, operation.index, operation.length, operation.marks, false);
2418
+ for (const operation2 of patch.textMark ?? []) {
2419
+ applyMockTextMarks(target, operation2.path, operation2.index, operation2.length, operation2.marks, false);
2126
2420
  }
2127
- for (const operation of patch.textUnmark ?? []) {
2128
- applyMockTextMarks(target, operation.path, operation.index, operation.length, Object.fromEntries(operation.names.map((name) => [name, null])), true);
2421
+ for (const operation2 of patch.textUnmark ?? []) {
2422
+ applyMockTextMarks(target, operation2.path, operation2.index, operation2.length, Object.fromEntries(operation2.names.map((name) => [name, null])), true);
2129
2423
  }
2130
- for (const operation of patch.counterIncrement ?? []) {
2131
- assertMockCounterAmount(operation.delta, "JSON counterIncrement delta");
2132
- const parent = ensureParentAtPath(target, operation.path);
2133
- const leaf = operation.path[operation.path.length - 1];
2424
+ for (const operation2 of patch.counterIncrement ?? []) {
2425
+ assertMockCounterAmount(operation2.delta, "JSON counterIncrement delta");
2426
+ const parent = ensureParentAtPath(target, operation2.path);
2427
+ const leaf = operation2.path[operation2.path.length - 1];
2134
2428
  const current = parent[leaf];
2135
2429
  if (current == null) {
2136
- parent[leaf] = operation.delta;
2430
+ parent[leaf] = operation2.delta;
2137
2431
  } else if (typeof current === "number") {
2138
- parent[leaf] = current + operation.delta;
2432
+ parent[leaf] = current + operation2.delta;
2139
2433
  } else {
2140
2434
  throw new Error(
2141
- `Cannot increment non-counter value at ${operation.path.map(String).join(".")}`
2435
+ `Cannot increment non-counter value at ${operation2.path.map(String).join(".")}`
2142
2436
  );
2143
2437
  }
2144
2438
  }
@@ -2642,44 +2936,44 @@ function validateMockJsonPatch(patch) {
2642
2936
  if (operationCount === 0) {
2643
2937
  throw new Error("JSON patch must include at least one operation");
2644
2938
  }
2645
- for (const operation of patch.set ?? []) {
2646
- validateMockJsonPath(operation.path, "JSON set");
2647
- hydrateMockAutomergeValues(operation.value);
2939
+ for (const operation2 of patch.set ?? []) {
2940
+ validateMockJsonPath(operation2.path, "JSON set");
2941
+ hydrateMockAutomergeValues(operation2.value);
2648
2942
  }
2649
- for (const operation of patch.unset ?? []) {
2650
- validateMockJsonPath(operation.path, "JSON unset");
2943
+ for (const operation2 of patch.unset ?? []) {
2944
+ validateMockJsonPath(operation2.path, "JSON unset");
2651
2945
  }
2652
- for (const operation of patch.listDelete ?? []) {
2653
- validateMockJsonPath(operation.path, "JSON listDelete");
2654
- assertMockNonNegativeInteger(operation.index, "JSON listDelete index");
2655
- assertMockNonNegativeInteger(operation.deleteCount, "JSON listDelete deleteCount");
2946
+ for (const operation2 of patch.listDelete ?? []) {
2947
+ validateMockJsonPath(operation2.path, "JSON listDelete");
2948
+ assertMockNonNegativeInteger(operation2.index, "JSON listDelete index");
2949
+ assertMockNonNegativeInteger(operation2.deleteCount, "JSON listDelete deleteCount");
2656
2950
  }
2657
- for (const operation of patch.listInsert ?? []) {
2658
- validateMockJsonPath(operation.path, "JSON listInsert");
2659
- assertMockNonNegativeInteger(operation.index, "JSON listInsert index");
2660
- if (!Array.isArray(operation.values)) {
2951
+ for (const operation2 of patch.listInsert ?? []) {
2952
+ validateMockJsonPath(operation2.path, "JSON listInsert");
2953
+ assertMockNonNegativeInteger(operation2.index, "JSON listInsert index");
2954
+ if (!Array.isArray(operation2.values)) {
2661
2955
  throw new Error("JSON listInsert values must be an array");
2662
2956
  }
2663
- hydrateMockAutomergeValues(operation.values);
2957
+ hydrateMockAutomergeValues(operation2.values);
2664
2958
  }
2665
- for (const operation of patch.textSplice ?? []) {
2666
- validateMockJsonPath(operation.path, "JSON textSplice");
2667
- assertMockNonNegativeInteger(operation.index, "JSON textSplice index");
2668
- assertMockNonNegativeInteger(operation.deleteCount, "JSON textSplice deleteCount");
2669
- if (operation.insert !== void 0 && typeof operation.insert !== "string") {
2959
+ for (const operation2 of patch.textSplice ?? []) {
2960
+ validateMockJsonPath(operation2.path, "JSON textSplice");
2961
+ assertMockNonNegativeInteger(operation2.index, "JSON textSplice index");
2962
+ assertMockNonNegativeInteger(operation2.deleteCount, "JSON textSplice deleteCount");
2963
+ if (operation2.insert !== void 0 && typeof operation2.insert !== "string") {
2670
2964
  throw new Error("JSON textSplice insert must be a string");
2671
2965
  }
2672
2966
  }
2673
- for (const operation of [...patch.textMark ?? [], ...patch.textUnmark ?? []]) {
2674
- validateMockJsonPath(operation.path, "JSON text mark");
2675
- assertMockNonNegativeInteger(operation.index, "JSON text mark index");
2676
- if (!Number.isInteger(operation.length) || operation.length <= 0) {
2967
+ for (const operation2 of [...patch.textMark ?? [], ...patch.textUnmark ?? []]) {
2968
+ validateMockJsonPath(operation2.path, "JSON text mark");
2969
+ assertMockNonNegativeInteger(operation2.index, "JSON text mark index");
2970
+ if (!Number.isInteger(operation2.length) || operation2.length <= 0) {
2677
2971
  throw new Error("JSON text mark length must be a positive integer");
2678
2972
  }
2679
2973
  }
2680
- for (const operation of patch.counterIncrement ?? []) {
2681
- validateMockJsonPath(operation.path, "JSON counterIncrement");
2682
- assertMockCounterAmount(operation.delta, "JSON counterIncrement delta");
2974
+ for (const operation2 of patch.counterIncrement ?? []) {
2975
+ validateMockJsonPath(operation2.path, "JSON counterIncrement");
2976
+ assertMockCounterAmount(operation2.delta, "JSON counterIncrement delta");
2683
2977
  }
2684
2978
  }
2685
2979
  function ensureMockAutomergeParent(target, path) {
@@ -2781,64 +3075,64 @@ function reviveMockRichTextValue(value) {
2781
3075
  );
2782
3076
  }
2783
3077
  function applyMockAutomergeJsonOperations(target, patch) {
2784
- for (const operation of patch.set ?? []) {
2785
- setMockAutomergeValue(target, operation.path, hydrateMockAutomergeValues(operation.value));
3078
+ for (const operation2 of patch.set ?? []) {
3079
+ setMockAutomergeValue(target, operation2.path, hydrateMockAutomergeValues(operation2.value));
2786
3080
  }
2787
- for (const operation of patch.unset ?? []) {
2788
- unsetMockAutomergeValue(target, operation.path);
3081
+ for (const operation2 of patch.unset ?? []) {
3082
+ unsetMockAutomergeValue(target, operation2.path);
2789
3083
  }
2790
- for (const operation of patch.listDelete ?? []) {
2791
- readMockAutomergeList(target, operation.path).splice(operation.index, operation.deleteCount);
3084
+ for (const operation2 of patch.listDelete ?? []) {
3085
+ readMockAutomergeList(target, operation2.path).splice(operation2.index, operation2.deleteCount);
2792
3086
  }
2793
- for (const operation of patch.listInsert ?? []) {
2794
- ensureMockAutomergeList(target, operation.path).splice(
2795
- operation.index,
3087
+ for (const operation2 of patch.listInsert ?? []) {
3088
+ ensureMockAutomergeList(target, operation2.path).splice(
3089
+ operation2.index,
2796
3090
  0,
2797
- ...hydrateMockAutomergeValues(operation.values)
3091
+ ...hydrateMockAutomergeValues(operation2.values)
2798
3092
  );
2799
3093
  }
2800
- for (const operation of patch.textSplice ?? []) {
2801
- ensureMockAutomergeText(target, operation.path, "JSON textSplice");
3094
+ for (const operation2 of patch.textSplice ?? []) {
3095
+ ensureMockAutomergeText(target, operation2.path, "JSON textSplice");
2802
3096
  spliceMockAutomergeText(
2803
3097
  target,
2804
- operation.path,
2805
- operation.index,
2806
- operation.deleteCount,
2807
- operation.insert ?? ""
3098
+ operation2.path,
3099
+ operation2.index,
3100
+ operation2.deleteCount,
3101
+ operation2.insert ?? ""
2808
3102
  );
2809
3103
  }
2810
- for (const operation of patch.textMark ?? []) {
2811
- for (const [name, value] of Object.entries(operation.marks)) {
3104
+ for (const operation2 of patch.textMark ?? []) {
3105
+ for (const [name, value] of Object.entries(operation2.marks)) {
2812
3106
  Automerge.mark(
2813
3107
  target,
2814
- operation.path,
2815
- { start: operation.index, end: operation.index + operation.length, expand: "none" },
3108
+ operation2.path,
3109
+ { start: operation2.index, end: operation2.index + operation2.length, expand: "none" },
2816
3110
  name,
2817
3111
  reviveMockRichTextValue(value)
2818
3112
  );
2819
3113
  }
2820
3114
  }
2821
- for (const operation of patch.textUnmark ?? []) {
2822
- for (const name of operation.names) {
3115
+ for (const operation2 of patch.textUnmark ?? []) {
3116
+ for (const name of operation2.names) {
2823
3117
  Automerge.unmark(
2824
3118
  target,
2825
- operation.path,
2826
- { start: operation.index, end: operation.index + operation.length, expand: "none" },
3119
+ operation2.path,
3120
+ { start: operation2.index, end: operation2.index + operation2.length, expand: "none" },
2827
3121
  name
2828
3122
  );
2829
3123
  }
2830
3124
  }
2831
- for (const operation of patch.counterIncrement ?? []) {
2832
- const parent = ensureMockAutomergeParent(target, operation.path);
2833
- const leaf = operation.path[operation.path.length - 1];
3125
+ for (const operation2 of patch.counterIncrement ?? []) {
3126
+ const parent = ensureMockAutomergeParent(target, operation2.path);
3127
+ const leaf = operation2.path[operation2.path.length - 1];
2834
3128
  const existing = parent[leaf];
2835
3129
  if (existing === void 0 || existing === null) {
2836
- parent[leaf] = new Automerge.Counter(operation.delta);
3130
+ parent[leaf] = new Automerge.Counter(operation2.delta);
2837
3131
  } else if (Automerge.isCounter(existing)) {
2838
- existing.increment(operation.delta);
3132
+ existing.increment(operation2.delta);
2839
3133
  } else {
2840
3134
  throw new Error(
2841
- `Cannot increment non-counter value at '${operation.path.map(String).join(".")}'; create it with MindooValue.counter() first`
3135
+ `Cannot increment non-counter value at '${operation2.path.map(String).join(".")}'; create it with MindooValue.counter() first`
2842
3136
  );
2843
3137
  }
2844
3138
  }
@@ -2850,7 +3144,232 @@ function changeMockAutomergeDocument(doc, baseHeads, apply) {
2850
3144
  }
2851
3145
  return Automerge.change(doc, apply);
2852
3146
  }
3147
+ function reviveMockRichTextSpans(spans2) {
3148
+ return spans2.map(
3149
+ (span) => span.type === "text" ? {
3150
+ type: "text",
3151
+ value: span.value,
3152
+ marks: span.marks ? reviveMockRichTextValue(span.marks) : void 0
3153
+ } : { type: "block", value: reviveMockRichTextValue(span.value) }
3154
+ );
3155
+ }
3156
+ function dehydrateMockRichTextValue(value) {
3157
+ if (Automerge.isImmutableString(value)) {
3158
+ return { type: "immutableString", value: String(value) };
3159
+ }
3160
+ if (Array.isArray(value)) {
3161
+ return value.map((entry) => dehydrateMockRichTextValue(entry));
3162
+ }
3163
+ if (!value || typeof value !== "object" || value instanceof Date || value instanceof Uint8Array) {
3164
+ return value;
3165
+ }
3166
+ return Object.fromEntries(
3167
+ Object.entries(value).map(([key, entry]) => [key, dehydrateMockRichTextValue(entry)])
3168
+ );
3169
+ }
3170
+ function validateMockRichTextSpans(spans2) {
3171
+ if (!Array.isArray(spans2)) {
3172
+ throw new Error("Rich-text patch spans must be an array");
3173
+ }
3174
+ for (const span of spans2) {
3175
+ if (!span || typeof span !== "object") {
3176
+ throw new Error("Rich-text span must be an object");
3177
+ }
3178
+ if (span.type === "text") {
3179
+ if (typeof span.value !== "string") {
3180
+ throw new Error("Rich-text text span value must be a string");
3181
+ }
3182
+ continue;
3183
+ }
3184
+ if (span.type === "block") {
3185
+ if (!span.value || typeof span.value !== "object" || Array.isArray(span.value)) {
3186
+ throw new Error("Rich-text block span value must be an object");
3187
+ }
3188
+ continue;
3189
+ }
3190
+ throw new Error("Rich-text span type must be 'text' or 'block'");
3191
+ }
3192
+ }
3193
+ function validateMockRichTextPatch(patch) {
3194
+ validateMockJsonPath(patch.path, "Rich-text patch");
3195
+ const hasSpans = patch.spans !== void 0;
3196
+ const hasSequence = patch.spansSequence !== void 0;
3197
+ if (hasSpans === hasSequence) {
3198
+ throw new Error("Rich-text patch must include exactly one of spans or spansSequence");
3199
+ }
3200
+ if (hasSpans) {
3201
+ validateMockRichTextSpans(patch.spans);
3202
+ return;
3203
+ }
3204
+ if (!Array.isArray(patch.spansSequence) || patch.spansSequence.length === 0) {
3205
+ throw new Error("Rich-text patch spansSequence must be a non-empty array");
3206
+ }
3207
+ for (const spans2 of patch.spansSequence) {
3208
+ validateMockRichTextSpans(spans2);
3209
+ }
3210
+ }
3211
+ function validateMockRichTextStepPatch(patch) {
3212
+ validateMockJsonPath(patch.path, "Rich-text steps patch");
3213
+ if (!Array.isArray(patch.steps) || patch.steps.length === 0) {
3214
+ throw new Error("Rich-text steps patch steps must be a non-empty array");
3215
+ }
3216
+ for (const step of patch.steps) {
3217
+ if (!step || typeof step !== "object") {
3218
+ throw new Error("Rich-text step must be an object");
3219
+ }
3220
+ if (step.type !== "splice") {
3221
+ throw new Error("Rich-text step type must be 'splice'");
3222
+ }
3223
+ if (!Number.isInteger(step.index) || step.index < 0) {
3224
+ throw new Error("Rich-text splice step index must be a non-negative integer");
3225
+ }
3226
+ if (!Number.isInteger(step.deleteCount) || step.deleteCount < 0) {
3227
+ throw new Error("Rich-text splice step deleteCount must be a non-negative integer");
3228
+ }
3229
+ if (step.insert !== void 0 && typeof step.insert !== "string") {
3230
+ throw new Error("Rich-text splice step insert must be a string");
3231
+ }
3232
+ for (const range of step.marks ?? []) {
3233
+ if (!Number.isInteger(range.index) || range.index < 0) {
3234
+ throw new Error("Rich-text mark range index must be a non-negative integer");
3235
+ }
3236
+ if (!Number.isInteger(range.length) || range.length <= 0) {
3237
+ throw new Error("Rich-text mark range length must be a positive integer");
3238
+ }
3239
+ if (!range.marks || typeof range.marks !== "object" || Array.isArray(range.marks)) {
3240
+ throw new Error("Rich-text mark range marks must be an object");
3241
+ }
3242
+ }
3243
+ }
3244
+ }
3245
+ function ensureMockRichTextPath(target, path) {
3246
+ let node = target;
3247
+ for (let index = 0; index < path.length - 1; index += 1) {
3248
+ const segment = path[index];
3249
+ if (node[segment] === void 0 || node[segment] === null) {
3250
+ node[segment] = typeof path[index + 1] === "number" ? [] : {};
3251
+ }
3252
+ node = node[segment];
3253
+ if (node === null || typeof node !== "object") {
3254
+ throw new Error(`Cannot apply rich-text patch through non-object path segment '${String(segment)}'`);
3255
+ }
3256
+ }
3257
+ const leaf = path[path.length - 1];
3258
+ const current = node[leaf];
3259
+ if (current === void 0 || current === null) {
3260
+ node[leaf] = "";
3261
+ return;
3262
+ }
3263
+ if (typeof current !== "string") {
3264
+ throw new Error(`Cannot apply rich-text patch to non-string value at '${path.map(String).join(".")}'`);
3265
+ }
3266
+ }
3267
+ function changeMockAutomergeDocumentOnFork(doc, baseHeads, apply) {
3268
+ const current = Automerge.getHeads(doc);
3269
+ if (!baseHeads || baseHeads.length === 0 || current.length === baseHeads.length && current.every((head) => baseHeads.includes(head))) {
3270
+ return Automerge.change(doc, apply);
3271
+ }
3272
+ const base = Automerge.view(doc, baseHeads);
3273
+ const actor = Automerge.getActorId(doc);
3274
+ const ownChangeSince = Automerge.getChanges(base, doc).some(
3275
+ (change2) => Automerge.decodeChange(change2).actor === actor
3276
+ );
3277
+ const fork = Automerge.clone(base, ownChangeSince ? void 0 : { actor });
3278
+ return Automerge.merge(doc, Automerge.change(fork, apply));
3279
+ }
3280
+ function isMockUpdateSpansBoundsError(error) {
3281
+ const message = error instanceof Error ? error.message : String(error);
3282
+ return /Cannot updateSpans|out of bounds/i.test(message);
3283
+ }
3284
+ function readMockAutomergeRichText(document2, path) {
3285
+ validateMockJsonPath(path, "Rich-text snapshot");
3286
+ const doc = loadMockAutomergeDocument(document2);
3287
+ return Automerge.spans(doc, path).map((span) => {
3288
+ const record = span;
3289
+ return record.type === "text" ? {
3290
+ type: "text",
3291
+ value: typeof record.value === "string" ? record.value : "",
3292
+ marks: record.marks ? dehydrateMockRichTextValue(record.marks) : void 0
3293
+ } : {
3294
+ type: "block",
3295
+ value: dehydrateMockRichTextValue(record.value)
3296
+ };
3297
+ });
3298
+ }
3299
+ function commonMockBaseHeads(patch) {
3300
+ const all = [
3301
+ patch.json?.baseHeads,
3302
+ ...(patch.text ?? []).map((p) => p.baseHeads),
3303
+ ...(patch.richText ?? []).map((p) => p.baseHeads),
3304
+ ...(patch.richTextSteps ?? []).map((p) => p.baseHeads)
3305
+ ].filter((heads) => Array.isArray(heads) && heads.length > 0);
3306
+ if (all.length === 0) return void 0;
3307
+ const first = [...all[0]].sort().join(",");
3308
+ return all.every((heads) => [...heads].sort().join(",") === first) ? all[0] : null;
3309
+ }
3310
+ function applyMockRichTextSpansInChange(target, richTextPatch) {
3311
+ const sequence = richTextPatch.spansSequence ?? (richTextPatch.spans ? [richTextPatch.spans] : []);
3312
+ const config = richTextPatch.updateSpansConfig;
3313
+ ensureMockRichTextPath(target, richTextPatch.path);
3314
+ const path = richTextPatch.path;
3315
+ for (const spans2 of sequence) {
3316
+ const revived = reviveMockRichTextSpans(spans2);
3317
+ try {
3318
+ Automerge.updateSpans(target, path, revived, config);
3319
+ } catch (error) {
3320
+ if (!isMockUpdateSpansBoundsError(error)) {
3321
+ throw error;
3322
+ }
3323
+ setMockAutomergeValue(target, richTextPatch.path, "");
3324
+ Automerge.updateSpans(target, path, revived, config);
3325
+ }
3326
+ }
3327
+ }
3328
+ function applyMockRichTextStepsInChange(target, stepPatch) {
3329
+ ensureMockRichTextPath(target, stepPatch.path);
3330
+ const path = stepPatch.path;
3331
+ for (const step of stepPatch.steps) {
3332
+ Automerge.splice(target, path, step.index, step.deleteCount, step.insert ?? "");
3333
+ for (const range of step.marks ?? []) {
3334
+ for (const [name, value] of Object.entries(range.marks)) {
3335
+ Automerge.mark(
3336
+ target,
3337
+ path,
3338
+ { start: range.index, end: range.index + range.length, expand: "none" },
3339
+ name,
3340
+ reviveMockRichTextValue(value)
3341
+ );
3342
+ }
3343
+ }
3344
+ }
3345
+ }
2853
3346
  function applyMockAutomergeUpdate(doc, patch) {
3347
+ const common = commonMockBaseHeads(patch);
3348
+ if (common !== null) {
3349
+ if (patch.json) validateMockJsonPatch(patch.json);
3350
+ for (const textPatch of patch.text ?? []) validateMockJsonPath(textPatch.path, "Text patch");
3351
+ for (const richTextPatch of patch.richText ?? []) validateMockRichTextPatch(richTextPatch);
3352
+ for (const stepPatch of patch.richTextSteps ?? []) validateMockRichTextStepPatch(stepPatch);
3353
+ return changeMockAutomergeDocumentOnFork(doc, common, (target) => {
3354
+ for (const [key, value] of Object.entries(patch.set ?? {})) {
3355
+ if (value !== void 0) {
3356
+ target[key] = hydrateMockAutomergeValues(value);
3357
+ }
3358
+ }
3359
+ for (const key of patch.unset ?? []) {
3360
+ delete target[key];
3361
+ }
3362
+ if (patch.json) applyMockAutomergeJsonOperations(target, patch.json);
3363
+ for (const textPatch of patch.text ?? []) {
3364
+ ensureMockAutomergeText(target, textPatch.path, "text patch");
3365
+ for (const edit of textPatch.edits) {
3366
+ spliceMockAutomergeText(target, textPatch.path, edit.index, edit.deleteCount, edit.insert ?? "");
3367
+ }
3368
+ }
3369
+ for (const richTextPatch of patch.richText ?? []) applyMockRichTextSpansInChange(target, richTextPatch);
3370
+ for (const stepPatch of patch.richTextSteps ?? []) applyMockRichTextStepsInChange(target, stepPatch);
3371
+ });
3372
+ }
2854
3373
  let next = doc;
2855
3374
  if (patch.set || (patch.unset?.length ?? 0) > 0) {
2856
3375
  next = Automerge.change(next, (target) => {
@@ -2883,26 +3402,44 @@ function applyMockAutomergeUpdate(doc, patch) {
2883
3402
  });
2884
3403
  }
2885
3404
  for (const richTextPatch of patch.richText ?? []) {
2886
- validateMockJsonPath(richTextPatch.path, "Rich-text patch");
2887
- const spans = richTextPatch.spansSequence?.at(-1) ?? richTextPatch.spans ?? [];
2888
- next = Automerge.change(next, (target) => {
2889
- setMockAutomergeValue(target, richTextPatch.path, hydrateMockAutomergeValues(structuredClone(spans)));
3405
+ validateMockRichTextPatch(richTextPatch);
3406
+ const sequence = richTextPatch.spansSequence ?? (richTextPatch.spans ? [richTextPatch.spans] : []);
3407
+ const config = richTextPatch.updateSpansConfig;
3408
+ next = changeMockAutomergeDocumentOnFork(next, richTextPatch.baseHeads, (target) => {
3409
+ ensureMockRichTextPath(target, richTextPatch.path);
3410
+ const path = richTextPatch.path;
3411
+ for (const spans2 of sequence) {
3412
+ const revived = reviveMockRichTextSpans(spans2);
3413
+ try {
3414
+ Automerge.updateSpans(target, path, revived, config);
3415
+ } catch (error) {
3416
+ if (!isMockUpdateSpansBoundsError(error)) {
3417
+ throw error;
3418
+ }
3419
+ setMockAutomergeValue(target, richTextPatch.path, "");
3420
+ Automerge.updateSpans(target, path, revived, config);
3421
+ }
3422
+ }
2890
3423
  });
2891
3424
  }
2892
3425
  for (const stepPatch of patch.richTextSteps ?? []) {
2893
- validateMockJsonPath(stepPatch.path, "Rich-text steps patch");
3426
+ validateMockRichTextStepPatch(stepPatch);
2894
3427
  next = changeMockAutomergeDocument(next, stepPatch.baseHeads, (target) => {
2895
- const current = readMockAutomergeValue(target, stepPatch.path);
2896
- if (current !== void 0 && current !== null && typeof current !== "string") {
2897
- setMockAutomergeValue(
2898
- target,
2899
- stepPatch.path,
2900
- materializeMockRichTextValue(projectMockAutomergeValue(current))
2901
- );
2902
- }
2903
- ensureMockAutomergeText(target, stepPatch.path, "rich-text steps");
3428
+ ensureMockRichTextPath(target, stepPatch.path);
3429
+ const path = stepPatch.path;
2904
3430
  for (const step of stepPatch.steps) {
2905
- spliceMockAutomergeText(target, stepPatch.path, step.index, step.deleteCount, step.insert ?? "");
3431
+ Automerge.splice(target, path, step.index, step.deleteCount, step.insert ?? "");
3432
+ for (const range of step.marks ?? []) {
3433
+ for (const [name, value] of Object.entries(range.marks)) {
3434
+ Automerge.mark(
3435
+ target,
3436
+ path,
3437
+ { start: range.index, end: range.index + range.length, expand: "none" },
3438
+ name,
3439
+ reviveMockRichTextValue(value)
3440
+ );
3441
+ }
3442
+ }
2906
3443
  }
2907
3444
  });
2908
3445
  }
@@ -2925,8 +3462,8 @@ function forkMockAutomergeDocument(doc, baseHeads, actor) {
2925
3462
  }
2926
3463
  return Automerge.clone(Automerge.view(doc, [...heads]), { actor });
2927
3464
  }
2928
- function getFieldValue2(source, field) {
2929
- return field.split(".").reduce((current, part) => {
3465
+ function getFieldValue2(source, field2) {
3466
+ return field2.split(".").reduce((current, part) => {
2930
3467
  if (current && typeof current === "object" && part in current) {
2931
3468
  return current[part];
2932
3469
  }
@@ -2938,7 +3475,7 @@ function matchesDocumentFilter(document2, filter) {
2938
3475
  return true;
2939
3476
  }
2940
3477
  return Object.entries(filter).every(
2941
- ([field, expected]) => getFieldValue2(document2.data, field) === expected
3478
+ ([field2, expected]) => getFieldValue2(document2.data, field2) === expected
2942
3479
  );
2943
3480
  }
2944
3481
  function withValidatedCreateIds(documents) {
@@ -3015,7 +3552,7 @@ function computeMockTextScore(document2, clause) {
3015
3552
  if (terms.length === 0) {
3016
3553
  return void 0;
3017
3554
  }
3018
- const haystack = clause.fields ? clause.fields.map((field) => collectMockText(getFieldValue2(document2.data, field))).join(" ") : collectMockText(
3555
+ const haystack = clause.fields ? clause.fields.map((field2) => collectMockText(getFieldValue2(document2.data, field2))).join(" ") : collectMockText(
3019
3556
  Object.fromEntries(
3020
3557
  Object.entries(document2.data).filter(([key]) => !key.startsWith("_"))
3021
3558
  )
@@ -3245,7 +3782,7 @@ function createLiveMockViewNavigator(initial, rebuild, subscribe) {
3245
3782
  });
3246
3783
  }
3247
3784
  function projectMockFields(data, fields) {
3248
- return fields ? Object.fromEntries(fields.map((field) => [field, getFieldValue2(data, field)])) : structuredClone(data);
3785
+ return fields ? Object.fromEntries(fields.map((field2) => [field2, getFieldValue2(data, field2)])) : structuredClone(data);
3249
3786
  }
3250
3787
  function mockLastModified(document2) {
3251
3788
  return document2.updatedAt ? Date.parse(document2.updatedAt) || 0 : 0;
@@ -3773,9 +4310,10 @@ async function mockSha256Hex(value) {
3773
4310
  const digest = await subtle.digest("SHA-256", new TextEncoder().encode(value));
3774
4311
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
3775
4312
  }
3776
- function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
4313
+ function createDatabaseHandle(definition, peers, directoryUsers = () => [], localActor = MOCK_AUTOMERGE_LOCAL_ACTOR) {
3777
4314
  let createCounter = 0;
3778
4315
  let changeCounter = 0;
4316
+ const replicaTag = localActor === MOCK_AUTOMERGE_LOCAL_ACTOR ? "" : `-${localActor.slice(0, 6)}`;
3779
4317
  const defaultViewFactory = async () => createDefaultViewNavigator();
3780
4318
  const automergeMode = definition.automerge === true;
3781
4319
  let changeSequence = 0;
@@ -3852,7 +4390,7 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
3852
4390
  return writeAutomergeDocument(
3853
4391
  docId,
3854
4392
  existing,
3855
- Automerge.change(loadMockAutomergeDocument(existing, MOCK_AUTOMERGE_LOCAL_ACTOR), (target) => {
4393
+ Automerge.change(loadMockAutomergeDocument(existing, localActor), (target) => {
3856
4394
  target._encryptFor = hydrateMockAutomergeValues(nextEncryptFor);
3857
4395
  })
3858
4396
  );
@@ -3967,9 +4505,9 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
3967
4505
  };
3968
4506
  }
3969
4507
  const projectedData = query?.fields ? Object.fromEntries(
3970
- query.fields.map((field) => [
3971
- field,
3972
- getFieldValue2(document2.data, field)
4508
+ query.fields.map((field2) => [
4509
+ field2,
4510
+ getFieldValue2(document2.data, field2)
3973
4511
  ])
3974
4512
  ) : document2.data;
3975
4513
  return {
@@ -4030,7 +4568,7 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
4030
4568
  return {
4031
4569
  path: [...path],
4032
4570
  heads: document2.heads ? [...document2.heads] : void 0,
4033
- spans: structuredClone(readRichTextSpansAtPath(document2.data, path))
4571
+ spans: automergeMode ? readMockAutomergeRichText(document2, path) : structuredClone(readRichTextSpansAtPath(document2.data, path))
4034
4572
  };
4035
4573
  },
4036
4574
  // Mock cursors store the index they were created at and do not follow
@@ -4162,10 +4700,10 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
4162
4700
  id = callerId;
4163
4701
  } else if (typeof input.idPrefix === "string" && input.idPrefix.length > 0) {
4164
4702
  createCounter += 1;
4165
- id = `${input.idPrefix}_${String(createCounter).padStart(22, "0")}`;
4703
+ id = `${input.idPrefix}_${String(createCounter).padStart(22, "0")}${replicaTag}`;
4166
4704
  } else {
4167
4705
  createCounter += 1;
4168
- id = `doc-${createCounter}`;
4706
+ id = `doc-${createCounter}${replicaTag}`;
4169
4707
  }
4170
4708
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
4171
4709
  if (Array.isArray(input.recipients) && input.recipientOptions?.includeSelf === false && input.recipients.every((name) => name.trim().length === 0)) {
@@ -4187,7 +4725,7 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
4187
4725
  stored2,
4188
4726
  createMockAutomergeDocument(
4189
4727
  { ...input.set ?? {}, ...encryptFor },
4190
- MOCK_AUTOMERGE_LOCAL_ACTOR
4728
+ localActor
4191
4729
  )
4192
4730
  );
4193
4731
  storedDocuments.set(id, stored2);
@@ -4237,7 +4775,7 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
4237
4775
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4238
4776
  const existing = storedDocuments.get(docId);
4239
4777
  if (automergeMode) {
4240
- const current = existing?.automergeBinary ? loadMockAutomergeDocument(existing, MOCK_AUTOMERGE_LOCAL_ACTOR) : createMockAutomergeDocument(existing?.data ?? {}, MOCK_AUTOMERGE_LOCAL_ACTOR);
4778
+ const current = existing?.automergeBinary ? loadMockAutomergeDocument(existing, localActor) : createMockAutomergeDocument(existing?.data ?? {}, localActor);
4241
4779
  return writeAutomergeDocument(docId, existing, applyMockAutomergeUpdate(current, patch));
4242
4780
  }
4243
4781
  const updated = {
@@ -4559,7 +5097,7 @@ function createDatabaseHandle(definition, peers, directoryUsers = () => []) {
4559
5097
  if (!automergeMode || !existing.automergeBinary) {
4560
5098
  return await defaultDocuments.update(docId, input);
4561
5099
  }
4562
- const current = loadMockAutomergeDocument(existing, MOCK_AUTOMERGE_LOCAL_ACTOR);
5100
+ const current = loadMockAutomergeDocument(existing, localActor);
4563
5101
  const remote = applyMockAutomergeUpdate(
4564
5102
  forkMockAutomergeDocument(
4565
5103
  current,
@@ -4692,7 +5230,12 @@ function createMockSessionState(options = {}) {
4692
5230
  capabilities: [...definition.info.capabilities]
4693
5231
  }));
4694
5232
  for (const definition of definitions) {
4695
- const created = createDatabaseHandle(definition, databasePeers, () => directoryUsers);
5233
+ const created = createDatabaseHandle(
5234
+ definition,
5235
+ databasePeers,
5236
+ () => directoryUsers,
5237
+ options.automergeActor ?? MOCK_AUTOMERGE_LOCAL_ACTOR
5238
+ );
4696
5239
  const databaseId = definition.info.id;
4697
5240
  databaseHandles.set(
4698
5241
  databaseId,
@@ -4706,35 +5249,48 @@ function createMockSessionState(options = {}) {
4706
5249
  databaseDocumentStores.set(definition.info.id, created.documents);
4707
5250
  databaseLiveQueryRefreshers.set(definition.info.id, created.notifyLiveQueries);
4708
5251
  databaseRemoteUpdaters.set(definition.info.id, created.applyRemoteUpdate);
5252
+ const createView = async (input) => {
5253
+ const build = () => createEvaluatingViewNavigator(
5254
+ input,
5255
+ input.databaseIds.flatMap((databaseId2) => {
5256
+ const list = databaseViewDocumentLists.get(databaseId2);
5257
+ return list ? list() : [];
5258
+ })
5259
+ );
5260
+ const subscribe = (onChange) => {
5261
+ const listener = { databaseIds: [...input.databaseIds], onChange };
5262
+ viewChangeListeners.add(listener);
5263
+ return () => viewChangeListeners.delete(listener);
5264
+ };
5265
+ const evaluating = await build();
5266
+ if (evaluating) {
5267
+ return createLiveMockViewNavigator(evaluating, build, subscribe);
5268
+ }
5269
+ console.warn(
5270
+ "[mindoodb-app-sdk/testing] mindoodb peer not available; using empty view navigator. Install mindoodb as a devDependency for evaluating VirtualViews."
5271
+ );
5272
+ return createLiveMockViewNavigator(
5273
+ await createDefaultViewNavigator(),
5274
+ async () => null,
5275
+ subscribe
5276
+ );
5277
+ };
4709
5278
  databaseViewApis.set(definition.info.id, {
4710
- async create(input) {
4711
- const build = () => createEvaluatingViewNavigator(
4712
- input,
4713
- input.databaseIds.flatMap((databaseId2) => {
4714
- const list = databaseViewDocumentLists.get(databaseId2);
4715
- return list ? list() : [];
4716
- })
4717
- );
4718
- const subscribe = (onChange) => {
4719
- const listener = { databaseIds: [...input.databaseIds], onChange };
4720
- viewChangeListeners.add(listener);
4721
- return () => viewChangeListeners.delete(listener);
4722
- };
4723
- const evaluating = await build();
4724
- if (evaluating) {
4725
- return createLiveMockViewNavigator(evaluating, build, subscribe);
5279
+ create: createView,
5280
+ // A view configured in the launch context, evaluated over the mock
5281
+ // documents of its sources: a fresh navigator per open, with the
5282
+ // open options applied, as Haven builds one.
5283
+ async open(viewId, openOptions) {
5284
+ const configured = launchContext.views.find((view2) => view2.id === viewId);
5285
+ if (!configured) {
5286
+ return await createDefaultViewNavigator();
4726
5287
  }
4727
- console.warn(
4728
- "[mindoodb-app-sdk/testing] mindoodb peer not available; using empty view navigator. Install mindoodb as a devDependency for evaluating VirtualViews."
4729
- );
4730
- return createLiveMockViewNavigator(
4731
- await createDefaultViewNavigator(),
4732
- async () => null,
4733
- subscribe
4734
- );
4735
- },
4736
- async open(_viewId) {
4737
- return await createDefaultViewNavigator();
5288
+ return await createView({
5289
+ databaseIds: configured.sources.map((source) => source.databaseId),
5290
+ definition: configuredViewDefinition(configured),
5291
+ categorizationStyle: configured.categorizationStyle,
5292
+ ...openOptions ? { options: openOptions } : {}
5293
+ });
4738
5294
  },
4739
5295
  ...definition.methods?.views ?? {}
4740
5296
  });
@@ -4830,16 +5386,21 @@ function createMockSessionState(options = {}) {
4830
5386
  }
4831
5387
  const view2 = await api.create(input);
4832
5388
  const viewId = input.definition.id || crypto.randomUUID();
4833
- sessionViews.set(viewId, view2);
5389
+ sessionViews.set(viewId, input);
4834
5390
  return view2;
4835
5391
  },
5392
+ // Every open is a navigator of its own with its own options, as in
5393
+ // Haven: disposing one leaves the others and later opens working.
4836
5394
  async openViewNavigator(viewId, options2) {
4837
- const existing = sessionViews.get(viewId);
4838
- if (existing) {
4839
- return existing;
5395
+ const created = sessionViews.get(viewId);
5396
+ if (created) {
5397
+ const api2 = databaseViewApis.get(created.databaseIds[0]);
5398
+ if (api2) {
5399
+ return await api2.create({ ...created, ...options2 ? { options: options2 } : {} });
5400
+ }
4840
5401
  }
4841
5402
  const sourceDatabaseId = launchContext.views.find(
4842
- (view3) => view3.id === viewId
5403
+ (view2) => view2.id === viewId
4843
5404
  )?.sources[0]?.databaseId;
4844
5405
  if (!sourceDatabaseId) {
4845
5406
  throw new Error(`Unknown test view: ${viewId}`);
@@ -4850,9 +5411,7 @@ function createMockSessionState(options = {}) {
4850
5411
  `Unknown test database for view ${viewId}: ${sourceDatabaseId}`
4851
5412
  );
4852
5413
  }
4853
- const view2 = await api.open(viewId, options2);
4854
- sessionViews.set(viewId, view2);
4855
- return view2;
5414
+ return await api.open(viewId, options2);
4856
5415
  },
4857
5416
  menus,
4858
5417
  drag,
@@ -4981,6 +5540,8 @@ function createMockSessionState(options = {}) {
4981
5540
  directoryUsers = [...users];
4982
5541
  },
4983
5542
  getDirectoryUsers: () => [...directoryUsers],
5543
+ replicaStores: () => databaseDocumentStores,
5544
+ notifyReplicaChange: (databaseId) => databaseLiveQueryRefreshers.get(databaseId)?.(),
4984
5545
  embedHost,
4985
5546
  getDatabase(databaseId) {
4986
5547
  const database = databaseHandles.get(databaseId);
@@ -5098,9 +5659,92 @@ function buildMockIncomingContent(input, deliveryId) {
5098
5659
  })
5099
5660
  };
5100
5661
  }
5662
+ var replicaStates = /* @__PURE__ */ new WeakMap();
5663
+ function sameMockHeads(a, b) {
5664
+ const x = a?.heads ?? [];
5665
+ const y = b?.heads ?? [];
5666
+ return x.length === y.length && x.every((head) => y.includes(head));
5667
+ }
5668
+ function mockReplicaDifferences(a, b) {
5669
+ const left = replicaStates.get(a)?.replicaStores();
5670
+ const right = replicaStates.get(b)?.replicaStores();
5671
+ if (!left || !right) throw new Error("syncMockReplicas: not a mock controller");
5672
+ let count = 0;
5673
+ for (const [databaseId, docsA] of left) {
5674
+ const docsB = right.get(databaseId);
5675
+ if (!docsB) continue;
5676
+ for (const id of /* @__PURE__ */ new Set([...docsA.keys(), ...docsB.keys()])) {
5677
+ const x = docsA.get(id);
5678
+ const y = docsB.get(id);
5679
+ if (!x || !y || x.isDeleted !== y.isDeleted || !sameMockHeads(x, y)) count += 1;
5680
+ }
5681
+ }
5682
+ return count;
5683
+ }
5684
+ async function syncMockReplicas(a, b) {
5685
+ const stateA = replicaStates.get(a);
5686
+ const stateB = replicaStates.get(b);
5687
+ if (!stateA || !stateB) throw new Error("syncMockReplicas: not a mock controller");
5688
+ let changed = 0;
5689
+ for (const [databaseId, docsA] of stateA.replicaStores()) {
5690
+ const docsB = stateB.replicaStores().get(databaseId);
5691
+ if (!docsB) continue;
5692
+ const touchedA = /* @__PURE__ */ new Set();
5693
+ const touchedB = /* @__PURE__ */ new Set();
5694
+ for (const id of /* @__PURE__ */ new Set([...docsA.keys(), ...docsB.keys()])) {
5695
+ const x = docsA.get(id);
5696
+ const y = docsB.get(id);
5697
+ if (x && y && x.isDeleted === y.isDeleted && sameMockHeads(x, y)) continue;
5698
+ changed += 1;
5699
+ let merged;
5700
+ if (x?.automergeBinary && y?.automergeBinary) {
5701
+ const doc = Automerge.merge(Automerge.load(x.automergeBinary), Automerge.load(y.automergeBinary));
5702
+ merged = { ...x, ...y, attachments: structuredClone(x.attachments ?? y.attachments ?? []) };
5703
+ persistMockAutomergeDocument(merged, doc);
5704
+ } else if (x && y) {
5705
+ merged = structuredClone((x.updatedAt ?? "") >= (y.updatedAt ?? "") ? x : y);
5706
+ } else {
5707
+ merged = structuredClone(x ?? y);
5708
+ }
5709
+ merged.isDeleted = Boolean(x?.isDeleted || y?.isDeleted);
5710
+ merged.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5711
+ if (!x || !sameMockHeads(x, merged) || x.isDeleted !== merged.isDeleted) {
5712
+ docsA.set(id, structuredClone(merged));
5713
+ touchedA.add(id);
5714
+ }
5715
+ if (!y || !sameMockHeads(y, merged) || y.isDeleted !== merged.isDeleted) {
5716
+ docsB.set(id, structuredClone(merged));
5717
+ touchedB.add(id);
5718
+ }
5719
+ }
5720
+ const dbA = stateA.getDatabase(databaseId);
5721
+ const dbB = stateB.getDatabase(databaseId);
5722
+ for (const id of /* @__PURE__ */ new Set([...docsA.keys()])) {
5723
+ if (docsA.get(id)?.isDeleted) continue;
5724
+ for (const [from2, to, touched] of [
5725
+ [dbA, dbB, touchedB],
5726
+ [dbB, dbA, touchedA]
5727
+ ]) {
5728
+ const have = new Set((await to.attachments.list(id).catch(() => [])).map((f) => f.fileName));
5729
+ for (const file of await from2.attachments.list(id).catch(() => [])) {
5730
+ if (have.has(file.fileName)) continue;
5731
+ const reader = await from2.attachments.openReadStream(id, file.fileName);
5732
+ const writer = await to.attachments.openWriteStream(id, file.fileName, file.mimeType);
5733
+ for (let chunk = await reader.read(); chunk !== null; chunk = await reader.read()) await writer.write(chunk);
5734
+ await reader.close();
5735
+ await writer.close();
5736
+ touched.add(id);
5737
+ }
5738
+ }
5739
+ }
5740
+ if (touchedA.size) stateA.notifyReplicaChange(databaseId);
5741
+ if (touchedB.size) stateB.notifyReplicaChange(databaseId);
5742
+ }
5743
+ return changed;
5744
+ }
5101
5745
  function createMockMindooDBAppSession(options = {}) {
5102
5746
  const state = createMockSessionState(options);
5103
- return {
5747
+ const controller = {
5104
5748
  bridge: state.bridge,
5105
5749
  session: state.session,
5106
5750
  getLaunchContext: state.getLaunchContext,
@@ -5129,10 +5773,13 @@ function createMockMindooDBAppSession(options = {}) {
5129
5773
  setComponents: (components) => state.embedHost.setComponents(components),
5130
5774
  getEmbeddingFinish: () => state.embedHost.getEmbeddingFinish(),
5131
5775
  setEmbedDirty: (embedId, dirty) => state.embedHost.setEmbedDirty(embedId, dirty),
5776
+ setEmbedAgentTools: (embedId, names, toolOptions) => state.embedHost.setEmbedAgentTools(embedId, names, toolOptions),
5132
5777
  failNextEmbedSave: (message) => state.embedHost.failNextSave(message),
5133
5778
  getEmbeddingDirty: () => state.embedHost.getEmbeddingDirty(),
5134
5779
  requestEmbeddingSave: () => state.embedHost.requestEmbeddingSave()
5135
5780
  };
5781
+ replicaStates.set(controller, state);
5782
+ return controller;
5136
5783
  }
5137
5784
  function createMockMindooDBAppBridge(options = {}) {
5138
5785
  return createMockMindooDBAppSession(options);
@@ -5151,6 +5798,7 @@ function createFakeBridgeHost(options = {}) {
5151
5798
  ...options.agentConsent !== void 0 ? { agentConsent: options.agentConsent } : {},
5152
5799
  ...options.agentInvokeTimeoutMs !== void 0 ? { agentInvokeTimeoutMs: options.agentInvokeTimeoutMs } : {},
5153
5800
  appId: () => state.getLaunchContext().appId,
5801
+ embedded: () => Boolean(state.getLaunchContext().embed),
5154
5802
  post(message) {
5155
5803
  const port = [...connectedPorts].pop();
5156
5804
  if (!port) return false;
@@ -5169,6 +5817,11 @@ function createFakeBridgeHost(options = {}) {
5169
5817
  (port) => port.postMessage({ protocol: PROTOCOL, kind: "embed-event", event: { type: "dirty", ...event } })
5170
5818
  );
5171
5819
  });
5820
+ state.embedHost.onAgentTools((event) => {
5821
+ connectedPorts.forEach(
5822
+ (port) => port.postMessage({ protocol: PROTOCOL, kind: "embed-event", event: { type: "agentTools", ...event } })
5823
+ );
5824
+ });
5172
5825
  const embeddingRequests = /* @__PURE__ */ new Map();
5173
5826
  let embeddingRequestCounter = 0;
5174
5827
  const beforeCloseWaiters = /* @__PURE__ */ new Map();
@@ -5421,9 +6074,18 @@ function createFakeBridgeHost(options = {}) {
5421
6074
  case "embeds.save":
5422
6075
  await state.embedHost.saveEmbed(String(params.embedId));
5423
6076
  return { ok: true };
6077
+ case "embeds.setActive":
6078
+ state.embedHost.setActive(String(params.embedId));
6079
+ return { ok: true };
5424
6080
  case "embedding.setDirty":
5425
6081
  state.embedHost.setEmbeddingDirty(params.dirty === true);
5426
6082
  return { ok: true };
6083
+ case "embedding.lookup":
6084
+ return await state.embedHost.embedding.lookup(String(params.field), {
6085
+ ...typeof params.query === "string" ? { query: params.query } : {},
6086
+ ...Array.isArray(params.docIds) ? { docIds: params.docIds.map(String) } : {},
6087
+ ...typeof params.limit === "number" ? { limit: params.limit } : {}
6088
+ });
5427
6089
  case "embedding.respond": {
5428
6090
  const pending = embeddingRequests.get(String(params.requestId));
5429
6091
  embeddingRequests.delete(String(params.requestId));
@@ -6060,6 +6722,7 @@ function createFakeBridgeHost(options = {}) {
6060
6722
  closeEmbed: (embedId, reason, result) => state.embedHost.closeEmbed(embedId, reason, result),
6061
6723
  getEmbeddingFinish: () => state.embedHost.getEmbeddingFinish(),
6062
6724
  setEmbedDirty: (embedId, dirty) => state.embedHost.setEmbedDirty(embedId, dirty),
6725
+ setEmbedAgentTools: (embedId, names, toolOptions) => state.embedHost.setEmbedAgentTools(embedId, names, toolOptions),
6063
6726
  failNextEmbedSave: (message) => state.embedHost.failNextSave(message),
6064
6727
  getEmbeddingDirty: () => state.embedHost.getEmbeddingDirty(),
6065
6728
  requestEmbeddingSave() {
@@ -6185,10 +6848,12 @@ function createFakeBridgeHost(options = {}) {
6185
6848
  customRequestHandlers.delete(method);
6186
6849
  }
6187
6850
  };
6851
+ replicaStates.set(controller, state);
6188
6852
  return controller;
6189
6853
  }
6190
6854
  export {
6191
6855
  ALL_CAPABILITIES,
6856
+ DEFAULT_TEST_HOST_USERS,
6192
6857
  MockForbiddenError,
6193
6858
  TEST_HOST_LOCALES,
6194
6859
  capabilitiesForDefinitionPermissions,
@@ -6203,8 +6868,12 @@ export {
6203
6868
  loadMindoodbForTesting,
6204
6869
  mockAgentToolPrefix,
6205
6870
  mockDatabasesFromDefinition,
6871
+ mockReplicaDifferences,
6872
+ modeSwitch,
6206
6873
  mountHavenTestHost,
6874
+ mountTwoUserTestHost,
6207
6875
  previewJson,
6208
6876
  readTestHostUrlSettings,
6877
+ syncMockReplicas,
6209
6878
  validateMockAgentTools
6210
6879
  };