@agent-native/core-corpus 0.202.0 → 0.202.1-nightly-20261006213000

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 (24) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +7 -2
  3. package/corpus/templates/brain/changelog/2026-10-06-zoom-transcripts-now-download.md +6 -0
  4. package/corpus/templates/brain/server/lib/connectors.ts +66 -4
  5. package/corpus/templates/brain/server/lib/zoom.ts +37 -4
  6. package/corpus/templates/brain/server/register-secrets.ts +3 -3
  7. package/corpus/templates/content/changelog/2026-10-06-generated-documents-from-chatgpt-open-in-the-full-content-ed.md +6 -0
  8. package/corpus/templates/content/server/lib/chatgpt-directory-tools.ts +0 -2
  9. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +12 -6
  10. package/corpus/templates/design/actions/generate-design.ts +188 -45
  11. package/corpus/templates/design/actions/present-design-variants.ts +113 -39
  12. package/corpus/templates/design/app/hooks/use-question-flow.ts +1 -1
  13. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +10 -2
  14. package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +5 -4
  15. package/corpus/templates/design/changelog/2026-10-05-design-keeps-exact-size-screens-at-their-requested-dimension.md +6 -0
  16. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
  17. package/corpus/templates/design/shared/canvas-dimensions.ts +204 -0
  18. package/corpus/templates/design/shared/responsive-frame-layout.ts +1 -0
  19. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +17 -5
  20. package/corpus/templates/mail/app/root.tsx +16 -1
  21. package/corpus/templates/mail/changelog/2026-10-02-pressing-r-on-a-message-in-the-list-opens-the-reply-window-e.md +6 -0
  22. package/corpus/templates/slides/changelog/2026-10-06-generated-decks-from-chatgpt-open-in-the-full-slides-editor.md +6 -0
  23. package/corpus/templates/slides/server/lib/chatgpt-directory-tools.ts +0 -2
  24. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 11246
35
+ - template files: 11252
@@ -48,7 +48,8 @@ Zoom sources import cloud-recording transcripts through a Server-to-Server
48
48
  OAuth app. Credentials are the vault secrets `ZOOM_ACCOUNT_ID`,
49
49
  `ZOOM_CLIENT_ID`, and `ZOOM_CLIENT_SECRET`; the app needs scopes
50
50
  `cloud_recording:read:list_account_recordings:admin` and
51
- `cloud_recording:read:recording:admin`. Config is
51
+ `cloud_recording:read:recording:admin`, plus
52
+ `cloud_recording:read:list_recording_files:admin` for transcript download URLs. Config is
52
53
  `{"zoom":{"userIds":[...],"lookbackDays":7}}`. Without `userIds`, one
53
54
  account-wide recording list (`/accounts/me/recordings`, which needs only the `:admin` scope) covers every user.
54
55
  `userIds` (up to 50, user ID or email) narrows the import to those users and
@@ -63,7 +64,11 @@ account is imported. `update-source` replaces the whole `zoom` object, so send
63
64
  every Zoom field you want to keep. Changing the filter or raising `lookbackDays` rewinds the next sync to
64
65
  the `lookbackDays` window, so newly included meetings are backfilled (raise
65
66
  `lookbackDays`, up to 30, to reach further back). Run stats report
66
- `meetingsSkippedByFilter` and `filterChanged`.
67
+ `meetingsSkippedByFilter`, `filterChanged`, `transcriptsWithoutDownloadUrl`,
68
+ `matchedMeetings` (ID, title, start, file types) and `skippedMeetings` (ID,
69
+ start). The account-wide list omits download URLs, so a matched meeting with a
70
+ finished transcript is looked up with `/meetings/{uuid}/recordings`
71
+ (`cloud_recording:read:list_recording_files:admin`) to get the transcript URL.
67
72
  Sources auto-sync hourly; each run overlaps the previous one by one day to
68
73
  catch late-processed transcripts, and captures dedupe by `zoom:<meeting uuid>`.
69
74
  Captures use the organization audience. There are no Zoom webhooks.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-06
4
+ ---
5
+
6
+ Zoom transcripts now import. Zoom's account-wide recording list leaves out download links, so Brain now looks up each matching meeting to get its transcript. Each Zoom sync also lists which meetings matched or were skipped, and which files Zoom returned, to make problems easier to spot.
@@ -32,6 +32,8 @@ import {
32
32
  import {
33
33
  ZoomHttpError,
34
34
  downloadZoomTranscript,
35
+ getZoomMeetingRecordings,
36
+ isReadyZoomTranscript,
35
37
  fetchZoomAccessToken,
36
38
  listZoomRecordings,
37
39
  hasProcessingTranscript,
@@ -3030,6 +3032,8 @@ async function syncGranola(source: SourceRow): Promise<ConnectorSyncResult> {
3030
3032
 
3031
3033
  const ZOOM_MAX_LOOKBACK_DAYS = 30;
3032
3034
  const ZOOM_DEFAULT_LOOKBACK_DAYS = 7;
3035
+ const ZOOM_DIAGNOSTIC_LIMIT = 50;
3036
+ const ZOOM_DIAGNOSTIC_FILE_LIMIT = 10;
3033
3037
  const ZOOM_DATE = /^\d{4}-\d{2}-\d{2}$/;
3034
3038
 
3035
3039
  function utcDate(ms: number): string {
@@ -3142,6 +3146,7 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
3142
3146
  recordingListsScanned: 0,
3143
3147
  meetingsSeen: 0,
3144
3148
  meetingsSkippedByFilter: 0,
3149
+ transcriptsWithoutDownloadUrl: 0,
3145
3150
  filterChanged,
3146
3151
  transcriptsDownloaded: 0,
3147
3152
  emptyTranscripts: 0,
@@ -3152,6 +3157,18 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
3152
3157
  rateLimited: false,
3153
3158
  };
3154
3159
  const pendingMeetingStarts: string[] = [];
3160
+ // Meeting IDs and file types (titles only for matched meetings) show why a
3161
+ // meeting did or did not import.
3162
+ const matchedMeetings: Array<{
3163
+ id: string;
3164
+ topic: string | null;
3165
+ start: string;
3166
+ files: string[];
3167
+ filesOmitted?: number;
3168
+ }> = [];
3169
+ const skippedMeetings: Array<{ id: string; start: string }> = [];
3170
+ stats.matchedMeetings = matchedMeetings;
3171
+ stats.skippedMeetings = skippedMeetings;
3155
3172
 
3156
3173
  try {
3157
3174
  const accountId = await requireConnectorCredential(
@@ -3199,11 +3216,40 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
3199
3216
  );
3200
3217
  stats.meetingsSkippedByFilter =
3201
3218
  Number(stats.meetingsSkippedByFilter) + listed.length - meetings.length;
3219
+ for (const meeting of listed) {
3220
+ if (skippedMeetings.length >= ZOOM_DIAGNOSTIC_LIMIT) break;
3221
+ if (!zoomMeetingMatchesFilter(meeting, meetingFilter)) {
3222
+ skippedMeetings.push({
3223
+ id: String(meeting.id),
3224
+ start: meeting.start_time,
3225
+ });
3226
+ }
3227
+ }
3202
3228
  const imported = await importedZoomExternalIds(
3203
3229
  source.id,
3204
3230
  meetings.map(zoomExternalId),
3205
3231
  );
3206
3232
  for (const meeting of meetings) {
3233
+ if (matchedMeetings.length < ZOOM_DIAGNOSTIC_LIMIT) {
3234
+ const files = meeting.recording_files ?? [];
3235
+ matchedMeetings.push({
3236
+ id: String(meeting.id),
3237
+ topic: meeting.topic ?? null,
3238
+ start: meeting.start_time,
3239
+ files: files
3240
+ .slice(0, ZOOM_DIAGNOSTIC_FILE_LIMIT)
3241
+ .map((file) =>
3242
+ [
3243
+ file.file_type,
3244
+ file.status ?? "",
3245
+ file.download_url ? "url" : "no-url",
3246
+ ].join(":"),
3247
+ ),
3248
+ ...(files.length > ZOOM_DIAGNOSTIC_FILE_LIMIT
3249
+ ? { filesOmitted: files.length - ZOOM_DIAGNOSTIC_FILE_LIMIT }
3250
+ : {}),
3251
+ });
3252
+ }
3207
3253
  if (hasProcessingTranscript(meeting)) {
3208
3254
  pendingMeetingStarts.push(meeting.start_time);
3209
3255
  stats.pendingTranscripts = Number(stats.pendingTranscripts) + 1;
@@ -3212,12 +3258,28 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
3212
3258
  stats.alreadyImported = Number(stats.alreadyImported) + 1;
3213
3259
  continue;
3214
3260
  }
3215
- const transcripts = (meeting.recording_files ?? []).filter(
3261
+ let recordingFiles = meeting.recording_files ?? [];
3262
+ const readyTranscripts = recordingFiles.filter(isReadyZoomTranscript);
3263
+ if (
3264
+ readyTranscripts.length > 0 &&
3265
+ readyTranscripts.every((file) => !file.download_url)
3266
+ ) {
3267
+ await renewRunLease(run);
3268
+ const detail = await zoomCall(
3269
+ "/meetings/{meetingUuid}/recordings",
3270
+ () => getZoomMeetingRecordings(token, meeting.uuid),
3271
+ );
3272
+ recordingFiles = detail.recording_files ?? [];
3273
+ }
3274
+ const transcripts = recordingFiles.filter(
3216
3275
  (file): file is typeof file & { download_url: string } =>
3217
- file.file_type === "TRANSCRIPT" &&
3218
- file.status !== "processing" &&
3219
- Boolean(file.download_url),
3276
+ isReadyZoomTranscript(file) && Boolean(file.download_url),
3220
3277
  );
3278
+ stats.transcriptsWithoutDownloadUrl =
3279
+ Number(stats.transcriptsWithoutDownloadUrl) +
3280
+ recordingFiles.filter(
3281
+ (file) => isReadyZoomTranscript(file) && !file.download_url,
3282
+ ).length;
3221
3283
  for (const file of transcripts) {
3222
3284
  await renewRunLease(run);
3223
3285
  const vtt = await zoomCall("recording transcript download", () =>
@@ -1,7 +1,3 @@
1
- import {
2
- normalizeZoomMeetingId,
3
- normalizeZoomMeetingTopic,
4
- } from "../../shared/zoom-meeting-filter.js";
5
1
  import { sanitizeSensitiveText } from "./sensitivity-policy.js";
6
2
 
7
3
  const ZOOM_API_BASE = "https://api.zoom.us/v2";
@@ -207,6 +203,30 @@ async function listZoomRecordingPages(
207
203
  return meetings;
208
204
  }
209
205
 
206
+ export function isReadyZoomTranscript(file: ZoomRecordingFile): boolean {
207
+ return file.file_type === "TRANSCRIPT" && file.status !== "processing";
208
+ }
209
+
210
+ // Zoom requires a UUID that starts with "/" or contains "//" to be encoded twice.
211
+ export function zoomMeetingUuidPath(uuid: string): string {
212
+ const encoded = encodeURIComponent(uuid);
213
+ return uuid.startsWith("/") || uuid.includes("//")
214
+ ? encodeURIComponent(encoded)
215
+ : encoded;
216
+ }
217
+
218
+ // The account-wide list omits download_url; this per-meeting lookup includes it.
219
+ export function getZoomMeetingRecordings(
220
+ token: string,
221
+ meetingUuid: string,
222
+ ): Promise<ZoomMeeting> {
223
+ return zoomApiJson<ZoomMeeting>(
224
+ token,
225
+ `${ZOOM_API_BASE}/meetings/${zoomMeetingUuidPath(meetingUuid)}/recordings`,
226
+ "meeting recording lookup",
227
+ );
228
+ }
229
+
210
230
  /**
211
231
  * Redirects are followed by hand: the bearer token may only ride along to
212
232
  * Zoom hosts, so a hop to a CDN or any other origin is fetched without it.
@@ -304,6 +324,19 @@ export interface ZoomMeetingFilter {
304
324
  meetingTopics: Set<string>;
305
325
  }
306
326
 
327
+ // Zoom shows meeting IDs as "123 4567 8901"; the API returns 12345678901.
328
+ export function normalizeZoomMeetingId(value: unknown): string | null {
329
+ if (typeof value !== "string" && typeof value !== "number") return null;
330
+ const id = String(value).replace(/[\s-]/g, "");
331
+ return /^\d{6,15}$/.test(id) ? id : null;
332
+ }
333
+
334
+ export function normalizeZoomMeetingTopic(value: unknown): string | null {
335
+ if (typeof value !== "string") return null;
336
+ const topic = value.trim().replace(/\s+/g, " ").toLowerCase();
337
+ return topic || null;
338
+ }
339
+
307
340
  function normalizedSet(
308
341
  raw: unknown,
309
342
  normalize: (value: unknown) => string | null,
@@ -66,7 +66,7 @@ registerRequiredSecret({
66
66
  key: "ZOOM_ACCOUNT_ID",
67
67
  label: "Zoom Account ID",
68
68
  description:
69
- "Optional Zoom Server-to-Server OAuth account ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
69
+ "Optional Zoom Server-to-Server OAuth account ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin, cloud_recording:read:list_recording_files:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
70
70
  docsUrl: "https://developers.zoom.us/docs/internal-apps/s2s-oauth/",
71
71
  scope: "workspace",
72
72
  kind: "api-key",
@@ -84,7 +84,7 @@ registerRequiredSecret({
84
84
  key: "ZOOM_CLIENT_ID",
85
85
  label: "Zoom Client ID",
86
86
  description:
87
- "Optional Zoom Server-to-Server OAuth client ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
87
+ "Optional Zoom Server-to-Server OAuth client ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin, cloud_recording:read:list_recording_files:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
88
88
  docsUrl: "https://developers.zoom.us/docs/internal-apps/s2s-oauth/",
89
89
  scope: "workspace",
90
90
  kind: "api-key",
@@ -102,7 +102,7 @@ registerRequiredSecret({
102
102
  key: "ZOOM_CLIENT_SECRET",
103
103
  label: "Zoom Client Secret",
104
104
  description:
105
- "Optional Zoom Server-to-Server OAuth client secret for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
105
+ "Optional Zoom Server-to-Server OAuth client secret for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin, cloud_recording:read:list_recording_files:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
106
106
  docsUrl: "https://developers.zoom.us/docs/internal-apps/s2s-oauth/",
107
107
  scope: "workspace",
108
108
  kind: "api-key",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-06
4
+ ---
5
+
6
+ Generated documents from ChatGPT open in the full Content editor.
@@ -15,8 +15,6 @@ export const CHATGPT_DIRECTORY_PROFILE = {
15
15
  connectorCatalog: CHATGPT_DIRECTORY_TOOL_NAMES,
16
16
  widgets: true,
17
17
  widgetDomain: "https://content.agent-native.com",
18
- // TEMPORARY: omit frame domains and widget domain only for the scan A/B.
19
- widgetDiagnostic: "no-frame-domains",
20
18
  keyToolNames: [
21
19
  "search-documents",
22
20
  "get-document",
@@ -357,8 +357,9 @@ follow-up ambiguity instead of resolving it.
357
357
 
358
358
  **Carry the form-factor answer through to generation — do not just ask and discard it.** Map the answer to the design's device SET, not to separate per-device screen files. Device widths of the SAME page are breakpoint frames of one document (see the `responsive-breakpoints` skill), never a `mobile.html` + `desktop.html` pair. Pass the answer through `generate-design`'s `devices` param — `("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`:
359
359
 
360
+ - An exact pixel size in the prompt defines one fixed-size canvas. Use those exact dimensions, pass `devices: []` (or `responsive: false` to `present-design-variants`), and do not generate mobile or other device frames for it. Keep each action prompt to one distinct exact canvas size; make separate calls scoped to each screen when a request names different sizes.
360
361
  - If the prompt/answer names specific devices, generate EXACTLY those, deduped ("mobile" only → one mobile frame; "mobile, tablet, desktop" → all three).
361
- - If nothing about form factor is specified — or the answer is "Both / responsive" or "Decide for me" — default to `["desktop","mobile"]`: a Desktop base + a Mobile frame only. Never auto-add a tablet, a redundant desktop, or a stray duplicate frame.
362
+ - If no exact pixel size or form factor is specified — or the answer is "Both / responsive" or "Decide for me" — default to `["desktop","mobile"]`: a Desktop base + a Mobile frame only. Never auto-add a tablet, a redundant desktop, or a stray duplicate frame.
362
363
 
363
364
  The WIDEST requested device is the base/primary frame; narrower devices become breakpoint frames (never at the primary width). Device frame sizes: mobile 390×844, tablet 768×1024, desktop 1440×900. `present-design-variants` still takes explicit `width`/`height` per variant to size its exploration screens (see Phase 2).
364
365
 
@@ -390,7 +391,7 @@ screen name.
390
391
 
391
392
  Each `content` is a complete, self-contained document (Alpine.js + Tailwind via CDN, full `<head>`, CSS variables in `:root`). Variations should be **structurally and compositionally distinct** — different layout grammars, hierarchy, density, and focal points — never just color swaps. When a design system is linked, keep its tokens, typography, components, and imagery rules fixed across variants; vary those only when the user explicitly asks to explore a replacement system. Label the directions with concrete names ("Editorial split", not "Variant A").
392
393
 
393
- Pass `width`/`height` on every variant to match the form-factor answer (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — the example above is desktop-sized. When `content` is omitted, `present-design-variants` infers a size from the prompt/label/description text and the width/height you pass still wins when given.
394
+ Pass `width`/`height` on every variant to match the form-factor answer (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — the example above is desktop-sized. When the prompt specifies exact pixel dimensions, use that exact size on every variant and pass `responsive: false`; do not clamp or add mobile frames. When `content` is omitted, `present-design-variants` infers a size from the prompt/label/description text and the width/height you pass still wins when given.
394
395
 
395
396
  Wait for the user's pick before refining. Once they choose, keep the selected
396
397
  screen, delete the unchosen variant screens with `delete-file`, and continue
@@ -424,9 +425,12 @@ Pass the `devices` param (`("mobile"|"tablet"|"desktop")[]`, default `["desktop"
424
425
 
425
426
  #### Non-web sizes — ad units, print one-pagers, social sizes
426
427
 
427
- `canvasFrames` accepts any exact `width`/`height` in px, so "create a 300x250
428
- ad" style requests work the same way — no special action, just the pixel
429
- dimensions the artifact actually needs. The editor's own Frame tool preset
428
+ `canvasFrames` accepts exact `width`/`height` in px, so "create a 300x250
429
+ ad" style requests work the same way — use the requested dimensions verbatim
430
+ and pass `devices: []` so the fixed-size screen has no extra mobile view. Keep
431
+ each action prompt to one distinct exact size; use separate calls for screens
432
+ with different dimensions. This also applies to exact-size email and social
433
+ assets. The editor's own Frame tool preset
430
434
  list (`app/components/design/inspector/frame-size-presets.ts`) documents the
431
435
  canonical sizes to reuse instead of guessing:
432
436
 
@@ -475,7 +479,9 @@ by unlinking the system. It is the one check that can catch a design that
475
479
  looks fine and is still not the user's.
476
480
 
477
481
  After the audit is clean, call `take-design-screenshot` on each changed screen
478
- (default: 1280px desktop + 375px mobile). Fix everything its `diagnostics`
482
+ (default: 1280px desktop + 375px mobile). For an exact-size screen, capture
483
+ only its specified viewport by passing `widths: [width]` and
484
+ `heights: [height]`. Fix everything its `diagnostics`
479
485
  report flags — real computed contrast ratios, horizontal/container overflow,
480
486
  broken images, zero-size or off-screen text, console errors — before reporting
481
487
  the design as ready; this is the same "visually inspect the result" pass, done