@agent-native/core 0.196.0 → 0.196.1-nightly-20260928204657

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 (51) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +1 -1
  3. package/corpus/templates/clips/actions/create-recording.ts +1 -1
  4. package/corpus/templates/clips/actions/lib/auto-title-candidates.ts +42 -0
  5. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +1 -1
  6. package/corpus/templates/clips/actions/lib/transcript-text.ts +16 -0
  7. package/corpus/templates/clips/actions/list-ai-requests.ts +49 -31
  8. package/corpus/templates/clips/actions/list-recordings.ts +2 -4
  9. package/corpus/templates/clips/actions/list-transactional-email-ai-requests.ts +12 -6
  10. package/corpus/templates/clips/actions/regenerate-title.ts +4 -1
  11. package/corpus/templates/clips/actions/request-transcript.ts +1 -1
  12. package/corpus/templates/clips/actions/save-browser-transcript.ts +1 -1
  13. package/corpus/templates/clips/app/components/editable-recording-title.tsx +1 -1
  14. package/corpus/templates/clips/app/components/library/recording-card.tsx +1 -1
  15. package/corpus/templates/clips/app/hooks/use-auto-title.ts +194 -219
  16. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +2 -1
  17. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  18. package/corpus/templates/clips/changelog/2026-09-28-clip-share-previews-show-a-branded-image-while-the-recording.md +6 -0
  19. package/corpus/templates/clips/server/lib/slack-unfurls.ts +2 -0
  20. package/corpus/templates/clips/server/lib/transactional-email-store.ts +42 -2
  21. package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +27 -15
  22. package/corpus/templates/clips/shared/ai-request-status.ts +6 -0
  23. package/corpus/templates/clips/shared/share-meta.ts +2 -27
  24. package/corpus/templates/clips/{actions/lib → shared}/title-source.ts +9 -2
  25. package/corpus/templates/slides/changelog/2026-09-28-typing-in-slides-agent-chat-keeps-every-character-in-order.md +6 -0
  26. package/corpus/templates/slides/scripts/edit-fidelity/README.md +11 -2
  27. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +135 -4
  28. package/dist/agent/production-agent.js +2 -0
  29. package/dist/agent/run-manager.d.ts +2 -0
  30. package/dist/agent/run-manager.js +9 -2
  31. package/dist/agent/thread-data-builder.d.ts +1 -0
  32. package/dist/agent/thread-data-builder.js +148 -0
  33. package/dist/chat-threads/store.d.ts +1 -0
  34. package/dist/chat-threads/store.js +244 -21
  35. package/dist/client/AgentKitAssistantChat.js +23 -12
  36. package/dist/client/chat/agentkit-agent-native.js +409 -8
  37. package/dist/client/chat/agentkit-protocol.js +3 -0
  38. package/dist/collab/awareness.d.ts +2 -2
  39. package/dist/collab/struct-routes.d.ts +1 -1
  40. package/dist/notifications/routes.d.ts +1 -1
  41. package/dist/observability/routes.d.ts +6 -6
  42. package/dist/org/handlers.d.ts +1 -1
  43. package/dist/resources/handlers.d.ts +1 -1
  44. package/dist/secrets/routes.d.ts +4 -4
  45. package/dist/server/agent-chat-plugin.d.ts +2 -0
  46. package/dist/server/agent-chat-plugin.js +12 -7
  47. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  48. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  49. package/dist/server/realtime-token.d.ts +1 -1
  50. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  51. package/package.json +2 -2
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 10508
34
+ - template files: 10512
@@ -176,7 +176,7 @@ The playable Slack embed is deliberately narrower than the share page:
176
176
 
177
177
  - Only `ready` recordings with `visibility === "public"` can produce a video block.
178
178
  - Password-protected, expired, archived, trashed, private, org-only, or still-processing clips must not produce a playable Slack block.
179
- - Slack thumbnails use the same-origin proxy for stored thumbnails, or a public video frame when no stored thumbnail exists; normal share-page metadata remains the fallback when no Slack app is installed.
179
+ - Slack thumbnails use the same-origin proxy for stored thumbnails and the branded social image while one is unavailable. The proxy redirects to that image if stored content cannot be served; normal share-page metadata remains the fallback when no Slack app is installed.
180
180
  - Do not put passwords, short-lived share tokens, raw provider URLs, or transcript text in Slack unfurl payloads.
181
181
 
182
182
  Required Slack app setup:
@@ -26,9 +26,9 @@ import {
26
26
  allowsSqlRecordingChunkScratch,
27
27
  STORAGE_SETUP_REQUIRED_REASON,
28
28
  } from "../server/lib/video-storage.js";
29
+ import { DEFAULT_RECORDING_TITLE } from "../shared/title-source.js";
29
30
  import { createRecordingSchema } from "./lib/create-recording-schema.js";
30
31
  import { validateRecordingScope } from "./lib/recording-scope.js";
31
- import { DEFAULT_RECORDING_TITLE } from "./lib/title-source.js";
32
32
 
33
33
  export function classifyInitialUploadFailure(error: unknown): {
34
34
  failureCode: RecordingFailureCode;
@@ -0,0 +1,42 @@
1
+ import { and, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
2
+
3
+ import { getDb, schema } from "../../server/db/index.js";
4
+ import { ownerEmailMatches } from "../../server/lib/recordings.js";
5
+ import type { AutoTitleCandidate } from "../../shared/ai-request-status.js";
6
+ import {
7
+ AUTO_REPLACEABLE_TITLE_SOURCES,
8
+ DEFAULT_RECORDING_TITLE,
9
+ } from "../../shared/title-source.js";
10
+ import { transcriptHasTextSql } from "./transcript-text.js";
11
+
12
+ export const AUTO_TITLE_CANDIDATE_LIMIT = 50;
13
+
14
+ /**
15
+ * The owner's ready recordings that still carry a replaceable title and have
16
+ * transcript text to title from — the SQL form of `isAutoTitleReplaceable`.
17
+ */
18
+ export function listAutoTitleCandidates(
19
+ ownerEmail: string,
20
+ ): Promise<AutoTitleCandidate[]> {
21
+ const r = schema.recordings;
22
+ const t = schema.recordingTranscripts;
23
+ return getDb()
24
+ .select({ id: r.id, createdAt: r.createdAt })
25
+ .from(r)
26
+ .innerJoin(t, eq(t.recordingId, r.id))
27
+ .where(
28
+ and(
29
+ ownerEmailMatches(r.ownerEmail, ownerEmail),
30
+ eq(r.status, "ready"),
31
+ isNull(r.trashedAt),
32
+ or(
33
+ inArray(r.titleSource, [...AUTO_REPLACEABLE_TITLE_SOURCES]),
34
+ sql`TRIM(${r.title}) IN ('', ${DEFAULT_RECORDING_TITLE})`,
35
+ ),
36
+ eq(t.status, "ready"),
37
+ transcriptHasTextSql(t),
38
+ ),
39
+ )
40
+ .orderBy(desc(r.createdAt))
41
+ .limit(AUTO_TITLE_CANDIDATE_LIMIT);
42
+ }
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { RECORDING_TITLE_SOURCES } from "./title-source.js";
3
+ import { RECORDING_TITLE_SOURCES } from "../../shared/title-source.js";
4
4
 
5
5
  const cliBoolean = z
6
6
  .union([z.boolean(), z.enum(["true", "false"])])
@@ -0,0 +1,16 @@
1
+ import { sql, type SQL } from "drizzle-orm";
2
+
3
+ import { schema } from "../../server/db/index.js";
4
+
5
+ /**
6
+ * True when a transcript row carries any spoken text, in either the flat text
7
+ * or at least one non-empty segment. NULL for a missing (left-joined) row.
8
+ */
9
+ export function transcriptHasTextSql(
10
+ transcripts: typeof schema.recordingTranscripts = schema.recordingTranscripts,
11
+ ): SQL<boolean | null> {
12
+ return sql<boolean | null>`(
13
+ TRIM(${transcripts.fullText}) <> ''
14
+ OR ${transcripts.segmentsJson} ~ '"text":"[^"]'
15
+ )`;
16
+ }
@@ -1,48 +1,66 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
2
  import { listAppState } from "@agent-native/core/application-state";
3
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
3
4
  import { accessFilter } from "@agent-native/core/sharing";
4
5
  import { and, eq, inArray } from "drizzle-orm";
5
6
  import { z } from "zod";
6
7
 
7
8
  import { getDb, schema } from "../server/db/index.js";
9
+ import { listAutoTitleCandidates } from "./lib/auto-title-candidates.js";
8
10
 
9
11
  const REQUEST_PREFIX = "clips-ai-request-";
10
12
 
13
+ type QueuedAiRequest = Record<string, unknown> & { recordingId: string };
14
+
15
+ async function listQueuedRequests(): Promise<QueuedAiRequest[]> {
16
+ const entries = await listAppState(REQUEST_PREFIX);
17
+
18
+ const requests = entries
19
+ .map((e) => e.value as Record<string, unknown>)
20
+ .filter(
21
+ (v): v is QueuedAiRequest =>
22
+ !!v && typeof v.recordingId === "string" && v.recordingId.length > 0,
23
+ );
24
+
25
+ if (requests.length === 0) return [];
26
+
27
+ const recordingIds = [...new Set(requests.map((r) => r.recordingId))];
28
+
29
+ const db = getDb();
30
+ const accessible = await db
31
+ .select({ id: schema.recordings.id, title: schema.recordings.title })
32
+ .from(schema.recordings)
33
+ .where(
34
+ and(
35
+ accessFilter(schema.recordings, schema.recordingShares),
36
+ inArray(schema.recordings.id, recordingIds),
37
+ eq(schema.recordings.status, "ready"),
38
+ ),
39
+ );
40
+
41
+ const titles = new Map(accessible.map((r) => [r.id, r.title]));
42
+
43
+ // Not every request kind stores `currentTitle` when queued; fill it from the
44
+ // recording so every delivered request tells the agent what the clip is.
45
+ return requests
46
+ .filter((r) => titles.has(r.recordingId))
47
+ .map((r) => ({
48
+ ...r,
49
+ currentTitle: r.currentTitle ?? titles.get(r.recordingId),
50
+ }));
51
+ }
52
+
11
53
  export default defineAction({
12
54
  description:
13
- "List pending clips AI requests (regenerate-title, summary, chapters, etc.) for recordings the current user can access.",
55
+ "List pending clips AI requests (regenerate-title, summary, chapters, etc.) for recordings the current user can access, plus the current user's ready recordings that still need an auto-generated title.",
14
56
  schema: z.object({}),
15
57
  http: { method: "GET" },
16
58
  run: async () => {
17
- const entries = await listAppState(REQUEST_PREFIX);
18
-
19
- const requests = entries
20
- .map((e) => e.value as Record<string, unknown>)
21
- .filter(
22
- (v): v is Record<string, unknown> & { recordingId: string } =>
23
- !!v && typeof v.recordingId === "string" && v.recordingId.length > 0,
24
- );
25
-
26
- if (requests.length === 0) return { requests: [] };
27
-
28
- const recordingIds = [...new Set(requests.map((r) => r.recordingId))];
29
-
30
- const db = getDb();
31
- const accessible = await db
32
- .select({ id: schema.recordings.id })
33
- .from(schema.recordings)
34
- .where(
35
- and(
36
- accessFilter(schema.recordings, schema.recordingShares),
37
- inArray(schema.recordings.id, recordingIds),
38
- eq(schema.recordings.status, "ready"),
39
- ),
40
- );
41
-
42
- const accessibleIds = new Set(accessible.map((r) => r.id));
43
-
44
- return {
45
- requests: requests.filter((r) => accessibleIds.has(r.recordingId)),
46
- };
59
+ const email = getRequestUserEmail();
60
+ const [requests, titleCandidates] = await Promise.all([
61
+ listQueuedRequests(),
62
+ email ? listAutoTitleCandidates(email) : [],
63
+ ]);
64
+ return { requests, titleCandidates };
47
65
  },
48
66
  });
@@ -31,6 +31,7 @@ import {
31
31
  parseSpaceIds,
32
32
  } from "../server/lib/recordings.js";
33
33
  import { profileNameFor } from "../server/lib/user-identities.js";
34
+ import { transcriptHasTextSql } from "./lib/transcript-text.js";
34
35
 
35
36
  function escapeLike(s: string): string {
36
37
  return s.replace(/([\\%_])/g, "\\$1");
@@ -320,10 +321,7 @@ export default defineAction({
320
321
  },
321
322
  transcriptStatus: schema.recordingTranscripts.status,
322
323
  transcriptHasText: sql<number>`(
323
- CASE WHEN (
324
- TRIM(COALESCE(${schema.recordingTranscripts.fullText}, '')) <> ''
325
- OR COALESCE(${schema.recordingTranscripts.segmentsJson}, '') LIKE '%"text":"_%'
326
- ) THEN 1 ELSE 0 END
324
+ CASE WHEN ${transcriptHasTextSql()} THEN 1 ELSE 0 END
327
325
  )`,
328
326
  })
329
327
  .from(schema.recordings)
@@ -1,6 +1,6 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
2
  import { accessFilter, resolveAccess } from "@agent-native/core/sharing";
3
- import { and, eq, gte, inArray, ne, or } from "drizzle-orm";
3
+ import { and, eq, gte, inArray, ne, or, sql } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
@@ -19,6 +19,9 @@ const MAX_CLAIMS = 10;
19
19
  const MAX_TITLE_LENGTH = 200;
20
20
  const MAX_DESCRIPTION_LENGTH = 600;
21
21
  export const MAX_TRANSCRIPT_EXCERPT_LENGTH = 1_200;
22
+ // boundedText collapses whitespace before slicing, so the SQL prefix must be
23
+ // far longer than the excerpt or whitespace-heavy transcripts come back short.
24
+ const TRANSCRIPT_FETCH_LENGTH = 20_000;
22
25
 
23
26
  export type TransactionalEmailContextPacket = {
24
27
  recordingId: string;
@@ -183,7 +186,9 @@ async function loadContextPackets(
183
186
  db
184
187
  .select({
185
188
  recordingId: schema.recordingTranscripts.recordingId,
186
- fullText: schema.recordingTranscripts.fullText,
189
+ fullText: sql<
190
+ string | null
191
+ >`left(${schema.recordingTranscripts.fullText}, ${TRANSCRIPT_FETCH_LENGTH})`,
187
192
  })
188
193
  .from(schema.recordingTranscripts)
189
194
  .where(
@@ -263,12 +268,13 @@ export async function claimTransactionalEmailAiRequests(
263
268
  ): Promise<{ requests: ClaimedTransactionalEmailAiRequest[] }> {
264
269
  const claimant = normalizeEmail(claimantEmail);
265
270
  const claimLimit = Math.min(Math.max(limit, 1), MAX_CLAIMS);
266
- const config = await transactionalEmailStore.readConfig();
271
+ const [config, claimCandidates] = await Promise.all([
272
+ transactionalEmailStore.readConfig(),
273
+ transactionalEmailStore.listAiClaimCandidates(claimant),
274
+ ]);
267
275
  if (!config) return { requests: [] };
268
276
  const staleBefore = new Date(Date.now() - AI_DISPATCH_STALE_MS);
269
- const candidates = (
270
- await transactionalEmailStore.listJobs(["awaiting_ai", "ai_dispatched"])
271
- ).filter(
277
+ const candidates = claimCandidates.filter(
272
278
  (job) =>
273
279
  isAiBackedType(job.type) &&
274
280
  (job.state === "awaiting_ai" ||
@@ -8,6 +8,10 @@ import { z } from "zod";
8
8
  import { getDb, schema } from "../server/db/index.js";
9
9
  import { isBuilderCreditsExhaustedMessage } from "../shared/builder-credits.js";
10
10
  import { withFullVideoAiInstructions } from "../shared/clips-ai-prefs.js";
11
+ import {
12
+ isAutoTitleReplaceable,
13
+ isDefaultTitle,
14
+ } from "../shared/title-source.js";
11
15
  import cleanupTranscript from "./cleanup-transcript.js";
12
16
  import { loadAgentsMdContext } from "./lib/agents-md-context.js";
13
17
  import {
@@ -20,7 +24,6 @@ import {
20
24
  cleanGeneratedTitle,
21
25
  fallbackTitleFromTranscript,
22
26
  } from "./lib/title-fallback.js";
23
- import { isAutoTitleReplaceable, isDefaultTitle } from "./lib/title-source.js";
24
27
  import regenerateSummary from "./regenerate-summary.js";
25
28
 
26
29
  function transcriptTextFromSegments(raw: string | null | undefined): string {
@@ -21,6 +21,7 @@ import {
21
21
  } from "../server/lib/recordings.js";
22
22
  import { isBuilderCreditsExhaustedMessage } from "../shared/builder-credits.js";
23
23
  import { normalizeLoomShareUrl } from "../shared/loom.js";
24
+ import { isAutoTitleReplaceable } from "../shared/title-source.js";
24
25
  import {
25
26
  isRetryableTranscriptFailure,
26
27
  transcriptFailureMessage,
@@ -55,7 +56,6 @@ import {
55
56
  isLikelyMismatchedTranscriptLanguage,
56
57
  normalizeProviderTranscript,
57
58
  } from "./lib/provider-transcript.js";
58
- import { isAutoTitleReplaceable } from "./lib/title-source.js";
59
59
  import regenerateSummary from "./regenerate-summary.js";
60
60
  import regenerateTitle from "./regenerate-title.js";
61
61
 
@@ -8,10 +8,10 @@ import { z } from "zod";
8
8
  import { getDb, schema } from "../server/db/index.js";
9
9
  import { dispatchPostFinalizeJob } from "../server/lib/post-finalize-dispatch.js";
10
10
  import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
11
+ import { isAutoTitleReplaceable } from "../shared/title-source.js";
11
12
  import { buildCaptionSegmentsFromText } from "../shared/transcript-segments.js";
12
13
  import { booleanParam } from "./lib/cli-params.js";
13
14
  import { finalizeEndedMeetingsForRecording } from "./lib/finalize-ended-meetings.js";
14
- import { isAutoTitleReplaceable } from "./lib/title-source.js";
15
15
 
16
16
  function nativeSegmentsJson(
17
17
  fullText: string,
@@ -1,5 +1,6 @@
1
1
  import { useActionMutation } from "@agent-native/core/client/hooks";
2
2
  import { useT } from "@agent-native/core/client/i18n";
3
+ import { isDefaultTitle } from "@shared/title-source";
3
4
  import { IconEdit } from "@tabler/icons-react";
4
5
  import { useQueryClient } from "@tanstack/react-query";
5
6
  import { useEffect, useRef, useState, type ReactNode } from "react";
@@ -7,7 +8,6 @@ import { toast } from "sonner";
7
8
 
8
9
  import { Input } from "@/components/ui/input";
9
10
  import { Skeleton } from "@/components/ui/skeleton";
10
- import { isDefaultTitle } from "@/hooks/use-auto-title";
11
11
  import { cn } from "@/lib/utils";
12
12
 
13
13
  interface EditableRecordingTitleProps {
@@ -1,6 +1,7 @@
1
1
  import { useFeatureFlag } from "@agent-native/core/client/feature-flags";
2
2
  import { useFormatters, useT } from "@agent-native/core/client/i18n";
3
3
  import { UPLOAD_RETRY_RESUME_FLAG } from "@shared/feature-flags";
4
+ import { isDefaultTitle } from "@shared/title-source";
4
5
  import { isRetryableUploadInterruption } from "@shared/upload-interruption";
5
6
  import {
6
7
  IconDotsVertical,
@@ -47,7 +48,6 @@ import {
47
48
  DropdownMenuTrigger,
48
49
  } from "@/components/ui/dropdown-menu";
49
50
  import { Skeleton } from "@/components/ui/skeleton";
50
- import { isDefaultTitle } from "@/hooks/use-auto-title";
51
51
  import type { RecordingSummary } from "@/hooks/use-library";
52
52
  import { attemptOpenDesktopApp } from "@/lib/capture-install-options";
53
53
  import {