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

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 (41) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/actions/create-recording.ts +1 -1
  3. package/corpus/templates/clips/actions/lib/auto-title-candidates.ts +42 -0
  4. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +1 -1
  5. package/corpus/templates/clips/actions/lib/transcript-text.ts +16 -0
  6. package/corpus/templates/clips/actions/list-ai-requests.ts +49 -31
  7. package/corpus/templates/clips/actions/list-recordings.ts +2 -4
  8. package/corpus/templates/clips/actions/list-transactional-email-ai-requests.ts +12 -6
  9. package/corpus/templates/clips/actions/regenerate-title.ts +4 -1
  10. package/corpus/templates/clips/actions/request-transcript.ts +1 -1
  11. package/corpus/templates/clips/actions/save-browser-transcript.ts +1 -1
  12. package/corpus/templates/clips/app/components/editable-recording-title.tsx +1 -1
  13. package/corpus/templates/clips/app/components/library/recording-card.tsx +1 -1
  14. package/corpus/templates/clips/app/hooks/use-auto-title.ts +194 -219
  15. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +2 -1
  16. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  17. package/corpus/templates/clips/server/lib/transactional-email-store.ts +42 -2
  18. package/corpus/templates/clips/shared/ai-request-status.ts +6 -0
  19. package/corpus/templates/clips/{actions/lib → shared}/title-source.ts +9 -2
  20. package/dist/agent/production-agent.js +2 -0
  21. package/dist/agent/run-manager.d.ts +2 -0
  22. package/dist/agent/run-manager.js +9 -2
  23. package/dist/agent/thread-data-builder.d.ts +1 -0
  24. package/dist/agent/thread-data-builder.js +148 -0
  25. package/dist/automation/index.d.ts +1 -1
  26. package/dist/chat-threads/store.d.ts +1 -0
  27. package/dist/chat-threads/store.js +244 -21
  28. package/dist/client/AgentKitAssistantChat.js +23 -12
  29. package/dist/client/chat/agentkit-agent-native.js +409 -8
  30. package/dist/client/chat/agentkit-protocol.js +3 -0
  31. package/dist/collab/awareness.d.ts +2 -2
  32. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  33. package/dist/notifications/routes.d.ts +3 -3
  34. package/dist/observability/routes.d.ts +2 -2
  35. package/dist/resources/handlers.d.ts +1 -1
  36. package/dist/server/agent-chat-plugin.d.ts +2 -0
  37. package/dist/server/agent-chat-plugin.js +12 -7
  38. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  39. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  40. package/dist/server/realtime-token.d.ts +1 -1
  41. package/package.json +1 -1
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: 10510
@@ -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 {