@agent-native/core 0.187.0-nightly-20260924172231 → 0.187.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 (35) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +11 -0
  3. package/corpus/templates/analytics/app/i18n/en-US.ts +1 -0
  4. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +101 -11
  5. package/corpus/templates/analytics/changelog/2026-09-24-session-list-hides-0m-recordings-by-default.md +6 -0
  6. package/corpus/templates/assets/CHANGELOG.md +6 -0
  7. package/corpus/templates/brain/actions/backfill-search-embeddings.ts +337 -0
  8. package/corpus/templates/brain/actions/get-brain-health.ts +1 -1
  9. package/corpus/templates/brain/app/lib/brain.ts +18 -0
  10. package/corpus/templates/brain/jobs/process-ingest-queue.ts +25 -3
  11. package/corpus/templates/brain/server/lib/brain-health.ts +197 -57
  12. package/corpus/templates/brain/server/lib/demo.ts +25 -4
  13. package/corpus/templates/brain/server/lib/hybrid-search.ts +4 -1
  14. package/corpus/templates/brain/server/lib/ingest-queue.ts +1 -0
  15. package/corpus/templates/brain/server/lib/search-index.ts +254 -17
  16. package/corpus/templates/brain/server/lib/search.ts +27 -0
  17. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  18. package/corpus/templates/clips/CHANGELOG.md +10 -0
  19. package/corpus/templates/content/CHANGELOG.md +22 -0
  20. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +559 -227
  21. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +25 -1
  22. package/corpus/templates/design/CHANGELOG.md +36 -0
  23. package/corpus/templates/dispatch/CHANGELOG.md +7 -0
  24. package/corpus/templates/mail/CHANGELOG.md +17 -0
  25. package/corpus/templates/slides/CHANGELOG.md +7 -0
  26. package/dist/automation/index.d.ts +1 -1
  27. package/dist/collab/awareness.d.ts +2 -2
  28. package/dist/collab/struct-routes.d.ts +1 -1
  29. package/dist/embeddings/index.d.ts +5 -0
  30. package/dist/embeddings/index.js +44 -11
  31. package/dist/observability/routes.d.ts +5 -5
  32. package/dist/progress/routes.d.ts +1 -1
  33. package/dist/secrets/routes.d.ts +6 -6
  34. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  35. package/package.json +4 -4
@@ -3,6 +3,7 @@ import {
3
3
  availableEmbeddingFamilies,
4
4
  defaultEmbeddingFamily,
5
5
  type EmbeddingFamily,
6
+ readEmbeddingFamilyAvailability,
6
7
  } from "@agent-native/core/embeddings";
7
8
  import {
8
9
  deletePgVectors,
@@ -39,6 +40,25 @@ export const BRAIN_MAX_EMBEDDED_BURSTS = 12;
39
40
 
40
41
  export type BrainSearchArtifact = z.infer<typeof artifactSchema>;
41
42
 
43
+ export type BrainEmbeddingReadinessStatus =
44
+ | "ready"
45
+ | "not-configured"
46
+ | "ambiguous"
47
+ | "unavailable";
48
+
49
+ export interface BrainEmbeddingReadiness {
50
+ status: BrainEmbeddingReadinessStatus;
51
+ ready: boolean;
52
+ configuredProviders: string[];
53
+ unavailableProviders: string[];
54
+ configuredFamilies: number;
55
+ provider: string | null;
56
+ model: string | null;
57
+ embeddingSetId: string | null;
58
+ dimensions: number | null;
59
+ warning: string | null;
60
+ }
61
+
42
62
  export interface SearchIndexCapture {
43
63
  id: string;
44
64
  sourceId: string;
@@ -245,11 +265,225 @@ export function burstRows(
245
265
  return rows;
246
266
  }
247
267
 
268
+ export function embeddingReadinessFromFamilies(
269
+ families: readonly EmbeddingFamily[],
270
+ unavailableProviders: readonly string[] = [],
271
+ ): BrainEmbeddingReadiness {
272
+ const configuredProviders = Array.from(
273
+ new Set(families.map((candidate) => candidate.provider)),
274
+ );
275
+ if (unavailableProviders.length) {
276
+ return {
277
+ status: "unavailable",
278
+ ready: false,
279
+ configuredProviders,
280
+ unavailableProviders: [...unavailableProviders],
281
+ configuredFamilies: families.length,
282
+ provider: null,
283
+ model: null,
284
+ embeddingSetId: null,
285
+ dimensions: null,
286
+ warning:
287
+ "Embedding credential status is temporarily unavailable. Retry before indexing.",
288
+ };
289
+ }
290
+ const family = defaultEmbeddingFamily(families);
291
+ if (family) {
292
+ return {
293
+ status: "ready",
294
+ ready: true,
295
+ configuredProviders: [family.provider],
296
+ unavailableProviders: [],
297
+ configuredFamilies: 1,
298
+ provider: family.provider,
299
+ model: family.model,
300
+ embeddingSetId: family.id,
301
+ dimensions: family.dimensions,
302
+ warning: null,
303
+ };
304
+ }
305
+ return {
306
+ status: families.length ? "ambiguous" : "not-configured",
307
+ ready: false,
308
+ configuredProviders,
309
+ unavailableProviders: [],
310
+ configuredFamilies: families.length,
311
+ provider: null,
312
+ model: null,
313
+ embeddingSetId: null,
314
+ dimensions: null,
315
+ warning: families.length
316
+ ? "Configure exactly one embedding provider."
317
+ : "Configure one embedding provider to enable semantic retrieval.",
318
+ };
319
+ }
320
+
321
+ export async function readEmbeddingReadiness(): Promise<BrainEmbeddingReadiness> {
322
+ const availability = await readEmbeddingFamilyAvailability();
323
+ return embeddingReadinessFromFamilies(
324
+ availability.families,
325
+ availability.unavailableProviders,
326
+ );
327
+ }
328
+
248
329
  async function configuredEmbeddingFamily(): Promise<EmbeddingFamily | null> {
249
330
  const families = await availableEmbeddingFamilies();
250
331
  return defaultEmbeddingFamily(families);
251
332
  }
252
333
 
334
+ export interface CaptureEmbeddingCoverage {
335
+ complete: boolean;
336
+ artifactEmbedded: boolean;
337
+ expectedBursts: number;
338
+ embeddedBursts: number;
339
+ }
340
+
341
+ export function captureAudienceIndexingFailureReason(
342
+ assignments: readonly unknown[],
343
+ ): "no-active-audience" | "multiple-audience-assignments" | null {
344
+ if (!assignments.length) return "no-active-audience";
345
+ return assignments.length === 1 ? null : "multiple-audience-assignments";
346
+ }
347
+
348
+ export function captureEmbeddingCoverageFromTargets(input: {
349
+ artifactId: string;
350
+ burstIds: string[];
351
+ embeddings: Array<{ targetType: string; targetId: string }>;
352
+ }): CaptureEmbeddingCoverage {
353
+ const embeddedTargets = new Set(
354
+ input.embeddings.map((row) => `${row.targetType}:${row.targetId}`),
355
+ );
356
+ const artifactEmbedded = embeddedTargets.has(`artifact:${input.artifactId}`);
357
+ const embeddedBursts = input.burstIds.filter((id) =>
358
+ embeddedTargets.has(`burst:${id}`),
359
+ ).length;
360
+ return {
361
+ complete: artifactEmbedded && embeddedBursts === input.burstIds.length,
362
+ artifactEmbedded,
363
+ expectedBursts: input.burstIds.length,
364
+ embeddedBursts,
365
+ };
366
+ }
367
+
368
+ export async function readCaptureEmbeddingCoverage(
369
+ captureId: string,
370
+ embeddingSetId: string,
371
+ ): Promise<CaptureEmbeddingCoverage> {
372
+ const db = getDb();
373
+ const assignments = await db
374
+ .select({
375
+ audienceId: schema.brainCaptureAudiences.audienceId,
376
+ aclHash: schema.brainCaptureAudiences.aclHash,
377
+ })
378
+ .from(schema.brainCaptureAudiences)
379
+ .where(eq(schema.brainCaptureAudiences.captureId, captureId));
380
+ if (captureAudienceIndexingFailureReason(assignments)) {
381
+ return {
382
+ complete: false,
383
+ artifactEmbedded: false,
384
+ expectedBursts: 0,
385
+ embeddedBursts: 0,
386
+ };
387
+ }
388
+ const assignment = assignments[0]!;
389
+ const [artifact] = await db
390
+ .select({
391
+ id: schema.brainSearchArtifacts.id,
392
+ contentHash: schema.brainSearchArtifacts.contentHash,
393
+ sensitivityPolicyVersion:
394
+ schema.brainSearchArtifacts.sensitivityPolicyVersion,
395
+ aclHash: schema.brainSearchArtifacts.aclHash,
396
+ indexVersion: schema.brainSearchArtifacts.indexVersion,
397
+ })
398
+ .from(schema.brainSearchArtifacts)
399
+ .innerJoin(
400
+ schema.brainRawCaptures,
401
+ eq(schema.brainSearchArtifacts.captureId, schema.brainRawCaptures.id),
402
+ )
403
+ .where(
404
+ and(
405
+ eq(schema.brainRawCaptures.id, captureId),
406
+ eq(schema.brainRawCaptures.sensitivityDisposition, "allowed"),
407
+ eq(schema.brainSearchArtifacts.status, "active"),
408
+ eq(schema.brainSearchArtifacts.audienceId, assignment.audienceId),
409
+ eq(schema.brainSearchArtifacts.aclHash, assignment.aclHash),
410
+ eq(
411
+ schema.brainSearchArtifacts.contentHash,
412
+ schema.brainRawCaptures.contentHash,
413
+ ),
414
+ eq(
415
+ schema.brainSearchArtifacts.sensitivityPolicyVersion,
416
+ schema.brainRawCaptures.sensitivityPolicyVersion,
417
+ ),
418
+ eq(
419
+ schema.brainSearchArtifacts.aclHash,
420
+ schema.brainRawCaptures.audienceAclHash,
421
+ ),
422
+ eq(
423
+ schema.brainSearchArtifacts.indexVersion,
424
+ BRAIN_SEARCH_INDEX_VERSION,
425
+ ),
426
+ ),
427
+ )
428
+ .limit(1);
429
+ if (!artifact) {
430
+ return {
431
+ complete: false,
432
+ artifactEmbedded: false,
433
+ expectedBursts: 0,
434
+ embeddedBursts: 0,
435
+ };
436
+ }
437
+ const bursts = await db
438
+ .select({ id: schema.brainSearchBursts.id })
439
+ .from(schema.brainSearchBursts)
440
+ .where(
441
+ and(
442
+ eq(schema.brainSearchBursts.artifactId, artifact.id),
443
+ eq(schema.brainSearchBursts.indexed, 1),
444
+ eq(schema.brainSearchBursts.contentHash, artifact.contentHash),
445
+ eq(schema.brainSearchBursts.indexVersion, artifact.indexVersion),
446
+ ),
447
+ );
448
+ const targetIds = [artifact.id, ...bursts.map((burst) => burst.id)];
449
+ const embeddings = await db
450
+ .select({
451
+ targetType: schema.brainSearchEmbeddings.targetType,
452
+ targetId: schema.brainSearchEmbeddings.targetId,
453
+ })
454
+ .from(schema.brainSearchEmbeddings)
455
+ .where(
456
+ and(
457
+ eq(schema.brainSearchEmbeddings.status, "active"),
458
+ eq(schema.brainSearchEmbeddings.embeddingSetId, embeddingSetId),
459
+ eq(schema.brainSearchEmbeddings.contentHash, artifact.contentHash),
460
+ eq(
461
+ schema.brainSearchEmbeddings.sensitivityPolicyVersion,
462
+ artifact.sensitivityPolicyVersion,
463
+ ),
464
+ eq(schema.brainSearchEmbeddings.aclHash, artifact.aclHash),
465
+ eq(schema.brainSearchEmbeddings.indexVersion, artifact.indexVersion),
466
+ inArray(schema.brainSearchEmbeddings.targetId, targetIds),
467
+ ),
468
+ );
469
+ return captureEmbeddingCoverageFromTargets({
470
+ artifactId: artifact.id,
471
+ burstIds: bursts.map((burst) => burst.id),
472
+ embeddings,
473
+ });
474
+ }
475
+
476
+ export async function embedSearchTexts(
477
+ family: EmbeddingFamily | null,
478
+ texts: string[],
479
+ ): Promise<number[][] | null> {
480
+ if (!family) return null;
481
+ return family.embed(
482
+ texts.map((text) => ({ text })),
483
+ "document",
484
+ );
485
+ }
486
+
253
487
  async function indexExternalSearchLanes(input: {
254
488
  artifactId: string;
255
489
  artifact: BrainSearchArtifact;
@@ -276,7 +510,6 @@ async function indexExternalSearchLanes(input: {
276
510
  namespace: SEARCH_NAMESPACE,
277
511
  });
278
512
  const family = await configuredEmbeddingFamily();
279
- if (!family || !true) return;
280
513
  const targets = [
281
514
  {
282
515
  targetType: "artifact" as const,
@@ -291,10 +524,11 @@ async function indexExternalSearchLanes(input: {
291
524
  text: input.burstBodies[index] ?? "",
292
525
  })),
293
526
  ].filter((target) => target.text.trim());
294
- const vectors = await family.embed(
295
- targets.map((target) => ({ text: target.text })),
296
- "document",
527
+ const vectors = await embedSearchTexts(
528
+ family,
529
+ targets.map((target) => target.text),
297
530
  );
531
+ if (!family || !vectors) return;
298
532
  await ensurePgVectorIndex(dbExec, family.dimensions, {
299
533
  namespace: SEARCH_NAMESPACE,
300
534
  });
@@ -633,20 +867,23 @@ export async function indexBrainCapture(captureId: string): Promise<{
633
867
  })
634
868
  .from(schema.brainCaptureAudiences)
635
869
  .where(eq(schema.brainCaptureAudiences.captureId, captureId));
636
- let indexed = 0;
637
- for (const audience of audiences) {
638
- const result = await indexCaptureForSearch({
639
- capture: {
640
- ...capture,
641
- sensitivityDisposition: capture.sensitivityDisposition,
642
- },
643
- audience,
644
- id: nanoid(),
645
- now: nowIso(),
646
- });
647
- if (result.indexed) indexed += 1;
870
+ const audienceFailure = captureAudienceIndexingFailureReason(audiences);
871
+ if (audienceFailure) {
872
+ await unindexBrainCapture(captureId);
873
+ return { indexed: 0, reason: audienceFailure };
648
874
  }
649
- return indexed ? { indexed } : { indexed: 0, reason: "no-active-audience" };
875
+ const result = await indexCaptureForSearch({
876
+ capture: {
877
+ ...capture,
878
+ sensitivityDisposition: capture.sensitivityDisposition,
879
+ },
880
+ audience: audiences[0]!,
881
+ id: nanoid(),
882
+ now: nowIso(),
883
+ });
884
+ return result.indexed
885
+ ? { indexed: 1 }
886
+ : { indexed: 0, reason: result.reason ?? "search-index-failed" };
650
887
  }
651
888
 
652
889
  export async function unindexBrainCapture(captureId: string): Promise<void> {
@@ -298,6 +298,33 @@ export function buildSnippet(
298
298
  return buildSearchSnippet(value, terms, maxLength);
299
299
  }
300
300
 
301
+ export function citationEvidenceMatchesCapture(
302
+ citation:
303
+ | {
304
+ quote?: string | null;
305
+ preview?: string | null;
306
+ verbatim?: boolean;
307
+ }
308
+ | null
309
+ | undefined,
310
+ captureContent: string,
311
+ ) {
312
+ const redactedContent = redactSensitiveText(captureContent)
313
+ .replace(/\s+/g, " ")
314
+ .trim();
315
+ const quote = citation?.quote?.trim();
316
+ if (quote) return captureContent.includes(quote);
317
+ if (citation?.verbatim !== false) return false;
318
+ const preview = citation.preview
319
+ ?.trim()
320
+ .replace(/^\.\.\./, "")
321
+ .replace(/\.\.\.$/, "")
322
+ .trim()
323
+ .replace(/\s+/g, " ");
324
+ if (!preview) return false;
325
+ return redactedContent.includes(preview);
326
+ }
327
+
301
328
  export function scoreSearchText(
302
329
  fields: {
303
330
  title?: string | null;
@@ -9,6 +9,12 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
9
9
 
10
10
  - Calendar opens faster by loading translated catalogs only when you need them.
11
11
 
12
+ ### Fixed
13
+
14
+ - Bookings no longer confirm without their configured Zoom meeting
15
+ - Release a booking slot when Zoom creation never starts
16
+ - Zoom booking timeouts keep the slot reserved to prevent duplicate meetings.
17
+
12
18
  ## 2026-09-22
13
19
 
14
20
  ### Improved
@@ -7,10 +7,19 @@ from the command menu (Cmd+K → "What's new") or from Settings.
7
7
 
8
8
  ### Improved
9
9
 
10
+ - Clip share sidebars stay expanded in stacked layouts and use a floating, borderless control when collapsed.
11
+ - Clip viewers can collapse the comments, transcript, and Agent sidebar
12
+ - Search results clarify where a match appears in the video.
13
+ - Search results show where a transcript or comment match appears
10
14
  - Insights charts load only when opened, keeping the recording library faster.
11
15
 
12
16
  ### Fixed
13
17
 
18
+ - Comment fields grow as you type so longer messages stay visible
19
+ - New recordings stay loading until their share is available
20
+ - Playback errors no longer cover the video player controls
21
+ - Trash recordings are excluded from search results.
22
+ - Opening a trashed recording now links its breadcrumb back to Trash.
14
23
  - Long recording menus scroll within the visible space so every action stays reachable.
15
24
 
16
25
  ## 2026-09-22
@@ -28,6 +37,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
28
37
 
29
38
  ### Fixed
30
39
 
40
+ - Stopped Clips AI actions now clear the in-progress status.
31
41
  - Clips keeps uploaded videos visible while the library refreshes and localizes Loom import failures.
32
42
  - Clips recovers dropped uploads after a lost finalization response and clears saving feedback after successful saves.
33
43
  - The desktop app stops re-checking flags and meetings every few seconds after your session expires, and resumes when you sign in again.
@@ -3,6 +3,28 @@
3
3
  All notable user-facing changes to Agent-Native Content are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-23
7
+
8
+ ### Added
9
+
10
+ - Every page in the sidebar now has a "…" menu to rename, duplicate, move, copy its link, open it in a new tab, or move it to Trash, and it shows who last edited the page. Duplicate copies sub-pages too, and Move can take a page and its sub-pages to another workspace after warning you that its sharing will change.
11
+
12
+ ### Improved
13
+
14
+ - The sidebar is easier to scan: the page you're on is highlighted wherever it appears, nested pages show guide lines and Trash stays in reach below the page list.
15
+
16
+ ### Fixed
17
+
18
+ - Tables inserted in the visual editor now let you resize columns.
19
+ - Markdown tables with aligned columns now open as editable tables in pages, keeping column alignment and all cell content.
20
+ - The Trash view no longer highlights a document that is not in Trash.
21
+
22
+ ## 2026-09-22
23
+
24
+ ### Fixed
25
+
26
+ - Comments, replies, and suggested-edit decisions now appear immediately without leaving duplicate text in the composer while they save.
27
+
6
28
  ## 2026-09-21
7
29
 
8
30
  ### Improved