@agent-native/core-corpus 0.201.0-nightly-20261005153421 → 0.201.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 (235) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +9 -2
  3. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +14 -4
  4. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +12 -7
  5. package/corpus/templates/analytics/CHANGELOG.md +6 -0
  6. package/corpus/templates/analytics/README.md +2 -2
  7. package/corpus/templates/analytics/changelog/2026-10-02-events-with-a-very-long-name-app-or-page-path-no-longer-caus.md +6 -0
  8. package/corpus/templates/analytics/package.json +1 -0
  9. package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +9 -19
  10. package/corpus/templates/analytics/server/handlers/session-replay.ts +34 -47
  11. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -6
  12. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -36
  13. package/corpus/templates/analytics/server/lib/indexed-text.ts +53 -0
  14. package/corpus/templates/analytics/server/lib/request-errors.ts +73 -0
  15. package/corpus/templates/analytics/server/lib/session-event-index.ts +6 -16
  16. package/corpus/templates/analytics/server/lib/session-replay.ts +12 -2
  17. package/corpus/templates/analytics/server/plugins/auth.ts +2 -1
  18. package/corpus/templates/analytics/server/plugins/otel.ts +6 -0
  19. package/corpus/templates/assets/.agents/skills/security/SKILL.md +9 -2
  20. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +12 -7
  21. package/corpus/templates/assets/CHANGELOG.md +7 -0
  22. package/corpus/templates/assets/README.md +2 -2
  23. package/corpus/templates/assets/package.json +1 -0
  24. package/corpus/templates/assets/server/plugins/auth.ts +2 -1
  25. package/corpus/templates/assets/server/plugins/otel.ts +6 -0
  26. package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +2 -1
  27. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +5 -4
  28. package/corpus/templates/brain/.agents/skills/security/SKILL.md +9 -2
  29. package/corpus/templates/brain/.agents/skills/storing-data/SKILL.md +12 -7
  30. package/corpus/templates/brain/CHANGELOG.md +6 -0
  31. package/corpus/templates/brain/README.md +12 -8
  32. package/corpus/templates/brain/changelog/2026-10-05-private-slack-channels-are-searchable-org-wide.md +6 -0
  33. package/corpus/templates/brain/package.json +1 -0
  34. package/corpus/templates/brain/server/lib/audiences.ts +1 -98
  35. package/corpus/templates/brain/server/lib/connectors.ts +2 -166
  36. package/corpus/templates/brain/server/plugins/otel.ts +6 -0
  37. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +9 -2
  38. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +12 -7
  39. package/corpus/templates/calendar/CHANGELOG.md +7 -0
  40. package/corpus/templates/calendar/README.md +2 -2
  41. package/corpus/templates/calendar/package.json +1 -0
  42. package/corpus/templates/calendar/server/plugins/auth.ts +2 -1
  43. package/corpus/templates/calendar/server/plugins/otel.ts +6 -0
  44. package/corpus/templates/chat/.agents/skills/security/SKILL.md +9 -2
  45. package/corpus/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
  46. package/corpus/templates/chat/README.md +2 -2
  47. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +9 -0
  48. package/corpus/templates/clips/.agents/skills/security/SKILL.md +9 -2
  49. package/corpus/templates/clips/CHANGELOG.md +24 -0
  50. package/corpus/templates/clips/README.md +2 -2
  51. package/corpus/templates/clips/actions/lib/audio-chunk-list.ts +23 -0
  52. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +187 -292
  53. package/corpus/templates/clips/actions/lib/chunked-transcription.ts +151 -0
  54. package/corpus/templates/clips/actions/lib/recording-audio-source.ts +191 -0
  55. package/corpus/templates/clips/actions/request-transcript.ts +81 -135
  56. package/corpus/templates/clips/actions/search-recordings.ts +19 -5
  57. package/corpus/templates/clips/app/components/clips-command-menu.tsx +5 -0
  58. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -1
  59. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -0
  60. package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
  61. package/corpus/templates/clips/app/lib/local-recording-upload.ts +13 -6
  62. package/corpus/templates/clips/app/lib/recording-section.ts +1 -1
  63. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +7 -5
  64. package/corpus/templates/clips/app/routes/record.tsx +16 -6
  65. package/corpus/templates/clips/changelog/2026-10-02-long-uploads-an-hour-or-more-now-transcribe-reliably-instead.md +6 -0
  66. package/corpus/templates/clips/changelog/2026-10-05-confirming-discard-recording-now-returns-you-to-your-library.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recording-now-removes-its-failed-upload-card-fr.md +6 -0
  68. package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recovered-recording-no-longer-warns-that-it-s-o.md +6 -0
  69. package/corpus/templates/clips/changelog/2026-10-05-search-results-identify-recordings-in-trash-and-open-them-wi.md +6 -0
  70. package/corpus/templates/clips/package.json +1 -0
  71. package/corpus/templates/clips/server/lib/builder-cdn.ts +5 -0
  72. package/corpus/templates/clips/server/lib/builder-media-compression.ts +2 -2
  73. package/corpus/templates/clips/server/plugins/auth.ts +2 -1
  74. package/corpus/templates/clips/server/plugins/otel.ts +6 -0
  75. package/corpus/templates/clips/shared/transcript-failure.ts +5 -0
  76. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
  77. package/corpus/templates/content/.agents/skills/security/SKILL.md +9 -2
  78. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +12 -7
  79. package/corpus/templates/content/CHANGELOG.md +41 -0
  80. package/corpus/templates/content/README.md +2 -2
  81. package/corpus/templates/content/actions/_database-navigation.ts +523 -344
  82. package/corpus/templates/content/actions/_database-row-mutation.ts +44 -10
  83. package/corpus/templates/content/actions/_database-setup-discovery.ts +9 -3
  84. package/corpus/templates/content/actions/_database-utils.ts +299 -271
  85. package/corpus/templates/content/actions/_property-utils.ts +293 -190
  86. package/corpus/templates/content/actions/_relation-values.ts +3 -1
  87. package/corpus/templates/content/actions/get-content-database.ts +1 -0
  88. package/corpus/templates/content/actions/query-content-database-items.ts +32 -5
  89. package/corpus/templates/content/actions/set-document-property.ts +17 -11
  90. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +21 -6
  91. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -43
  92. package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +159 -50
  93. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +12 -28
  94. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +6 -1
  95. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +139 -70
  96. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +16 -1
  97. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +180 -75
  98. package/corpus/templates/content/app/components/editor/database/DatabaseViewSkeleton.tsx +76 -0
  99. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +60 -2
  100. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +41 -42
  101. package/corpus/templates/content/app/components/editor/document-editor-layout.ts +18 -0
  102. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +11 -3
  103. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +15 -7
  104. package/corpus/templates/content/app/components/editor/suggestions/model.ts +13 -0
  105. package/corpus/templates/content/app/components/editor/suggestions/read-only-blocks.ts +136 -0
  106. package/corpus/templates/content/app/components/editor/table-view.ts +73 -0
  107. package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +1 -1
  108. package/corpus/templates/content/app/components/layout/Layout.tsx +5 -1
  109. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +57 -16
  110. package/corpus/templates/content/app/components/sidebar/MovePageDialog.tsx +4 -4
  111. package/corpus/templates/content/app/components/sidebar/SidebarNavigationRow.tsx +34 -10
  112. package/corpus/templates/content/app/global.css +16 -8
  113. package/corpus/templates/content/app/hooks/content-action-refresh.ts +14 -13
  114. package/corpus/templates/content/app/hooks/use-content-database.ts +72 -9
  115. package/corpus/templates/content/app/hooks/use-content-personal-navigation.ts +7 -3
  116. package/corpus/templates/content/app/hooks/use-documents.ts +27 -0
  117. package/corpus/templates/content/app/i18n-data.ts +3 -2
  118. package/corpus/templates/content/app/lib/content-landing.ts +3 -31
  119. package/corpus/templates/content/app/lib/files-navigation.ts +182 -10
  120. package/corpus/templates/content/app/lib/files-root-hint.ts +20 -32
  121. package/corpus/templates/content/app/lib/page-startup-hints.ts +108 -0
  122. package/corpus/templates/content/app/lib/private-link-redirect.ts +25 -0
  123. package/corpus/templates/content/app/lib/sidebar-layout-hint.ts +34 -1
  124. package/corpus/templates/content/app/lib/startup-timing.ts +2 -0
  125. package/corpus/templates/content/app/root.tsx +3 -1
  126. package/corpus/templates/content/app/routes/_app.home.tsx +10 -19
  127. package/corpus/templates/content/app/routes/p.$id.tsx +23 -5
  128. package/corpus/templates/content/changelog/2026-10-02-a-page-link-you-cant-open-now-says-so-instead-of-opening-an.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-02-after-a-refresh-the-files-sidebar-shows-the-open-page-s-fold.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-02-large-collections-open-faster.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-02-pages-stay-inside-the-window-at-every-width-long-inline-code.md +6 -0
  132. package/corpus/templates/content/changelog/2026-10-02-pages-with-open-comments-and-collections-with-sorted-or-filt.md +6 -0
  133. package/corpus/templates/content/changelog/2026-10-02-suggest-edits-works-on-pages-written-by-agents-including-pag.md +6 -0
  134. package/corpus/templates/content/changelog/2026-10-02-while-you-suggest-edits-tables-and-other-blocks-that-suggest.md +6 -0
  135. package/corpus/templates/content/changelog/2026-10-05-collection-view-context-now-survives-quick-page-changes-when.md +6 -0
  136. package/corpus/templates/content/changelog/2026-10-05-collection-views-keep-their-saved-rows-accurate-during-refre.md +6 -0
  137. package/corpus/templates/content/changelog/2026-10-05-exact-saved-view-links-keep-collection-retry-available.md +6 -0
  138. package/corpus/templates/content/changelog/2026-10-05-expanding-a-files-folder-now-shares-one-navigation-read-acro.md +6 -0
  139. package/corpus/templates/content/changelog/2026-10-05-files-navigation-retries-temporary-failures-consistently.md +6 -0
  140. package/corpus/templates/content/changelog/2026-10-05-shared-page-owners-keep-edit-and-manage-controls-in-files-an.md +6 -0
  141. package/corpus/templates/content/changelog/2026-10-05-tagging-an-ai-in-a-comment-works-again-instead-of-failing-ri.md +6 -0
  142. package/corpus/templates/content/changelog/2026-10-05-the-home-loading-shell-keeps-the-layout-of-your-last-opened-.md +6 -0
  143. package/corpus/templates/content/changelog/2026-10-05-typing-and-a-space-or-inserting-a-toggle-or-callout-no-longe.md +6 -0
  144. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +4 -1
  145. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  146. package/corpus/templates/content/package.json +2 -1
  147. package/corpus/templates/content/scripts/seed-perf-database.mjs +600 -0
  148. package/corpus/templates/content/scripts/trace-startup.mjs +25 -3
  149. package/corpus/templates/content/server/db/index.ts +1 -1
  150. package/corpus/templates/content/server/lib/suggested-edits.ts +23 -26
  151. package/corpus/templates/content/server/plugins/auth.ts +2 -1
  152. package/corpus/templates/content/server/plugins/otel.ts +6 -0
  153. package/corpus/templates/content/shared/api.ts +17 -0
  154. package/corpus/templates/content/shared/suggestion-diff.ts +124 -38
  155. package/corpus/templates/content/shared/suggestion-formatting.ts +134 -3
  156. package/corpus/templates/crm/.agents/skills/security/SKILL.md +9 -2
  157. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +12 -7
  158. package/corpus/templates/crm/package.json +1 -0
  159. package/corpus/templates/crm/server/plugins/otel.ts +6 -0
  160. package/corpus/templates/design/.agents/skills/security/SKILL.md +9 -2
  161. package/corpus/templates/design/.agents/skills/storing-data/SKILL.md +12 -7
  162. package/corpus/templates/design/CHANGELOG.md +41 -0
  163. package/corpus/templates/design/README.md +2 -2
  164. package/corpus/templates/design/actions/list-designs.ts +11 -4
  165. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +110 -32
  166. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +28 -0
  167. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -0
  168. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +79 -0
  169. package/corpus/templates/design/app/components/layout/Header.tsx +2 -4
  170. package/corpus/templates/design/app/components/ui/tabs.tsx +59 -1
  171. package/corpus/templates/design/app/global.css +1 -34
  172. package/corpus/templates/design/app/i18n/en-US.ts +4 -0
  173. package/corpus/templates/design/app/lib/design-filter.ts +4 -2
  174. package/corpus/templates/design/app/pages/Index.tsx +91 -59
  175. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +58 -17
  176. package/corpus/templates/design/changelog/2026-10-01-filter-designs-by-owner.md +6 -0
  177. package/corpus/templates/design/changelog/2026-10-03-layers-dropped-into-visible-canvas-space-beyond-a-screen-now.md +6 -0
  178. package/corpus/templates/design/changelog/2026-10-03-visible-screen-overflow-no-longer-routes-drops-to-the-canvas.md +6 -0
  179. package/corpus/templates/design/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-designs.md +6 -0
  180. package/corpus/templates/design/package.json +1 -0
  181. package/corpus/templates/design/server/plugins/auth.ts +2 -1
  182. package/corpus/templates/design/server/plugins/otel.ts +6 -0
  183. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +9 -2
  184. package/corpus/templates/dispatch/.agents/skills/storing-data/SKILL.md +12 -7
  185. package/corpus/templates/dispatch/README.md +2 -2
  186. package/corpus/templates/dispatch/package.json +1 -0
  187. package/corpus/templates/dispatch/server/plugins/otel.ts +6 -0
  188. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +2 -1
  189. package/corpus/templates/factory/.agents/skills/security/SKILL.md +9 -2
  190. package/corpus/templates/factory/.agents/skills/storing-data/SKILL.md +12 -7
  191. package/corpus/templates/factory/package.json +1 -0
  192. package/corpus/templates/factory/server/plugins/otel.ts +6 -0
  193. package/corpus/templates/forms/.agents/skills/security/SKILL.md +9 -2
  194. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +12 -7
  195. package/corpus/templates/forms/README.md +2 -2
  196. package/corpus/templates/forms/package.json +1 -0
  197. package/corpus/templates/forms/server/plugins/auth.ts +2 -1
  198. package/corpus/templates/forms/server/plugins/otel.ts +6 -0
  199. package/corpus/templates/mail/.agents/skills/security/SKILL.md +9 -2
  200. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +12 -7
  201. package/corpus/templates/mail/CHANGELOG.md +6 -0
  202. package/corpus/templates/mail/README.md +2 -2
  203. package/corpus/templates/mail/package.json +1 -0
  204. package/corpus/templates/mail/server/plugins/auth.ts +2 -1
  205. package/corpus/templates/mail/server/plugins/otel.ts +6 -0
  206. package/corpus/templates/plan/.agents/skills/security/SKILL.md +9 -2
  207. package/corpus/templates/plan/.agents/skills/storing-data/SKILL.md +12 -7
  208. package/corpus/templates/plan/CHANGELOG.md +1 -0
  209. package/corpus/templates/plan/README.md +2 -2
  210. package/corpus/templates/plan/package.json +1 -0
  211. package/corpus/templates/plan/server/plugins/auth.ts +2 -1
  212. package/corpus/templates/plan/server/plugins/otel.ts +6 -0
  213. package/corpus/templates/slides/.agents/skills/security/SKILL.md +9 -2
  214. package/corpus/templates/slides/.agents/skills/storing-data/SKILL.md +12 -7
  215. package/corpus/templates/slides/CHANGELOG.md +23 -0
  216. package/corpus/templates/slides/README.md +2 -2
  217. package/corpus/templates/slides/actions/list-decks.ts +11 -4
  218. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +44 -36
  219. package/corpus/templates/slides/app/components/home/SlidesHomeLibrary.tsx +79 -0
  220. package/corpus/templates/slides/app/components/layout/Header.tsx +2 -15
  221. package/corpus/templates/slides/app/components/ui/tabs.tsx +59 -0
  222. package/corpus/templates/slides/app/global.css +3 -54
  223. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  224. package/corpus/templates/slides/app/lib/deck-filter.ts +5 -2
  225. package/corpus/templates/slides/app/pages/Index.tsx +102 -88
  226. package/corpus/templates/slides/changelog/2026-10-01-filter-decks-by-owner.md +6 -0
  227. package/corpus/templates/slides/changelog/2026-10-02-hide-recent-until-decks-available.md +6 -0
  228. package/corpus/templates/slides/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-decks-a.md +6 -0
  229. package/corpus/templates/slides/package.json +1 -0
  230. package/corpus/templates/slides/server/handlers/request-auth-context.ts +8 -0
  231. package/corpus/templates/slides/server/plugins/auth.ts +2 -1
  232. package/corpus/templates/slides/server/plugins/otel.ts +6 -0
  233. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +9 -2
  234. package/corpus/templates/tasks/.agents/skills/storing-data/SKILL.md +12 -7
  235. package/package.json +1 -1
@@ -8,10 +8,7 @@ import type {
8
8
  BrainSourceProvider,
9
9
  } from "../../shared/types.js";
10
10
  import { getDb, schema } from "../db/index.js";
11
- import {
12
- listAccessibleAudienceIds,
13
- refreshSlackPrivateChannelAudience,
14
- } from "./audiences.js";
11
+ import { listAccessibleAudienceIds } from "./audiences.js";
15
12
  import {
16
13
  BrainCaptureBlockedError,
17
14
  createCapture,
@@ -245,28 +242,6 @@ interface SlackListResponse {
245
242
  response_metadata?: { next_cursor?: string };
246
243
  }
247
244
 
248
- interface SlackMembersResponse {
249
- members?: string[];
250
- response_metadata?: { next_cursor?: string };
251
- }
252
-
253
- interface SlackUserInfoResponse {
254
- user?: {
255
- deleted?: boolean;
256
- is_app_user?: boolean;
257
- is_bot?: boolean;
258
- is_workflow_bot?: boolean;
259
- profile?: { email?: string };
260
- };
261
- }
262
-
263
- type SlackUserEmailCacheEntry =
264
- | { kind: "human"; email: string }
265
- | { kind: "non-human" }
266
- | { kind: "unresolved" };
267
- type SlackUserEmailCache = Map<string, SlackUserEmailCacheEntry>;
268
-
269
- const SLACK_USER_LOOKUP_CONCURRENCY = 4;
270
245
  const SLACK_THREAD_CAPTURE_CONCURRENCY = 4;
271
246
  const GRANOLA_NOTE_CAPTURE_CONCURRENCY = 4;
272
247
  const CONNECTOR_SYNC_LEASE_MS = 10 * 60 * 1_000;
@@ -1073,94 +1048,6 @@ async function slackPermalink(
1073
1048
  return data.permalink ?? null;
1074
1049
  }
1075
1050
 
1076
- async function slackPrivateChannelMemberEmails(
1077
- token: string,
1078
- channelId: string,
1079
- userEmailCache: SlackUserEmailCache,
1080
- ): Promise<string[] | null> {
1081
- const memberIds = new Set<string>();
1082
- const seenCursors = new Set<string>();
1083
- let cursor: string | undefined;
1084
- while (true) {
1085
- const response = await slackApi<SlackMembersResponse>(
1086
- token,
1087
- "conversations.members",
1088
- { channel: channelId, limit: 1_000, cursor },
1089
- );
1090
- if (
1091
- !Array.isArray(response.members) ||
1092
- response.members.some(
1093
- (memberId) => typeof memberId !== "string" || !memberId,
1094
- )
1095
- ) {
1096
- return null;
1097
- }
1098
- for (const memberId of response.members) {
1099
- memberIds.add(memberId);
1100
- }
1101
- const nextCursor = response.response_metadata?.next_cursor?.trim();
1102
- if (!nextCursor) break;
1103
- if (seenCursors.has(nextCursor)) return null;
1104
- seenCursors.add(nextCursor);
1105
- cursor = nextCursor;
1106
- }
1107
- const userIds = Array.from(memberIds);
1108
- if (
1109
- userIds.some((userId) => userEmailCache.get(userId)?.kind === "unresolved")
1110
- ) {
1111
- return null;
1112
- }
1113
-
1114
- const uncachedUserIds = userIds.filter(
1115
- (userId) => !userEmailCache.has(userId),
1116
- );
1117
- for (
1118
- let offset = 0;
1119
- offset < uncachedUserIds.length;
1120
- offset += SLACK_USER_LOOKUP_CONCURRENCY
1121
- ) {
1122
- const batch = uncachedUserIds.slice(
1123
- offset,
1124
- offset + SLACK_USER_LOOKUP_CONCURRENCY,
1125
- );
1126
- await Promise.all(
1127
- batch.map(async (userId) => {
1128
- const user = await slackApi<SlackUserInfoResponse>(
1129
- token,
1130
- "users.info",
1131
- { user: userId },
1132
- );
1133
- const slackUser = user.user;
1134
- if (
1135
- slackUser?.deleted === true ||
1136
- slackUser?.is_app_user === true ||
1137
- slackUser?.is_bot === true ||
1138
- slackUser?.is_workflow_bot === true
1139
- ) {
1140
- userEmailCache.set(userId, { kind: "non-human" });
1141
- return;
1142
- }
1143
- const email = slackUser?.profile?.email?.trim().toLowerCase();
1144
- userEmailCache.set(
1145
- userId,
1146
- email ? { kind: "human", email } : { kind: "unresolved" },
1147
- );
1148
- }),
1149
- );
1150
- if (
1151
- batch.some((userId) => userEmailCache.get(userId)?.kind === "unresolved")
1152
- ) {
1153
- return null;
1154
- }
1155
- }
1156
-
1157
- const emails = userIds.flatMap((userId) => {
1158
- const entry = userEmailCache.get(userId);
1159
- return entry?.kind === "human" ? [entry.email] : [];
1160
- });
1161
- return Array.from(new Set(emails)).sort();
1162
- }
1163
-
1164
1051
  function configuredSlackChannelIds(config: Record<string, unknown>) {
1165
1052
  return new Set(
1166
1053
  slackChannelRefsFromConfig(config).filter((value) =>
@@ -1175,7 +1062,6 @@ async function createSlackThreadCapture(input: {
1175
1062
  messages: SlackMessage[];
1176
1063
  permalink: string | null;
1177
1064
  syncRunId?: string;
1178
- memberEmails?: string[] | null;
1179
1065
  }) {
1180
1066
  const normalized = normalizeSlackThreadCapture({
1181
1067
  channel: input.channel,
@@ -1193,11 +1079,7 @@ async function createSlackThreadCapture(input: {
1193
1079
  content: normalized.content,
1194
1080
  capturedAt: normalized.capturedAt,
1195
1081
  metadata: normalized.metadata,
1196
- audience: {
1197
- kind: input.channel.is_private ? "slack-private-channel" : "org",
1198
- memberEmails: input.memberEmails ?? undefined,
1199
- upstreamRefHash: input.channel.id,
1200
- },
1082
+ audience: { kind: "org", upstreamRefHash: input.channel.id },
1201
1083
  });
1202
1084
  return { capture, blocked: false };
1203
1085
  } catch (error) {
@@ -2468,7 +2350,6 @@ async function syncSlack(source: SourceRow): Promise<ConnectorSyncResult> {
2468
2350
  }
2469
2351
  const channels = [...channelsById.values()];
2470
2352
  stats.eligibleChannels = channels.length;
2471
- const userEmailCache: SlackUserEmailCache = new Map();
2472
2353
 
2473
2354
  const channelsToScan = includePublicChannels
2474
2355
  ? (() => {
@@ -2510,29 +2391,6 @@ async function syncSlack(source: SourceRow): Promise<ConnectorSyncResult> {
2510
2391
  stats.publicChannelsAlreadyJoined =
2511
2392
  Number(stats.publicChannelsAlreadyJoined) + 1;
2512
2393
  }
2513
- const privateMemberEmails = channel.is_private
2514
- ? await slackPrivateChannelMemberEmails(
2515
- token,
2516
- channel.id,
2517
- userEmailCache,
2518
- )
2519
- : null;
2520
- if (channel.is_private) {
2521
- if (privateMemberEmails === null) {
2522
- stats.rejectedChannels = Number(stats.rejectedChannels) + 1;
2523
- continue;
2524
- }
2525
- await refreshSlackPrivateChannelAudience({
2526
- source,
2527
- channelId: channel.id,
2528
- memberEmails: privateMemberEmails,
2529
- });
2530
- if (!privateMemberEmails.length) {
2531
- stats.rejectedChannels = Number(stats.rejectedChannels) + 1;
2532
- continue;
2533
- }
2534
- }
2535
-
2536
2394
  stats.scannedChannels = Number(stats.scannedChannels) + 1;
2537
2395
  const channelCursor = nextCursor.channels?.[channel.id] ?? {};
2538
2396
  const pendingLatest =
@@ -2579,7 +2437,6 @@ async function syncSlack(source: SourceRow): Promise<ConnectorSyncResult> {
2579
2437
  messages: thread.messages?.length ? thread.messages : [message],
2580
2438
  permalink,
2581
2439
  syncRunId: runId,
2582
- memberEmails: privateMemberEmails,
2583
2440
  });
2584
2441
  }),
2585
2442
  );
@@ -2838,26 +2695,6 @@ export async function refreshSlackThreadCapture(
2838
2695
  `Slack thread refresh rejected for channel ${channelId}: it matches a Brain public-channel exclusion`,
2839
2696
  );
2840
2697
  }
2841
- const memberEmails = channel.is_private
2842
- ? await slackPrivateChannelMemberEmails(token, channel.id, new Map())
2843
- : null;
2844
- if (channel.is_private) {
2845
- if (memberEmails === null) {
2846
- throw new Error(
2847
- `Slack private channel ${channel.id} has no resolvable member emails; refusing to refresh without an ACL`,
2848
- );
2849
- }
2850
- if (!memberEmails.length) {
2851
- await refreshSlackPrivateChannelAudience({
2852
- source,
2853
- channelId: channel.id,
2854
- memberEmails,
2855
- });
2856
- throw new Error(
2857
- `Slack private channel ${channel.id} has no human members; refusing to refresh a capture`,
2858
- );
2859
- }
2860
- }
2861
2698
  const [thread, permalink] = await Promise.all([
2862
2699
  slackApi<SlackRepliesResponse>(token, "conversations.replies", {
2863
2700
  channel: channel.id,
@@ -2872,7 +2709,6 @@ export async function refreshSlackThreadCapture(
2872
2709
  messages: thread.messages ?? [],
2873
2710
  permalink,
2874
2711
  syncRunId: runId,
2875
- memberEmails,
2876
2712
  });
2877
2713
  const stats = {
2878
2714
  channelId: channel.id,
@@ -0,0 +1,6 @@
1
+ import { defineNitroPlugin } from "@agent-native/core/server";
2
+ import { startAgentNativeOtel } from "@agent-native/otel";
3
+
4
+ export default defineNitroPlugin(() => {
5
+ startAgentNativeOtel();
6
+ });
@@ -290,9 +290,16 @@ export default defineEventHandler(async (event) => {
290
290
 
291
291
  ## Data Scoping
292
292
 
293
- In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level — the agent cannot bypass it.
293
+ In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views, and checks every statement before it runs so it can only reach those views.
294
294
 
295
- The `db-query` / `db-exec` tools (and the extension SQL bridge, which shares the same path) reject schema-qualified table references like `public.<table>` — a qualified name resolves to the base table and would skip the temp view. Use bare table names; scoping is applied automatically.
295
+ The `db-query` / `db-exec` / `db-patch` tools (and the extension SQL bridge, which shares the same path) read each statement the way Postgres does and refuse anything that could reach data around the temp views:
296
+
297
+ - schema- or database-qualified names like `public.<table>`, which resolve to the base table;
298
+ - names that resolve to anything other than the current user's temp views, such as materialized views or tables the scoping does not cover;
299
+ - app-defined SQL or PL/pgSQL functions and operators, whose bodies read tables without the views;
300
+ - built-ins that run SQL text, change session settings, or reach outside the database (`query_to_xml`, `set_config`, `dblink`, most `pg_*` functions).
301
+
302
+ `db-query` runs in a read-only transaction, and an `INSERT` must list its columns when the table has access-control columns. Use bare table names and plain expressions; scoping is applied automatically. When the agent needs data the views do not expose, add an app action with its own access checks.
296
303
 
297
304
  ### Per-User Scoping (`owner_email`)
298
305
 
@@ -75,10 +75,11 @@ state their PostgreSQL types directly.
75
75
 
76
76
  Member offboarding and email changes refuse to run while any column named
77
77
  `email`, `*_email`, `*scope_id`, `created_by`, `updated_by`, `invited_by`,
78
- `owner`, `principal_id`, `session_id`, or `user_id` has no policy. `owner_email`
79
- and `createSharesTable()` tables are handled for you. Declare every other one,
80
- including columns that are not member identities, from the app's database
81
- plugin graph (Clips does it in `server/db/index.ts`):
78
+ `owner`, `principal_id`, `session_id`, or `user_id` has no policy.
79
+ `createSharesTable()` tables are handled for you, and undeclared `owner_email`
80
+ columns transfer to the successor, which is right only for owned content.
81
+ Declare every other one, including columns that are not member identities, from
82
+ the app's database plugin graph (Clips does it in `server/db/index.ts`):
82
83
 
83
84
  ```ts
84
85
  import { registerIdentityColumns } from "@agent-native/core/org";
@@ -88,13 +89,17 @@ registerIdentityColumns([
88
89
  { table: "space_members", column: "email", emailChange: "rekey", offboard: "delete", orgScope: { column: "space_id", references: { table: "spaces", column: "id", orgColumn: "org_id" } }, reason: "Space membership grants access." },
89
90
  // Someone else's address: never rewritten.
90
91
  { table: "meeting_participants", column: "email", emailChange: "retain", offboard: "retain", reason: "Attendee address from the calendar provider." },
92
+ // A credential in an owner_email table: never handed to the successor.
93
+ { table: "api_keys", column: "owner_email", emailChange: "rekey", offboard: "revoke", reason: "Bearer keys act as their owner." },
91
94
  ]);
92
95
  ```
93
96
 
94
- Choose `delete` for grants, credentials, and pending tokens; `retain` for
97
+ Choose `delete` for grants, credentials, and pending tokens, or `revoke` (sets
98
+ `revoked_at`) when a reader treats a missing row as "not revoked"; `retain` for
95
99
  attribution, history, and third-party addresses; `transfer` only for owned
96
- data. A table without `org_id` needs `orgScope` or an organization-scoped
97
- removal leaves its rows alone.
100
+ data. Declare `owner_email` tables that hold credentials or grants, or
101
+ offboarding hands them to the successor. A table without `org_id` needs
102
+ `orgScope` or an organization-scoped removal leaves its rows alone.
98
103
 
99
104
  | Template | Tables |
100
105
  | ------------ | --------------------------------------------- |
@@ -3,6 +3,13 @@
3
3
  All notable user-facing changes to Agent-Native Calendar are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-02
7
+
8
+ ### Improved
9
+
10
+ - Builder.io setup copy now clarifies account activation and sign-in.
11
+ - Owners and admins can save Apollo, HubSpot, Gong, and Pylon keys for the whole organization or just for themselves.
12
+
6
13
  ## 2026-10-01
7
14
 
8
15
  ### Improved
@@ -4,7 +4,7 @@ An open-source, agent-native alternative to the Google Calendar + Calendly combo
4
4
  An agent-powered calendar with Google Calendar sync and Calendly-style public
5
5
  booking links — schedule, find slots, and manage availability in plain English.
6
6
 
7
- **Live app: [calendar.agent-native.com](https://calendar.agent-native.com)**
7
+ **Live app: [calendar.agent-native.com](https://calendar.agent-native.com?utm_source=github&utm_medium=referral&utm_content=calendar-readme)**
8
8
 
9
9
  Connect your Google Calendar and the agent can read your schedule, find free
10
10
  slots, create events, and manage booking links. Anything you can do in the UI,
@@ -33,4 +33,4 @@ pnpm dev
33
33
  ```
34
34
 
35
35
  Connecting Google Calendar in dev needs a Google OAuth client — see the docs for
36
- setup. Full docs: [agent-native.com/docs/template-calendar](https://agent-native.com/docs/template-calendar).
36
+ setup. Full docs: [agent-native.com/docs/template-calendar](https://agent-native.com/docs/template-calendar?utm_source=github&utm_medium=referral&utm_content=calendar-readme).
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@agent-native/core": "workspace:*",
21
+ "@agent-native/otel": "workspace:*",
21
22
  "@agent-native/scheduling": "workspace:*",
22
23
  "@agent-native/toolkit": "workspace:*",
23
24
  "@electric-sql/pglite": "^0.5.8",
@@ -6,7 +6,8 @@ export default createAuthPlugin({
6
6
  workspaceAppPublicPaths: ["/"],
7
7
  marketing: {
8
8
  appName: "Calendar",
9
- learnMoreUrl: "https://agent-native.com/apps/calendar",
9
+ learnMoreUrl:
10
+ "https://agent-native.com/apps/calendar?utm_source=app&utm_medium=product&utm_content=onboarding-learn-more",
10
11
  tagline:
11
12
  "Your AI agent schedules, reschedules, and manages your calendar so you never have to.",
12
13
  features: [
@@ -0,0 +1,6 @@
1
+ import { defineNitroPlugin } from "@agent-native/core/server";
2
+ import { startAgentNativeOtel } from "@agent-native/otel";
3
+
4
+ export default defineNitroPlugin(() => {
5
+ startAgentNativeOtel();
6
+ });
@@ -290,9 +290,16 @@ export default defineEventHandler(async (event) => {
290
290
 
291
291
  ## Data Scoping
292
292
 
293
- In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level — the agent cannot bypass it.
293
+ In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views, and checks every statement before it runs so it can only reach those views.
294
294
 
295
- The `db-query` / `db-exec` tools (and the extension SQL bridge, which shares the same path) reject schema-qualified table references like `public.<table>` — a qualified name resolves to the base table and would skip the temp view. Use bare table names; scoping is applied automatically.
295
+ The `db-query` / `db-exec` / `db-patch` tools (and the extension SQL bridge, which shares the same path) read each statement the way Postgres does and refuse anything that could reach data around the temp views:
296
+
297
+ - schema- or database-qualified names like `public.<table>`, which resolve to the base table;
298
+ - names that resolve to anything other than the current user's temp views, such as materialized views or tables the scoping does not cover;
299
+ - app-defined SQL or PL/pgSQL functions and operators, whose bodies read tables without the views;
300
+ - built-ins that run SQL text, change session settings, or reach outside the database (`query_to_xml`, `set_config`, `dblink`, most `pg_*` functions).
301
+
302
+ `db-query` runs in a read-only transaction, and an `INSERT` must list its columns when the table has access-control columns. Use bare table names and plain expressions; scoping is applied automatically. When the agent needs data the views do not expose, add an app action with its own access checks.
296
303
 
297
304
  ### Per-User Scoping (`owner_email`)
298
305
 
@@ -75,10 +75,11 @@ state their PostgreSQL types directly.
75
75
 
76
76
  Member offboarding and email changes refuse to run while any column named
77
77
  `email`, `*_email`, `*scope_id`, `created_by`, `updated_by`, `invited_by`,
78
- `owner`, `principal_id`, `session_id`, or `user_id` has no policy. `owner_email`
79
- and `createSharesTable()` tables are handled for you. Declare every other one,
80
- including columns that are not member identities, from the app's database
81
- plugin graph (Clips does it in `server/db/index.ts`):
78
+ `owner`, `principal_id`, `session_id`, or `user_id` has no policy.
79
+ `createSharesTable()` tables are handled for you, and undeclared `owner_email`
80
+ columns transfer to the successor, which is right only for owned content.
81
+ Declare every other one, including columns that are not member identities, from
82
+ the app's database plugin graph (Clips does it in `server/db/index.ts`):
82
83
 
83
84
  ```ts
84
85
  import { registerIdentityColumns } from "@agent-native/core/org";
@@ -88,13 +89,17 @@ registerIdentityColumns([
88
89
  { table: "space_members", column: "email", emailChange: "rekey", offboard: "delete", orgScope: { column: "space_id", references: { table: "spaces", column: "id", orgColumn: "org_id" } }, reason: "Space membership grants access." },
89
90
  // Someone else's address: never rewritten.
90
91
  { table: "meeting_participants", column: "email", emailChange: "retain", offboard: "retain", reason: "Attendee address from the calendar provider." },
92
+ // A credential in an owner_email table: never handed to the successor.
93
+ { table: "api_keys", column: "owner_email", emailChange: "rekey", offboard: "revoke", reason: "Bearer keys act as their owner." },
91
94
  ]);
92
95
  ```
93
96
 
94
- Choose `delete` for grants, credentials, and pending tokens; `retain` for
97
+ Choose `delete` for grants, credentials, and pending tokens, or `revoke` (sets
98
+ `revoked_at`) when a reader treats a missing row as "not revoked"; `retain` for
95
99
  attribution, history, and third-party addresses; `transfer` only for owned
96
- data. A table without `org_id` needs `orgScope` or an organization-scoped
97
- removal leaves its rows alone.
100
+ data. Declare `owner_email` tables that hold credentials or grants, or
101
+ offboarding hands them to the successor. A table without `org_id` needs
102
+ `orgScope` or an organization-scoped removal leaves its rows alone.
98
103
 
99
104
  | Template | Tables |
100
105
  | ------------ | --------------------------------------------- |
@@ -5,7 +5,7 @@ the center, durable threads, standard app navigation, auth, live sync, and
5
5
  actions. Start here when you want a real browser app to build on without
6
6
  committing to a domain template.
7
7
 
8
- **Live app: [chat.agent-native.com](https://chat.agent-native.com)**
8
+ **Live app: [chat.agent-native.com](https://chat.agent-native.com?utm_source=github&utm_medium=referral&utm_content=chat-readme)**
9
9
 
10
10
  Chat is the basic agent-native app starting point. It gives you the app-agent
11
11
  loop wired end to end and one example action, so you can add your own UI, data,
@@ -30,4 +30,4 @@ pnpm install
30
30
  pnpm dev
31
31
  ```
32
32
 
33
- Full docs: [agent-native.com/docs/template-chat](https://agent-native.com/docs/template-chat).
33
+ Full docs: [agent-native.com/docs/template-chat](https://agent-native.com/docs/template-chat?utm_source=github&utm_medium=referral&utm_content=chat-readme).
@@ -131,6 +131,15 @@ Transcription takes an audio file and returns text + segments. That's not a prom
131
131
  2. **Cloud fallback — Builder.io managed (Gemini).** When a Builder account is connected (`BUILDER_PRIVATE_KEY` or per-user OAuth, no separate API key needed), `request-transcript` calls `transcribeWithBuilder()`, which routes audio to Builder.io's managed Gemini transcription. Returns text plus timestamped segments. Retry a failed transcript with `force: true`; pass `regenerate: true` to replace an existing ready transcript from the stored recording media. Regeneration keeps the prior ready transcript available if the new provider attempt fails.
132
132
  3. **Cloud fallback — Groq Whisper.** `GROQ_API_KEY` → `https://api.groq.com/openai/v1/audio/transcriptions`, model `whisper-large-v3-turbo`. Fast (~$0.04/hour of audio) Whisper-compatible speech-to-text used when Builder is unavailable.
133
133
 
134
+ Cloud transcription always re-encodes the audio to mono 16 kHz and splits it
135
+ into parts of about 15 minutes, transcribed a few at a time and stitched back on
136
+ the recording timeline. Long uploads therefore transcribe like short ones; do
137
+ not tell users a recording is "too long" to transcribe. The provider numbers
138
+ speakers per part, so in a multi-part transcript the same label may refer to
139
+ different people in different parts. If any part still fails after one retry, nothing is saved and the transcript fails with `CHUNK_FAILED`;
140
+ `transcript-cleanup-<id>` in application state records which part failed and
141
+ the provider error. Retry with `request-transcript` and `force: true`.
142
+
134
143
  The transcript included by `view-screen` is only a bounded preview. When its
135
144
  `previewTruncated` field is true, the preview may end mid-sentence and does not
136
145
  show where transcription ended. Call `get-recording-player-data` before
@@ -290,9 +290,16 @@ export default defineEventHandler(async (event) => {
290
290
 
291
291
  ## Data Scoping
292
292
 
293
- In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level — the agent cannot bypass it.
293
+ In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views, and checks every statement before it runs so it can only reach those views.
294
294
 
295
- The `db-query` / `db-exec` tools (and the extension SQL bridge, which shares the same path) reject schema-qualified table references like `public.<table>` — a qualified name resolves to the base table and would skip the temp view. Use bare table names; scoping is applied automatically.
295
+ The `db-query` / `db-exec` / `db-patch` tools (and the extension SQL bridge, which shares the same path) read each statement the way Postgres does and refuse anything that could reach data around the temp views:
296
+
297
+ - schema- or database-qualified names like `public.<table>`, which resolve to the base table;
298
+ - names that resolve to anything other than the current user's temp views, such as materialized views or tables the scoping does not cover;
299
+ - app-defined SQL or PL/pgSQL functions and operators, whose bodies read tables without the views;
300
+ - built-ins that run SQL text, change session settings, or reach outside the database (`query_to_xml`, `set_config`, `dblink`, most `pg_*` functions).
301
+
302
+ `db-query` runs in a read-only transaction, and an `INSERT` must list its columns when the table has access-control columns. Use bare table names and plain expressions; scoping is applied automatically. When the agent needs data the views do not expose, add an app action with its own access checks.
296
303
 
297
304
  ### Per-User Scoping (`owner_email`)
298
305
 
@@ -3,10 +3,34 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-05
7
+
8
+ ### Fixed
9
+
10
+ - Confirming Discard recording now returns you to your library.
11
+ - Discarding a recording now removes its failed upload card from your library.
12
+ - Discarding a recovered recording no longer warns that it's open in another tab.
13
+ - Search results identify recordings in Trash and open them with Trash navigation.
14
+
15
+ ## 2026-10-03
16
+
17
+ ### Fixed
18
+
19
+ - Invalid Lab choices can be repaired from Labs settings.
20
+
6
21
  ## 2026-10-02
7
22
 
23
+ ### Improved
24
+
25
+ - Builder.io setup now offers one-click activation and clear existing-account sign-in.
26
+
8
27
  ### Fixed
9
28
 
29
+ - Cancelled Rewind Clip setup can be retried immediately
30
+ - Long uploads (an hour or more) now transcribe reliably instead of failing.
31
+ - Recording countdowns begin during setup, with explicit cue outcomes and startup timing
32
+ - Recording visibility defaults no longer switch to Public when saved settings cannot be read.
33
+ - Screen recording startup now cleans up canceled Rewind setup and keeps countdown timing reliable on older macOS.
10
34
  - Recording reminders and other actionable notifications show their message above the buttons
11
35
  - Temporary upload authorization failures remain retryable, while permanently rejected Builder credentials trigger storage setup errors.
12
36
 
@@ -4,7 +4,7 @@ An open-source, agent-native alternative to Loom, Granola, and Wispr Flow rolled
4
4
  into one app. Screen recording, calendar-synced meeting notes, and push-to-talk
5
5
  voice dictation — all transcribed, searchable, and yours to own.
6
6
 
7
- **Live app: [clips.agent-native.com](https://clips.agent-native.com)**
7
+ **Live app: [clips.agent-native.com](https://clips.agent-native.com?utm_source=github&utm_medium=referral&utm_content=clips-readme)**
8
8
 
9
9
  The agent transcribes, titles, summarizes, and indexes everything you capture,
10
10
  then lets you ask "find the clip where we discussed the rollout plan" across every
@@ -34,4 +34,4 @@ pnpm dev
34
34
 
35
35
  Clips needs a video storage backend (Builder.io or an S3-compatible bucket)
36
36
  before recordings can upload — see the docs for setup.
37
- Full docs: [agent-native.com/docs/template-clips](https://agent-native.com/docs/template-clips).
37
+ Full docs: [agent-native.com/docs/template-clips](https://agent-native.com/docs/template-clips?utm_source=github&utm_medium=referral&utm_content=clips-readme).
@@ -0,0 +1,23 @@
1
+ export interface AudioChunkListEntry {
2
+ file: string;
3
+ startMs: number;
4
+ durationMs: number;
5
+ }
6
+
7
+ // Parses ffmpeg's `-segment_list_type csv` output: `file,start,end` per line,
8
+ // times in seconds.
9
+ export function parseAudioChunkList(csv: string): AudioChunkListEntry[] {
10
+ return csv
11
+ .split(/\r?\n/)
12
+ .filter((line) => line.trim())
13
+ .map((line) => {
14
+ const [file, start, end] = line.split(",");
15
+ const startMs = Math.round(Number(start) * 1000);
16
+ const endMs = Math.round(Number(end) * 1000);
17
+ if (!file || !Number.isFinite(startMs) || !(endMs > startMs)) {
18
+ // guard:allow-bare-error — invariant: malformed ffmpeg output; the caller rethrows it as EXTRACTION_FAILED.
19
+ throw new Error(`Unreadable audio chunk list entry: "${line}"`);
20
+ }
21
+ return { file, startMs, durationMs: endMs - startMs };
22
+ });
23
+ }