@mulmoclaude/core 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/assets/helps/collection-skills.md +4 -2
  2. package/assets/helps/wiki.md +47 -2
  3. package/dist/collection/core/ontologyGraph.d.ts +6 -2
  4. package/dist/collection/index.cjs +4 -4
  5. package/dist/collection/index.cjs.map +1 -1
  6. package/dist/collection/index.js +4 -4
  7. package/dist/collection/index.js.map +1 -1
  8. package/dist/collection/registry/server/index.cjs +2 -2
  9. package/dist/collection/registry/server/index.js +2 -2
  10. package/dist/collection/server/backendAvailability.d.ts +5 -0
  11. package/dist/collection/server/csvStore.d.ts +0 -5
  12. package/dist/collection/server/index.cjs +4 -2
  13. package/dist/collection/server/index.d.ts +1 -0
  14. package/dist/collection/server/index.js +3 -3
  15. package/dist/collection/server/manageTool.d.ts +4 -0
  16. package/dist/collection/server/schemaDocs.d.ts +21 -0
  17. package/dist/collection/server/store.d.ts +37 -0
  18. package/dist/collection/server/watchFs.d.ts +17 -0
  19. package/dist/collection-watchers/index.cjs +114 -203
  20. package/dist/collection-watchers/index.cjs.map +1 -1
  21. package/dist/collection-watchers/index.d.ts +1 -1
  22. package/dist/collection-watchers/index.js +114 -202
  23. package/dist/collection-watchers/index.js.map +1 -1
  24. package/dist/collection-watchers/watcher.d.ts +8 -13
  25. package/dist/{discovery-B9Vfojyy.js → discovery-BbsJwVEq.js} +128 -15
  26. package/dist/discovery-BbsJwVEq.js.map +1 -0
  27. package/dist/{discovery-BxbJy2VN.cjs → discovery-Bklck7Ck.cjs} +137 -12
  28. package/dist/discovery-Bklck7Ck.cjs.map +1 -0
  29. package/dist/feeds/server/index.cjs +2 -2
  30. package/dist/feeds/server/index.js +2 -2
  31. package/dist/google/index.cjs +1 -1
  32. package/dist/google/index.js +1 -1
  33. package/dist/{promptSafety-0ZKHX-6J.cjs → promptSafety-BFt2g_wn.cjs} +2 -2
  34. package/dist/promptSafety-BFt2g_wn.cjs.map +1 -0
  35. package/dist/{promptSafety-RE1SPxBN.js → promptSafety-cZIeiZtB.js} +3 -3
  36. package/dist/promptSafety-cZIeiZtB.js.map +1 -0
  37. package/dist/remote-view/index.cjs +8 -0
  38. package/dist/remote-view/index.cjs.map +1 -1
  39. package/dist/remote-view/index.d.ts +7 -0
  40. package/dist/remote-view/index.js +8 -1
  41. package/dist/remote-view/index.js.map +1 -1
  42. package/dist/{server-DOGKfUuD.cjs → server-CghbYZFY.cjs} +199 -22
  43. package/dist/server-CghbYZFY.cjs.map +1 -0
  44. package/dist/{server-sGNe-msA.js → server-DlG6ydHO.js} +200 -23
  45. package/dist/server-DlG6ydHO.js.map +1 -0
  46. package/dist/whisper/client.cjs.map +1 -1
  47. package/dist/whisper/client.js.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/discovery-B9Vfojyy.js.map +0 -1
  50. package/dist/discovery-BxbJy2VN.cjs.map +0 -1
  51. package/dist/promptSafety-0ZKHX-6J.cjs.map +0 -1
  52. package/dist/promptSafety-RE1SPxBN.js.map +0 -1
  53. package/dist/server-DOGKfUuD.cjs.map +0 -1
  54. package/dist/server-sGNe-msA.js.map +0 -1
@@ -1,14 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
3
2
  const require_ids = require("../ids-DWmHjm17.cjs");
4
- const require_promptSafety = require("../promptSafety-0ZKHX-6J.cjs");
3
+ const require_promptSafety = require("../promptSafety-BFt2g_wn.cjs");
5
4
  require("../collection/index.cjs");
6
- const require_discovery = require("../discovery-BxbJy2VN.cjs");
7
- const require_server = require("../server-DOGKfUuD.cjs");
5
+ const require_discovery = require("../discovery-Bklck7Ck.cjs");
6
+ const require_server = require("../server-CghbYZFY.cjs");
8
7
  const require_notifier = require("../notifier-bS8IEeLA.cjs");
9
- let node_path = require("node:path");
10
- node_path = require_rolldown_runtime.__toESM(node_path, 1);
11
- let node_fs = require("node:fs");
12
8
  let node_fs_promises = require("node:fs/promises");
13
9
  //#region src/collection-watchers/config.ts
14
10
  var NOOP_LOG = {
@@ -302,6 +298,15 @@ var watchers = /* @__PURE__ */ new Map();
302
298
  var rediscoveryTimer = null;
303
299
  var triggerTimer = null;
304
300
  var started = false;
301
+ /** Guards the clock tick against overlapping itself. Paired with
302
+ * `watcherEpoch`: a teardown while a pass is in flight bumps the epoch, so
303
+ * that pass's `finally` knows it belongs to a dead generation and must not
304
+ * clear a guard the restarted watcher set now owns. `triggerTickInFlight`
305
+ * is what teardown AWAITS — clearing the flag alone would let a restart run
306
+ * a second pass alongside the first. */
307
+ var triggerTickRunning = false;
308
+ var watcherEpoch = 0;
309
+ var triggerTickInFlight = null;
305
310
  /** Discovery options threaded into every `discoverCollections` /
306
311
  * `loadCollection` / `sweepStaleActiveEntries` call. Production: empty
307
312
  * (live workspace). Tests: `{ workspaceRoot, userSkillsDir }` pointing
@@ -309,15 +314,10 @@ var started = false;
309
314
  * without threading through every signature. */
310
315
  var discoveryOpts = {};
311
316
  var itemSlots = /* @__PURE__ */ new Map();
312
- /** Per-slug single-flight for a storage (db-file) collection's full
313
- * reconcile pass a burst of db writes collapses to one pass + one
314
- * trailing re-run, mirroring the per-item slots of the file watcher. */
315
- var storageSlots = /* @__PURE__ */ new Map();
316
- /** Trailing debounce per dataSource collection: an atomic file replace
317
- * (Excel save, editor rename) surfaces as 2-3 fs events — collapse them
318
- * into one change publish so live views refetch once. */
319
- var DATA_SOURCE_DEBOUNCE_MS = 300;
320
- var dataSourceTimers = /* @__PURE__ */ new Map();
317
+ /** Per-slug single-flight for a COLLECTION-granularity reconcile — a burst
318
+ * of changes the store couldn't attribute to a record collapses into one
319
+ * pass plus one trailing re-run, mirroring the per-item slots. */
320
+ var collectionSlots = /* @__PURE__ */ new Map();
321
321
  /** Boot entry point: sweep stale active entries, then mount watchers for
322
322
  * every discovered collection and arm the periodic re-discovery poll.
323
323
  * Idempotent — a second call is a no-op. */
@@ -339,8 +339,15 @@ async function startCollectionWatchers(opts = {}) {
339
339
  const triggerMs = opts.triggerTickIntervalMs === void 0 ? TRIGGER_TICK_INTERVAL_MS : opts.triggerTickIntervalMs;
340
340
  if (triggerMs !== null) {
341
341
  triggerTimer = setInterval(() => {
342
- tickTimeTriggers().catch((err) => {
342
+ if (triggerTickRunning) return;
343
+ triggerTickRunning = true;
344
+ const epoch = watcherEpoch;
345
+ triggerTickInFlight = tickTimeTriggers().catch((err) => {
343
346
  log().warn("watcher trigger tick failed", { error: errMsg(err) });
347
+ }).finally(() => {
348
+ if (epoch !== watcherEpoch) return;
349
+ triggerTickRunning = false;
350
+ triggerTickInFlight = null;
344
351
  });
345
352
  }, triggerMs);
346
353
  triggerTimer.unref();
@@ -363,20 +370,22 @@ async function stopCollectionWatchers() {
363
370
  clearInterval(triggerTimer);
364
371
  triggerTimer = null;
365
372
  }
373
+ await triggerTickInFlight;
374
+ triggerTickInFlight = null;
375
+ watcherEpoch += 1;
376
+ triggerTickRunning = false;
366
377
  for (const watcher of watchers.values()) try {
367
- watcher.watcher.close();
378
+ watcher.unsubscribe();
368
379
  } catch {}
369
380
  watchers.clear();
370
381
  itemSlots.clear();
371
- storageSlots.clear();
372
- for (const timer of dataSourceTimers.values()) clearTimeout(timer);
373
- dataSourceTimers.clear();
382
+ collectionSlots.clear();
374
383
  discoveryOpts = {};
375
384
  started = false;
376
385
  }
377
386
  /** Test-only: manually trigger one rediscovery + reconcile pass. */
378
387
  async function _syncWatchersForTesting() {
379
- await syncWatchers();
388
+ return syncWatchers();
380
389
  }
381
390
  /** Test-only: drive one wall-clock tick synchronously, with an optional
382
391
  * injected clock. */
@@ -400,7 +409,6 @@ async function tickTimeTriggers(now = evalNow()) {
400
409
  });
401
410
  continue;
402
411
  }
403
- if (schema.dataSource !== void 0) continue;
404
412
  if (!schema.triggerField && !schema.spawn) continue;
405
413
  await reconcileAllItems(entry.collection, discoveryOpts, now);
406
414
  }
@@ -416,12 +424,14 @@ async function syncWatchers() {
416
424
  collections = await require_discovery.discoverCollections(discoveryOpts);
417
425
  } catch (err) {
418
426
  log().warn("watcher discover failed", { error: errMsg(err) });
419
- return;
427
+ return false;
420
428
  }
421
429
  const vanishedMutated = stopVanishedWatchers(new Set(collections.map((collection) => collection.slug)));
422
430
  const schemaMutated = await reconcileChangedSchemas(collections);
423
431
  const addedMutated = await startNewWatchers(collections);
424
- if (vanishedMutated || schemaMutated || addedMutated) await sweepStaleActiveEntries(discoveryOpts);
432
+ if (!vanishedMutated && !schemaMutated && !addedMutated) return false;
433
+ await sweepStaleActiveEntries(discoveryOpts);
434
+ return true;
425
435
  }
426
436
  function stopVanishedWatchers(liveSlugs) {
427
437
  let mutated = false;
@@ -429,7 +439,7 @@ function stopVanishedWatchers(liveSlugs) {
429
439
  if (liveSlugs.has(slug)) continue;
430
440
  const watcher = watchers.get(slug);
431
441
  if (watcher) try {
432
- watcher.watcher.close();
442
+ watcher.unsubscribe();
433
443
  } catch {}
434
444
  watchers.delete(slug);
435
445
  mutated = true;
@@ -462,7 +472,7 @@ async function reconcileChangedSchemas(collections) {
462
472
  if (storagePathChanged(existing.schemaJson, collection.schema)) {
463
473
  log().info("watcher storage path changed, remounting", { slug: collection.slug });
464
474
  try {
465
- existing.watcher.close();
475
+ existing.unsubscribe();
466
476
  } catch {}
467
477
  watchers.delete(collection.slug);
468
478
  if (collection.schema.dataSource !== void 0) require_discovery.publishCollectionChange({
@@ -474,199 +484,119 @@ async function reconcileChangedSchemas(collections) {
474
484
  }
475
485
  existing.schemaJson = nextJson;
476
486
  existing.collection = collection;
477
- if (collection.schema.dataSource !== void 0) {
478
- log().info("dataSource watcher schema changed, publishing", { slug: collection.slug });
479
- require_discovery.publishCollectionChange({
480
- slug: collection.slug,
481
- op: "upsert"
482
- });
483
- mutated = true;
484
- continue;
485
- }
486
487
  log().info("watcher schema changed, re-reconciling", { slug: collection.slug });
487
488
  await reconcileAllItems(collection, discoveryOpts);
489
+ if (collection.schema.dataSource !== void 0) require_discovery.publishCollectionChange({
490
+ slug: collection.slug,
491
+ op: "upsert"
492
+ });
488
493
  mutated = true;
489
494
  }
490
495
  return mutated;
491
496
  }
497
+ /** Mount a watcher for every collection that doesn't have one yet. Returns
498
+ * whether the watcher SET actually changed — the starters report whether
499
+ * they mounted, because ATTEMPTING is not mounting. A start that throws
500
+ * (logged and swallowed inside the starter) leaves `watchers` untouched, so
501
+ * the collection is retried next tick; counting that as a mutation made
502
+ * `syncWatchers` sweep on every tick for as long as the failure persisted. */
492
503
  async function startNewWatchers(collections) {
493
504
  let mutated = false;
494
505
  for (const collection of collections) {
495
506
  if (watchers.has(collection.slug)) continue;
496
- if (collection.schema.dataSource !== void 0) await startDataSourceWatcher(collection);
497
- else if (collection.schema.storage !== void 0) await startStorageWatcher(collection);
498
- else await startWatcherFor(collection);
499
- mutated = true;
507
+ if (await startWatcherFor(collection)) mutated = true;
500
508
  }
501
509
  return mutated;
502
510
  }
503
- /** Publish one (debounced) change event for a dataSource collection —
504
- * live views refetch the whole collection; no per-row diffing. */
505
- function scheduleDataSourcePublish(slug) {
506
- const existing = dataSourceTimers.get(slug);
507
- if (existing) clearTimeout(existing);
508
- const timer = setTimeout(() => {
509
- dataSourceTimers.delete(slug);
510
- require_discovery.publishCollectionChange({
511
- slug,
512
- op: "upsert"
513
- });
514
- }, DATA_SOURCE_DEBOUNCE_MS);
515
- timer.unref?.();
516
- dataSourceTimers.set(slug, timer);
517
- }
518
- /** Watch a dataSource collection's external data file. The watch mounts
519
- * on the PARENT directory (watching the file itself goes stale after an
520
- * atomic replace — the inode changes) and filters events to the file's
521
- * basename; a null filename (platform quirk) is treated as a hit. No
522
- * reconciler involvement — replacing the CSV just refreshes the views. */
523
- async function startDataSourceWatcher(collection) {
524
- const file = collection.dataSourceFile;
525
- if (file === void 0) return;
526
- const dir = node_path.default.dirname(file);
527
- const base = node_path.default.basename(file);
528
- try {
529
- await (0, node_fs_promises.mkdir)(dir, { recursive: true });
530
- const watcher = (0, node_fs.watch)(dir, { persistent: false }, (_eventType, filename) => {
531
- if (filename !== null && filename !== base) return;
532
- scheduleDataSourcePublish(collection.slug);
533
- });
534
- watcher.on("error", (err) => {
535
- log().warn("dataSource watcher error", {
536
- slug: collection.slug,
537
- error: errMsg(err)
538
- });
539
- });
540
- watchers.set(collection.slug, {
541
- slug: collection.slug,
542
- dataDir: dir,
543
- watcher,
544
- schemaJson: JSON.stringify(collection.schema),
545
- collection
546
- });
547
- log().info("dataSource watcher started", {
548
- slug: collection.slug,
549
- file
550
- });
551
- } catch (err) {
552
- log().warn("dataSource watcher start failed", {
553
- slug: collection.slug,
554
- error: errMsg(err)
555
- });
556
- }
557
- }
558
- /** Watch a `storage` collection's database file. One db file holds every
559
- * record, so an event can't name WHICH record changed — each (debounced)
560
- * event runs a full `reconcileAllItems` pass (bells / spawn) plus a
561
- * change publish so views refetch after EXTERNAL edits too (host writes
562
- * already publish their own change events; the extra ping is debounced
563
- * and idempotent). Mounts on the parent dir, same as the dataSource
564
- * watcher, so an atomic replace can't strand the watch. */
565
- async function startStorageWatcher(collection) {
566
- const file = collection.storageFile;
567
- if (file === void 0) return;
568
- const dir = node_path.default.dirname(file);
569
- const base = node_path.default.basename(file);
511
+ /** Mount one collection's change subscription, whatever its backend.
512
+ *
513
+ * The store decides how to detect a change and at what granularity; this
514
+ * decides what to do about one. That split is the point: adding a backend
515
+ * means implementing `watch` on its store, not another branch here.
516
+ *
517
+ * A store without `watch` cannot report external changes at all — it is
518
+ * still registered (so bells reconcile at boot and on the clock tick), just
519
+ * without live updates. */
520
+ async function startWatcherFor(collection) {
521
+ const { slug } = collection;
570
522
  try {
571
- await (0, node_fs_promises.mkdir)(dir, { recursive: true });
572
523
  await reconcileAllItems(collection, discoveryOpts);
573
- const watcher = (0, node_fs.watch)(dir, { persistent: false }, (_eventType, rawFilename) => {
574
- const filename = rawFilename === null ? null : String(rawFilename);
575
- if (filename !== null && !filename.startsWith(base)) return;
576
- scheduleStorageReconcile(collection.slug).catch((err) => {
577
- log().warn("storage watcher reconcile failed", {
578
- slug: collection.slug,
524
+ const store = require_discovery.storeFor(collection, discoveryOpts);
525
+ const unsubscribe = store.watch ? await store.watch((change) => {
526
+ handleStoreChange(slug, change).catch((err) => {
527
+ log().warn("store change handling failed", {
528
+ slug,
579
529
  error: errMsg(err)
580
530
  });
581
531
  });
582
- });
583
- watcher.on("error", (err) => {
584
- log().warn("storage watcher error", {
585
- slug: collection.slug,
586
- error: errMsg(err)
587
- });
588
- });
589
- watchers.set(collection.slug, {
590
- slug: collection.slug,
591
- dataDir: dir,
592
- watcher,
532
+ }) : () => {};
533
+ if (unsubscribe === null) {
534
+ log().warn("collection watcher could not arm, retrying next sync", { slug });
535
+ return false;
536
+ }
537
+ watchers.set(slug, {
538
+ slug,
539
+ dataDir: collection.dataDir,
540
+ unsubscribe,
593
541
  schemaJson: JSON.stringify(collection.schema),
594
542
  collection
595
543
  });
596
- log().info("storage watcher started", {
597
- slug: collection.slug,
598
- file
544
+ log().info("collection watcher started", {
545
+ slug,
546
+ live: store.watch !== void 0
599
547
  });
548
+ return true;
600
549
  } catch (err) {
601
- log().warn("storage watcher start failed", {
602
- slug: collection.slug,
550
+ log().warn("collection watcher start failed", {
551
+ slug,
603
552
  error: errMsg(err)
604
553
  });
554
+ return false;
605
555
  }
606
556
  }
607
- function scheduleStorageReconcile(slug) {
608
- return runSingleFlight(storageSlots, slug, async () => {
557
+ /** React to one reported change. Backend-agnostic by construction — it sees
558
+ * only the granularity the store reported.
559
+ *
560
+ * `item`: reconcile just that record, then publish it. `collection`: the
561
+ * store couldn't say which record, so re-derive everything and pair it with
562
+ * a sweep — a record deleted remotely leaves a bell that a walk over the
563
+ * SURVIVING records can never clear. */
564
+ async function handleStoreChange(slug, change) {
565
+ if (change.kind === "collection") {
566
+ await scheduleCollectionReconcile(slug);
567
+ return;
568
+ }
569
+ const current = watchers.get(slug)?.collection;
570
+ if (!current) return;
571
+ await scheduleItemReconcile(current, change.itemId);
572
+ }
573
+ /** Full re-derivation for a collection-granularity change, single-flighted
574
+ * per slug so a burst of writes collapses into one pass plus one trailing
575
+ * re-run. */
576
+ function scheduleCollectionReconcile(slug) {
577
+ return runSingleFlight(collectionSlots, slug, async () => {
609
578
  const collection = await require_discovery.loadCollection(slug, discoveryOpts);
610
579
  if (!collection) return;
611
580
  await reconcileAllItems(collection, discoveryOpts);
612
581
  await sweepStaleActiveEntries(discoveryOpts);
613
- require_discovery.publishCollectionChange({
582
+ }, () => {
583
+ safePublish({
614
584
  slug,
615
585
  op: "upsert"
616
586
  });
587
+ return Promise.resolve();
617
588
  });
618
589
  }
619
- /** Test-only: drive one storage-collection reconcile pass directly. */
620
- function _scheduleStorageReconcileForTesting(slug) {
621
- return scheduleStorageReconcile(slug);
590
+ /** Test-only: feed one store-reported change through the same path a live
591
+ * subscription uses, so a test can pin how a change is reacted to without
592
+ * depending on fs.watch timing. */
593
+ function _handleStoreChangeForTesting(slug, change) {
594
+ return handleStoreChange(slug, change);
622
595
  }
623
- async function startWatcherFor(collection) {
624
- const { slug, schema, dataDir } = collection;
625
- try {
626
- await (0, node_fs_promises.mkdir)(dataDir, { recursive: true });
627
- await reconcileAllItems(collection, discoveryOpts);
628
- const watcher = (0, node_fs.watch)(dataDir, { persistent: false }, (_eventType, filename) => {
629
- onEvent(slug, filename).catch((err) => {
630
- log().warn("watcher event failed", {
631
- slug,
632
- filename,
633
- error: errMsg(err)
634
- });
635
- });
636
- });
637
- watcher.on("error", (err) => {
638
- log().warn("watcher error", {
639
- slug,
640
- error: errMsg(err)
641
- });
642
- });
643
- watchers.set(slug, {
644
- slug,
645
- dataDir,
646
- watcher,
647
- schemaJson: JSON.stringify(schema),
648
- collection
649
- });
650
- log().info("watcher started", {
651
- slug,
652
- dataDir
653
- });
654
- } catch (err) {
655
- log().warn("watcher start failed", {
656
- slug,
657
- error: errMsg(err)
658
- });
659
- }
596
+ /** Test-only: drive one collection-granularity reconcile directly. */
597
+ function _scheduleCollectionReconcileForTesting(slug) {
598
+ return scheduleCollectionReconcile(slug);
660
599
  }
661
- /** Test-only: the per-key single-flight scheduler. Exported so test code
662
- * can drive rapid-fire calls directly and observe the trailing coalesce
663
- * — `fs.watch` event timing is too flaky to assert against.
664
- *
665
- * Single-flight semantics: while a reconcile is in flight for a given
666
- * (slug, itemId), additional events on the same key set `pending = true`
667
- * and return — the running reconcile re-runs once after it completes.
668
- * This collapses fs.watch's rapid-fire bursts (atomic rename surfaces as
669
- * 2-3 events) into a single reconcile + one trailing re-run. */
670
600
  function _scheduleItemReconcileForTesting(collection, itemId) {
671
601
  return scheduleItemReconcile(collection, itemId);
672
602
  }
@@ -740,30 +670,11 @@ function safePublish(payload) {
740
670
  });
741
671
  }
742
672
  }
743
- /** Handle a single fs.watch event. Re-loads the collection (schema may
744
- * have changed since startup), filters out non-record files, and
745
- * forwards to the single-flighted reconciler. `filename === null` (rare,
746
- * platform-specific) triggers a full directory rescan to be safe. */
747
- async function onEvent(slug, filename) {
748
- const collection = await require_discovery.loadCollection(slug, discoveryOpts);
749
- if (!collection) return;
750
- if (filename === null) {
751
- try {
752
- await reconcileAllItems(collection, discoveryOpts);
753
- await sweepStaleActiveEntries(discoveryOpts);
754
- } finally {
755
- safePublish({ slug });
756
- }
757
- return;
758
- }
759
- const name = typeof filename === "string" ? filename : filename.toString("utf-8");
760
- if (!name.endsWith(".json") || name.startsWith(".")) return;
761
- await scheduleItemReconcile(collection, name.slice(0, -5));
762
- }
763
673
  //#endregion
674
+ exports._handleStoreChangeForTesting = _handleStoreChangeForTesting;
764
675
  exports._resetReconcilerLocksForTesting = _resetReconcilerLocksForTesting;
676
+ exports._scheduleCollectionReconcileForTesting = _scheduleCollectionReconcileForTesting;
765
677
  exports._scheduleItemReconcileForTesting = _scheduleItemReconcileForTesting;
766
- exports._scheduleStorageReconcileForTesting = _scheduleStorageReconcileForTesting;
767
678
  exports._syncWatchersForTesting = _syncWatchersForTesting;
768
679
  exports._tickTimeTriggersForTesting = _tickTimeTriggersForTesting;
769
680
  exports.clearItemNotification = clearItemNotification;